Mass Assignment

Mass assignment: bidang permintaan tambahan yang berubah menjadi hak istimewa.

Daftar isi

Mass assignment adalah keadaan ketika handler menyalin seluruh kunci dari payload permintaan ke objek penyimpanannya tanpa penyaringan, sehingga kunci tambahan dari pengirim ikut tersimpan. Kerentanannya terletak pada fitur yang menerima data terstruktur, misalnya pembaruan profil, karena server memetakan bidang yang dikirim klien langsung ke objeknya. Topik ini mereproduksi serangan tersebut pada pembaruan profil peserta kegiatan, memperlihatkan peran berubah hanya dengan menambahkan kunci pada payload, lalu menambalnya dengan allowlist, yaitu daftar bidang yang boleh diterima server.

Cara bidang tambahan menyusup ke objek#

Framework aplikasi web menyediakan jalan pintas untuk mengikat (bind) data permintaan ke objeknya, misalnya satu panggilan yang menerima kamus JSON dan mengisikan setiap kuncinya ke objek pengguna. Jalan pintas ini menjadi kerentanan apabila objeknya menyimpan bidang yang tidak boleh ditentukan pengirim, misalnya peran atau tanda admin. Penyerang tidak perlu menyentuh formulir karena formulir hanyalah salah satu klien, sedangkan permintaan dapat disusun manual melalui devtools atau curl. Bidang yang disembunyikan pada tampilan tetap terekspos pada API, dan kebenarannya tetap diputuskan oleh server.

Pada OWASP Top 10:2025, mass assignment tidak menjadi kategori tersendiri dan ditangani dalam A01 Broken Access Control serta A06 Insecure Design. Alasannya sederhana, yaitu akibat akhirnya sama dengan kontrol akses yang rusak, karena pengirim dapat mengubah data yang seharusnya berada di luar jangkauannya. Sifatnya yang bersembunyi di balik kemudahan pengikatan data membuatnya sering lolos dari peninjauan kode yang hanya mencari sintaks berbahaya.

Nama bidang yang disuntikkan tidak perlu ditebak secara buta. Objek yang sama biasanya muncul pada respons API, halaman profil, maupun dokumentasi, sehingga kandidat kuncinya jelas terbaca. Penyerang tinggal menyalin nama bidang istimewa yang terlihat, menambahkannya ke payload, lalu mengamati apakah efeknya tersimpan. Pola pengujian semacam ini murah dilakukan, tetapi akibatnya besar, karena satu kunci yang lolos dapat mengubah otorisasi pemilik permintaan itu sendiri.

Reproduksi pada pembaruan profil peserta#

Objek profil pada contoh ini menyimpan nama, biografi, peran, dan tanda is_admin. Handler versi cacat menyalin seluruh payload JSON dari permintaan dengan profil.update(payload), sedangkan versi perbaikan hanya menerima bidang pada allowlist. Payload penyerang pada skrip menyertakan "peran": "panitia" dan "is_admin": true di samping bidang profil yang wajar. Dua objek profil yang sama diperbarui berurutan, satu dengan payload penyerang dan satu lagi dengan payload normal, sehingga perbedaan perilakunya terbaca berdampingan. Simpan skripnya, lalu jalankan dengan perintah python3 mass-assignment.py.

python mass-assignment.py
"""Mass assignment pada pembaruan profil peserta kegiatan.

Versi cacat menyalin seluruh kunci payload JSON ke objek pengguna,
sehingga bidang peran dan is_admin yang disuntikkan ikut tersimpan.
Versi perbaikan hanya menerima bidang pada allowlist dan menolak
sisanya. Status objek dicetak dengan json.dumps agar mudah dibaca.
"""

import json

BIDANG_DIIZINKAN = ("nama", "biografi")


def perbarui_cacat(profil, payload):
    profil.update(payload)
    return profil


def perbarui_aman(profil, payload):
    for kunci in payload:
        if kunci not in BIDANG_DIIZINKAN:
            raise ValueError(f"bidang '{kunci}' tidak berada pada allowlist")
    for kunci in BIDANG_DIIZINKAN:
        if kunci in payload:
            profil[kunci] = payload[kunci]
    return profil


payload_penyerang = json.loads(
    '{"nama": "Dewi Lestari", "biografi": "Anggota klub robotik",'
    ' "peran": "panitia", "is_admin": true}'
)
payload_normal = json.loads(
    '{"nama": "Dewi Lestari", "biografi": "Anggota klub robotik"}'
)

print("== Versi cacat: seluruh kunci payload disalin ==")
profil_dewi = {
    "nama": "Dewi",
    "biografi": "Peserta kegiatan sains",
    "peran": "peserta",
    "is_admin": False,
}
print(f"sebelum: {json.dumps(profil_dewi, sort_keys=True)}")
perbarui_cacat(profil_dewi, payload_penyerang)
print(f"sesudah: {json.dumps(profil_dewi, sort_keys=True)}")

print()
print("== Versi perbaikan: allowlist nama dan biografi ==")
profil_budi = {
    "nama": "Budi",
    "biografi": "Peserta kegiatan sains",
    "peran": "peserta",
    "is_admin": False,
}
print(f"sebelum: {json.dumps(profil_budi, sort_keys=True)}")
try:
    perbarui_aman(profil_budi, payload_penyerang)
except ValueError as pesan:
    print(f"payload penyerang: ditolak ({pesan})")
perbarui_aman(profil_budi, payload_normal)
print(f"payload normal: {json.dumps(profil_budi, sort_keys=True)}")
text Keluaran
== Versi cacat: seluruh kunci payload disalin ==
sebelum: {"biografi": "Peserta kegiatan sains", "is_admin": false, "nama": "Dewi", "peran": "peserta"}
sesudah: {"biografi": "Anggota klub robotik", "is_admin": true, "nama": "Dewi Lestari", "peran": "panitia"}

== Versi perbaikan: allowlist nama dan biografi ==
sebelum: {"biografi": "Peserta kegiatan sains", "is_admin": false, "nama": "Budi", "peran": "peserta"}
payload penyerang: ditolak (bidang 'peran' tidak berada pada allowlist)
payload normal: {"biografi": "Anggota klub robotik", "is_admin": false, "nama": "Dewi Lestari", "peran": "peserta"}

Keluaran versi cacat memperlihatkan kenaikan hak istimewa yang lengkap dalam satu permintaan, yaitu peran menjadi panitia dan is_admin menjadi true, tanpa kesalahan apa pun pada handler. Permintaan itu memang tampak normal bagi server karena strukturnya sama dengan permintaan sah, hanya kuncinya lebih banyak. Pada versi perbaikan, payload penyerang ditolak pada kunci pertama yang tidak terdaftar, sedangkan payload normal tetap diterima dan hanya memperbarui nama serta biografi. Status objek dicetak dengan sort_keys=True supaya pergeseran bidangnya mudah dilihat dengan membandingkan keadaan sebelum dan sesudah.

Perbaikan: allowlist bidang#

Allowlist menetapkan daftar bidang yang boleh diterima, dan setiap bidang di luar daftarnya ditolak. Kebalikannya, denylist, mencantumkan bidang yang dilarang, sehingga bidang baru yang terlupa menjadi celah berikutnya. Penolakan sebaiknya eksplisit dan dicatat pada log, karena permintaan yang menyertakan kunci istimewa hampir pasti bukan kecelakaan. Banyak framework menyediakan mekanismenya, misalnya strong parameters pada Rails dan daftar fields pada ModelForm Django.

Jalankan demo berikut. Aktifkan bidang yang disuntikkan, kirim permintaan pada mode tanpa allowlist, lalu amati peran dan tanda admin pada objek yang tersimpan. Ulangi pada mode dengan allowlist, dan permintaan yang sama akan ditolak tanpa mengubah objeknya.

Allowlist pada handler adalah lapisan pertama, sedangkan lapisan kedua berupa pemisahan objek, yaitu data permintaan diterima ke dalam objek masukan yang hanya memuat bidang profil, bukan langsung ke objek penyimpanan yang memuat peran. Dengan pemisahan ini, allowlist yang terlupakan pada satu handler tidak langsung membuka bidang istimewa, karena objek masukannya memang tidak memiliki kuncinya. Kedua lapisan tersebut murah diterapkan sejak awal dan jauh lebih sulit dilupakan daripada mengingat daftar bidang yang berbahaya.

Kesalahan umum#

Kesalahan berikut muncul ketika penyaringan bidang dianggap pekerjaan sekunder.

  • Membuat daftar larangan atas bidang berbahaya yang diketahui saat itu, sehingga bidang baru yang muncul tidak tercakup.
  • Mengabaikan kunci asing secara diam-diam tanpa penolakan, sehingga percobaan penyuntikan tidak tercatat pada log.
  • Menggabungkan endpoint pembaruan profil dengan bidang status atau peran pada objek yang sama, sehingga satu handler mencakup keduanya.
  • Menganggap struktur payload pasti sesuai karena pengujian selalu dilakukan melalui formulir milik aplikasi sendiri.

Ringkasan#

  • Mass assignment terjadi ketika handler menyalin seluruh kunci payload ke objek penyimpanan tanpa penyaringan.
  • Kunci istimewa seperti peran atau is_admin berpindah ke data hanya dengan ditambahkan pada permintaan.
  • Tambalannya berupa allowlist bidang pada setiap handler, penolakan yang eksplisit, dan pencatatan permintaan yang ditolak.

Referensi#