Cara Menguji Keamanan API dari Input Berbahaya Sebelum Diretas

Input adalah permukaan serangan. Buat pengujian negatif untuk injeksi, muatan yang terlalu besar, dan muatan yang salah bentuk, jadikan skema sebagai kontrol keamanan, dan jalankan di CI.

Ashley Innocent

Ashley Innocent

23 July 2026

Cara Menguji Keamanan API dari Input Berbahaya Sebelum Diretas

Apidog untuk Perusahaan

Penerapan On-Premises

SSO & RBAC

Sesuai SOC 2

Jelajahi Apidog Enterprise
TL;DR: Input API Anda adalah permukaan serangan, jadi ujilah seperti itu. Tulis kasus negatif yang mengirimkan bidang berukuran besar, tipe yang salah, isi yang salah format, dan string injeksi, lalu pastikan *endpoint* menjawab dengan kode 4xx dan tidak pernah 5xx. Ubah validasi skema menjadi kontrol keamanan dengan additionalProperties: false, *enum*, dan batasan panjang. Jalankan seluruh *suite* di CI pada setiap perubahan. Agen AI membuat ini mendesak: mereka menghasilkan dan meneruskan *payload* dengan kecepatan mesin, sehingga "muat data ini" yang diam-diam menjadi "jalankan kode ini" kini skalanya meningkat.

Sebagian besar *test suite* membuktikan bahwa API Anda berfungsi ketika pemanggil berlaku sopan. Anda mengirimkan badan yang valid, Anda mendapatkan 200, penegasan berhasil. Hasil itu hampir tidak memberi tahu Anda apa pun tentang apa yang terjadi ketika badan tersebut bersifat *hostile*. Input yang tidak tepercaya adalah data apa pun yang tidak dihasilkan sendiri oleh *endpoint* Anda: badan permintaan, *query string*, *header*, unggahan berkas, *payload* *webhook*, dan JSON yang dikumpulkan agen AI dengan cepat. Semuanya pantas mendapatkan asumsi yang sama, yaitu bahwa seseorang pada akhirnya akan mengirimkan versi terburuknya.

Pada Juli 2026, Hugging Face menggambarkan insiden keamanan yang vektor masuknya adalah data, bukan kata sandi yang dicuri. Kami membahas pelajaran dari pelanggaran tersebut secara terpisah; panduan ini adalah bagian praktisnya. Anda akan membangun tes yang mengirimkan jenis input yang dikirimkan penyerang, lalu menjalankannya secara otomatis pada setiap perubahan. Kategorinya selaras dengan OWASP API Security Top 10, yang patut tetap dibuka di sebuah tab. Apidog adalah salah satu cara untuk merancang kontrak dan menggerakkan tes ini, tetapi idenya berlaku di kerangka kerja apa pun yang sudah Anda gunakan.

Input adalah permukaan serangan, bukan bidang formulir

Validasi sering diperlakukan sebagai kesopanan pengalaman pengguna: menangkap email kosong, menampilkan batas merah, lalu lanjut. Pembingkaian itu adalah masalahnya. Setiap bidang yang diterima API Anda adalah janji yang dapat dilanggar oleh pemanggil, dan setiap janji yang dilanggar adalah jalur ke dalam logika Anda. Parameter limit yang Anda harapkan berupa bilangan bulat kecil menjadi 999999999. filename yang Anda harapkan berupa satu kata menjadi ../../etc/passwd. Objek config yang Anda harapkan menyimpan pengaturan menjadi serangkaian instruksi.

Pengujian keamanan bukanlah disiplin terpisah yang ditambahkan di akhir. Ini adalah pengujian negatif yang sama yang sudah Anda ketahui, ditujukan pada bidang yang paling mungkin merugikan Anda. Jika Anda membiasakan diri bertanya "apa hal terburuk yang bisa masuk ke bidang ini," Anda sudah setengah jalan menuju praktik dalam panduan praktik terbaik keamanan API kami. Sisa artikel ini mengubah satu pertanyaan itu menjadi tes konkret yang dapat Anda jalankan.

Bagaimana “muat data ini” menjadi “jalankan kode ini”

Insiden Hugging Face adalah contoh nyata mengapa input layak mendapatkan perhatian ini. Hugging Face mengatakan vektor masuknya adalah *dataset* yang berbahaya: *dataset* yang dibuat memicu pemuat *dataset* *remote-code*, dan injeksi *template* berada di dalam konfigurasi *dataset*. Anda dapat membaca laporan perusahaan sendiri di laporan insiden keamanannya.

Renungkan bentuk kegagalan itu. Sebuah *endpoint* menerima sesuatu yang digambarkan sebagai data. Memuat data tersebut menjalankan jalur kode yang dapat mengeksekusi instruksi yang dikendalikan penyerang. "Muat data ini" menjadi "jalankan kode ini." Injeksi *template* adalah cerita yang sama dalam skala yang lebih kecil: nilai konfigurasi yang seharusnya berupa teks *inert* dievaluasi, sehingga teks menjadi eksekusi.

Pelajaran yang bisa diambil bukanlah "Hugging Face melakukan kesalahan langka." Melainkan bahwa setiap *endpoint* yang menerima nama pemuat, format, *template*, objek terserialisasi, atau *blob* konfigurasi adalah menerima instruksi, terlepas dari apakah Anda sengaja melakukannya atau tidak. Jika Anda tidak pernah menulis tes yang mengirimkan konfigurasi berbahaya ke *endpoint* tersebut, Anda tidak pernah benar-benar memeriksa asumsi bahwa ia tetap *inert*. Asumsi yang tidak teruji itulah seluruh kerentanannya.

Validasi skema sebagai kontrol keamanan

Kontrol termurah yang bisa Anda tambahkan adalah skema yang ketat di bagian tepi. Skema bukan hanya dokumentasi. Ketika Anda menolak apa pun yang tidak sesuai, skema menjadi filter yang berjalan sebelum logika bisnis Anda melihat permintaan. JSON Schema memberi Anda *primitive* untuk membuat filter itu ketat.

Berikut adalah skema untuk konfigurasi *dataset* dari cerita tersebut, ditulis sedemikian rupa sehingga sebagian besar input berbahaya tidak pernah mencapai kode aplikasi:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["loader", "name"],
  "properties": {
    "loader": { "enum": ["csv", "json", "parquet"] },
    "name": { "type": "string", "maxLength": 128, "pattern": "^[\\w .-]+$" },
    "rows": { "type": "integer", "minimum": 0, "maximum": 1000000 }
  }
}

Bacalah sebagai empat pertahanan terpisah. additionalProperties: false secara langsung menolak bidang template yang diselundupkan, sehingga penyerang tidak dapat menambahkannya. *Enum* loader berarti pickle:// atau pemuat kode jarak jauh apa pun bukanlah nilai yang valid. maxLength menghentikan *string* multi-megabyte yang bertujuan untuk menghabiskan memori. pattern pada name menolak karakter {{ dan '; DROP TABLE sebelum mereka bergerak lebih jauh. Tidak satu pun dari baris ini mengetahui tentang penyerang. Mereka hanya menerima kumpulan input sempit yang sebenarnya Anda dukung, dan kesempitan itulah properti keamanannya.

Validasi kontrak seperti ini tidak menangkap setiap eksploitasi, dan tidak ada skema yang akan melakukannya. Yang ditutup adalah kategori spesifik dan umum: *bug* "kita tidak pernah memeriksa apa yang diterima *endpoint* ini". Kategori itulah di mana sejumlah besar pelanggaran dimulai.

Pengujian negatif: buktikan bahwa *endpoint* menolak

Tes *happy-path* menegaskan bahwa input yang baik menghasilkan *output* yang baik. Tes negatif menegaskan bahwa input yang buruk menghasilkan penolakan yang terkontrol. Perbedaan itu penting karena penolakan adalah sebuah fitur: kode 400 dengan kesalahan yang jelas adalah API Anda yang mempertahankan batasnya. Kode 500 adalah API Anda yang kehilangan kendali atas batas tersebut.

Bangun kasus negatif dengan cara yang sama setiap saat. Untuk setiap bidang, tuliskan apa yang harus ditolaknya: tipe yang salah, hilang saat wajib, ada saat terlarang, terlalu panjang, di luar jangkauan, dan *string* injeksi yang sesuai dengan formatnya. Kemudian, tegaskan dua hal pada respons. Pertama, statusnya adalah 4xx, biasanya 400 atau 422. Kedua, statusnya tidak pernah 5xx. Kode 500 berarti input berbahaya Anda mencapai kode yang tidak siap untuknya, yang justru merupakan jangkauan yang diinginkan penyerang. Daftar periksa pengujian keamanan API kami memiliki daftar awal bidang-per-bidang yang dapat Anda sesuaikan.

Satu aturan menjaga ini tetap jujur: tegaskan pada perilaku, bukan pada teks kesalahan. Jika Anda menegaskan bahwa pesan berbunyi "loader tidak valid," *refactor* yang tidak berbahaya akan merusak tes Anda dan mengajarkan tim untuk melonggarkannya. Tegaskan kode status, dan jika memungkinkan, tegaskan bahwa tidak ada efek samping yang terjadi sama sekali.

Kelas injeksi yang layak mendapatkan tes khusus

Beberapa *family* injeksi cukup sering muncul sehingga masing-masing layak mendapatkan kasus uji standar, bukan pemeriksaan manual satu kali. Anda tidak perlu menyeluruh di sini. Anda memerlukan satu kasus *probing* per kelas sehingga regresi gagal secara keras. Alat yang menjalankan deteksi kerentanan API otomatis dapat memperluas cakupan nanti, tetapi beberapa kasus yang ditulis tangan menangkap celah yang jelas terlebih dahulu.

Ukuran berlebihan, salah format, dan kebingungan jenis konten

Tidak setiap input berbahaya adalah *string* yang cerdas. Beberapa hanya terlalu besar atau berbentuk salah, dan ini seringkali merusak *parser* sebelum logika validasi Anda bahkan berjalan.

Kirim *payload* yang kelebihan ukuran: satu bidang yang berisi lima megabyte satu karakter, atau larik JSON dengan sejuta elemen. Sebuah API yang sehat memberlakukan batas ukuran badan dan mengembalikan 413 daripada mengalokasikan memori hingga kehabisan. Kirim juga badan yang salah format: JSON yang terpotong, koma di belakang, atau JSON yang bersarang seribu tingkat dalamnya untuk menyelidiki kelelahan tumpukan (*stack exhaustion*). Jawaban yang benar adalah kode 400 yang cepat, bukan *worker* yang terhenti.

Kebingungan jenis konten adalah yang paling senyap. Deklarasikan Content-Type: application/json tetapi kirim XML, atau deklarasikan application/xml dan kirim *payload* dengan entitas eksternal untuk menyelidiki XXE. Balikkan dan kirim JSON sebagai text/plain untuk melihat apakah *parser* yang longgar menerimanya. Setiap ketidakcocokan menguji apakah server Anda memercayai *header*, memercayai badan, atau memeriksa apakah keduanya setuju. Seharusnya membutuhkan persetujuan sebelum *parsing* apa pun.

Mengapa agen AI meningkatkan taruhan

Semua yang disebutkan di atas sudah berlaku sebelum agen ada. Agen mengubah volume dan kecepatan. Penyerang manusia mengetik satu permintaan berbahaya pada satu waktu. Agen AI menghasilkan dan meneruskan *payload* dengan kecepatan mesin, dan ia akan dengan senang hati membangun input yang tidak akan pernah dicoba oleh manusia.

Tiga properti memperburuk ini. Agen mensintesis input, sehingga mereka menghasilkan nilai bidang yang tidak ditulis oleh manusia dan tidak diantisipasi oleh tes. Agen mencoba ulang dan merangkai panggilan, sehingga satu dokumen hulu yang terkontaminasi dapat berubah menjadi ribuan permintaan berbahaya terhadap *endpoint* Anda dalam hitungan detik. Dan agen meneruskan data yang diperintahkan untuk dipercayai, begitulah cara *payload* yang tersembunyi dalam *dataset* atau *webhook* menjadi permintaan nyata ke API Anda. Pola Hugging Face, di mana "muat data ini" berubah menjadi "jalankan kode ini," justru merupakan jenis instruksi yang akan dibawa oleh agen melintasi batas kepercayaan tanpa menyadarinya. Catatan kami tentang injeksi *prompt* untuk tim API membahas lebih dalam tentang *hand-off* tersebut. Pertahanannya tidak berubah; itu hanya harus otomatis, karena Anda tidak dapat meninjau lalu lintas agen secara manual.

Bangun *suite* negatif dan jalankan di CI pada setiap perubahan

Ubah kasus di atas menjadi *suite* yang berjalan pada setiap *pull request*. Berikut adalah versi berparameter ringkas dalam *pytest* yang memukul *staging endpoint* dan menegaskan penolakan yang terkontrol:

import httpx
import pytest

BASE = "https://staging.internal/v1"

HOSTILE_CONFIGS = [
    {"loader": "pickle://s3/models/payload.pkl", "format": "auto"},  # remote-code loader
    {"loader": "csv", "name": "{{ 7*7 }}"},                          # template injection
    {"loader": "csv", "name": "{{ config.__class__ }}"},             # object traversal
    {"loader": "csv", "filter": "1); DROP TABLE datasets;--"},       # SQL injection
    {"loader": "csv", "name": "A" * 5_000_000},                      # oversized field
]

@pytest.mark.parametrize("config", HOSTILE_CONFIGS)
def test_dataset_config_is_refused(config):
    r = httpx.post(f"{BASE}/datasets", json={"config": config}, timeout=10)
    assert r.status_code in (400, 413, 422), r.text  # a boundary that says no
    assert r.status_code < 500, "5xx means the payload reached logic it should not"
    assert "49" not in r.text, "template rendered: server-side template injection"

Hubungkan ke CI agar dapat menjadi gerbang penggabungan. Sebuah *job* GitHub Actions minimal melakukan tugas tersebut:

name: api-abuse-tests
on: [push, pull_request]
jobs:
  negative-input:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pytest tests/negative_input.py -q

Di sinilah alat yang mengutamakan skema menemukan tempatnya. Di Apidog Anda merancang *endpoint* terhadap kontrak OpenAPI, sehingga setiap permintaan dan respons diperiksa terhadap kontrak tersebut saat Anda menguji. Anda dapat menyimpan skenario negatif tepat di samping skenario *happy-path*: bidang berukuran besar, tipe yang salah, dan *string* injeksi di atas, masing-masing dengan penegasan bahwa statusnya adalah 4xx. Kemudian Anda menjalankan skenario yang sama di CI melalui Apidog CLI, sehingga perubahan yang secara diam-diam melonggarkan validasi akan menyebabkan *build* gagal alih-alih di-deploy. Jika Anda ingin mencobanya, Unduh Apidog dan tambahkan satu skenario negatif ke *endpoint* yang sudah Anda miliki.

Jelaskan batasannya dengan jelas. Apidog adalah alat desain, pengujian, *mocking*, dan dokumentasi. Ini tidak menjalankan *web application firewall*, memfilter lalu lintas langsung, atau menggantikan SIEM, dan validasi kontrak selama pengujian tidak akan menangkap setiap eksploitasi. Yang dapat dilakukannya dengan baik adalah membuat kontrak eksplisit dan menjaga Anda tetap jujur tentang apa yang diterima oleh *endpoint*, sehingga kategori "kami tidak pernah memeriksa" berhenti menjadi hal yang mengejutkan Anda di produksi.

Pertanyaan yang Sering Diajukan

Apa perbedaan antara pengujian negatif dan *fuzzing*? Pengujian negatif mengirimkan serangkaian input buruk yang dipilih secara sengaja, satu per satu untuk setiap kegagalan yang Anda pedulikan. *Fuzzing* mengirimkan volume besar input acak atau termutasi untuk menemukan kasus yang tidak Anda pikirkan. Mulailah dengan tes negatif karena cepat, deterministik, dan mudah dijalankan di CI. Tambahkan *fuzzing* saat Anda menginginkan cakupan yang lebih luas dari imajinasi Anda sendiri.

Haruskah tes ini dijalankan terhadap produksi? Tidak. Jalankan terhadap *staging* atau lingkungan terisolasi. Beberapa kasus, seperti *payload* berukuran besar atau *probe* injeksi perintah, dirancang untuk menekan sistem, dan beberapa di antaranya dapat mengubah data jika ada *bug*. Lingkungan pengujian khusus memungkinkan tes menjadi agresif tanpa risiko apa pun terhadap pengguna nyata.

Bukankah *firewall* atau WAF akan menangkap ini? WAF adalah pertahanan mendalam yang berguna, tetapi bukan pengganti aplikasi yang menolak input buruk. Aturan dapat dilewati, dan WAF tidak dapat mengetahui logika bisnis Anda. Tujuan dari tes ini adalah untuk membuktikan bahwa *endpoint* itu sendiri menolak, sehingga Anda tidak bergantung pada filter yang tidak sepenuhnya Anda kendalikan.

Berapa banyak kasus negatif yang cukup per *endpoint*? Targetkan satu kasus per bidang per kelas kegagalan yang dapat terjadi: tipe salah, di luar jangkauan, terlalu panjang, bidang terlarang, dan *string* injeksi apa pun yang sesuai dengan formatnya. Biasanya ini adalah beberapa kasus per *endpoint*, bukan ratusan. Cakupan kelas lebih penting daripada jumlah mentah.

Apakah validasi skema menghentikan injeksi sepenuhnya? Tidak, dan itu seharusnya bukan satu-satunya lapisan Anda. Skema yang ketat menghilangkan sebagian besar input yang salah format dan berukuran besar serta memblokir bidang yang tidak terduga, tetapi suatu nilai bisa valid secara skema dan tetap merupakan injeksi SQL atau *template*. Tetap gunakan *parameterized queries*, deserialisasi yang aman, dan *output encoding*, serta gunakan skema untuk memperkecil permukaan yang harus dipertahankan oleh lapisan-lapisan tersebut.

Mengembangkan API dengan Apidog

Apidog adalah alat pengembangan API yang membantu Anda mengembangkan API dengan lebih mudah dan efisien.