Program yang menerima masukan dari luar juga menerima risiko dari luar, misalnya token yang mudah ditebak, data yang tidak diperiksa, dan perintah basis data yang dirangkai dari masukan pengguna. Topik ini membahas tiga praktik dasar yang menutup risiko tersebut, yaitu pembuatan token dengan secrets, validasi masukan di tepi program, dan pencegahan injection SQL dengan query berparameter. Seluruh contoh berjalan lokal, dan contoh basis datanya memakai sqlite3 bawaan Python.
Token acak dengan secrets#
Modul random dirancang untuk simulasi dan permainan, bukan untuk keamanan, karena urutan nilainya dapat diprediksi apabila benih (seed) yang ditanam diketahui. Modul secrets mengambil keacakan dari sumber acak sistem operasi yang dirancang untuk keperluan kriptografi, dan fungsi token_hex serta token_urlsafe menghasilkan token teks siap pakai. Skrip berikut membuat dua token lalu membandingkannya dengan compare_digest, yaitu fungsi perbandingan yang durasinya tidak membocorkan letak karakter pertama yang berbeda.
"""Demo modul secrets untuk token acak yang layak dipakai keperluan keamanan."""
import secrets
token_a = secrets.token_hex(16)
token_b = secrets.token_hex(16)
print("panjang token_hex(16) :", len(token_a), "karakter")
print("panjang token_urlsafe(32):", len(secrets.token_urlsafe(32)), "karakter")
print("dua token selalu berbeda :", token_a != token_b)
print("compare_digest cocok :", secrets.compare_digest(token_a, token_a))
print("compare_digest salah :", secrets.compare_digest(token_b, token_a))panjang token_hex(16) : 32 karakter
panjang token_urlsafe(32): 43 karakter
dua token selalu berbeda : True
compare_digest cocok : True
compare_digest salah : FalseNilai token itu sendiri tidak dicetak karena sifatnya rahasia, sedangkan panjangnya dapat diperiksa, yaitu 32 karakter heksadesimal untuk 16 byte dan 43 karakter untuk token_urlsafe 32 byte. Panjang token menentukan ruang tebakannya, yaitu 2 pangkat 128 kemungkinan untuk 16 byte, sehingga nilainya tidak dapat ditebak dengan mencoba beruntun. Dua token yang dibuat berurutan selalu berbeda, dan compare_digest mengembalikan True hanya ketika kedua nilainya persis sama. Fungsi perbandingan inilah yang dipakai saat mencocokkan token yang dikirim pengguna dengan token yang tersimpan, karena perbandingan biasa berhenti pada karakter pertama yang berbeda dan durasinya dapat diukur oleh penyerang.
Validasi masukan di tepi program#
Validasi masukan adalah pemeriksaan bentuk dan batas data sebelum data diproses lebih jauh, dan tempat terbaiknya ada di tepi program, yaitu tepat setelah data diterima. Prinsipnya berupa daftar putih (whitelist), yaitu menolak apa pun yang tidak secara eksplisit diizinkan, alih-alih mendaftar satu per satu pola yang dilarang. Skrip berikut memeriksa nama pengguna terhadap pola yang ketat dengan ekspresi reguler.
"""Demo validasi masukan dengan pola ekspresi reguler yang ketat."""
import re
POLA_NAMA = re.compile(r"^[a-z0-9_]{3,20}$")
def nama_valid(nama):
return POLA_NAMA.fullmatch(nama) is not None
for calon in ["budi_01", "Budi 01", "x", "admin; drop"]:
print(f"{calon!r:16} -> {nama_valid(calon)}")'budi_01' -> True
'Budi 01' -> False
'x' -> False
'admin; drop' -> FalsePola ^[a-z0-9_]{3,20}$ menuntut huruf kecil, angka, dan garis bawah dengan panjang 3 sampai 20 karakter, sedangkan fullmatch menolak string yang hanya cocok sebagian. Masukan “Budi 01” ditolak karena memuat huruf kapital dan spasi, masukan “x” ditolak karena terlalu pendek, dan masukan yang memuat tanda baca ditolak sebelum sempat menyentuh basis data. Aturan yang sama dapat dicoba pada demo berikut dengan mengetikkan beberapa calon nama.
Perilaku demo ini persis seperti fungsi nama_valid pada Python, yaitu pola yang sama diterapkan pada setiap masukan. Validasi di sisi klien seperti ini hanya kenyamanan antarmuka, karena permintaan tetap dapat dibuat langsung tanpa melalui halaman, sehingga pemeriksaan yang sama wajib diulang di sisi server.
Injection SQL dan query berparameter#
Injection SQL terjadi ketika masukan pengguna dirangkai menjadi teks perintah SQL, sehingga masukan tertentu dapat mengubah makna perintahnya. Skrip berikut membuat tabel siswa pada basis data sqlite3 di dalam memori, lalu membandingkan dua fungsi pencarian. Fungsi pertama merangkai string, sedangkan fungsi kedua memakai query berparameter dengan tanda tanya sebagai penampung nilai.
"""Demo injection SQL: query dirangkai string dibandingkan query berparameter."""
import sqlite3
koneksi = sqlite3.connect(":memory:")
koneksi.execute("CREATE TABLE siswa (nama TEXT, kelas TEXT)")
koneksi.executemany(
"INSERT INTO siswa VALUES (?, ?)",
[("Ali", "7A"), ("Budi", "7B"), ("Citra", "7A")],
)
koneksi.commit()
def cari_tidak_aman(nama):
query = "SELECT nama FROM siswa WHERE nama = '" + nama + "'"
return [baris[0] for baris in koneksi.execute(query)]
def cari_aman(nama):
query = "SELECT nama FROM siswa WHERE nama = ?"
return [baris[0] for baris in koneksi.execute(query, (nama,))]
masukan = "x' OR '1'='1"
print("masukan :", masukan)
print("tidak aman :", cari_tidak_aman(masukan))
print("aman :", cari_aman(masukan))
print("cari Ali :", cari_aman("Ali"))masukan : x' OR '1'='1
tidak aman : ['Ali', 'Budi', 'Citra']
aman : []
cari Ali : ['Ali']Masukan x' OR '1'='1 menutup tanda kutip pembuka lalu menambahkan kondisi yang selalu benar, dan fungsi pertama menyerahkan seluruh isi tabel kepada penyerang. Fungsi kedua mengirim masukan sebagai nilai terpisah, sehingga sqlite3 memperlakukannya murni sebagai data dan hasil pencariannya kosong. Pencarian nama Ali tetap berjalan normal pada fungsi berparameter, yang menunjukkan bahwa pencegahan ini tidak mengorbankan fungsi.
Pola yang sama berlaku di luar SQL. Perintah shell yang dirangkai dari masukan membuka injection perintah, sedangkan jalur berkas yang dirangkai dari masukan dapat keluar dari folder yang dimaksud melalui ../. Pencegahannya senada, yaitu memisahkan struktur dari data, baik melalui daftar argumen subprocess, validasi jalur, maupun query berparameter pada basis data.
Menyimpan kata sandi dengan hash#
Kata sandi tidak boleh disimpan dalam bentuk aslinya, karena kebocoran berkas berarti kebocoran seluruh sandi. Bentuk penyimpanannya berupa hash yang diperlambat, dan pustaka standar menyediakan hashlib.pbkdf2_hmac untuk itu, yaitu fungsi turunan kunci (key derivation) yang mengulang hash berkali-kali. Garam (salt) berupa byte acak dari secrets ditambahkan agar dua pengguna dengan sandi sama menghasilkan hash yang berbeda.
# menurunkan hash kata sandi dengan PBKDF2 dan garam acak
import hashlib
import secrets
garam = secrets.token_bytes(16)
kunci = hashlib.pbkdf2_hmac("sha256", b"sandi-piket-2026", garam, 200_000)
print("panjang garam:", len(garam), "byte")
print("panjang kunci:", len(kunci), "byte")panjang garam: 16 byte
panjang kunci: 32 byteKeluaran menunjukkan panjang kunci 32 byte sesuai SHA-256, sedangkan nilai garam sengaja tidak dicetak karena pada praktiknya ikut tersimpan di samping hash. Angka 200.000 pada contoh adalah jumlah perulangan hash yang memperlambat perhitungan secara sengaja, sehingga pencocokan kata sandi yang dicoba beruntun oleh penyerang menjadi jauh lebih mahal, sedangkan verifikasi yang sah hanya dilakukan sekali per upaya masuk. Jumlah perulangan semestinya bernilai besar karena itulah tujuan desainnya, dan angka pada contoh hanya dipilih agar demonstrasi tetap cepat. Proses verifikasi mengulang perhitungan yang sama dengan garam yang tersimpan, kemudian membandingkan hasilnya dengan compare_digest dari bagian pertama. Penyimpanan yang dijaga berupa pasangan garam dan hash untuk setiap pengguna, bukan sandi aslinya.
Ringkasan#
- secrets menghasilkan token dari sumber acak yang layak keamanan, dan
compare_digestmembandingkan token tanpa membocorkan informasi lewat durasi. - Validasi masukan memakai daftar putih yang ketat di tepi program, dan pemeriksaan yang sama diulang di sisi server.
- Query berparameter memisahkan perintah SQL dari data, sehingga masukan tidak dapat mengubah makna perintah.
- Kata sandi disimpan sebagai hash yang diperlambat bersama garam acak, bukan dalam bentuk aslinya.
Referensi#
- secrets — Python Software Foundation — Dokumentasi resmi modul secrets untuk token acak dan perbandingan yang aman dari sisi waktu.
- sqlite3 — Python Software Foundation — Dokumentasi resmi modul sqlite3, termasuk query berparameter dengan penampung tanda tanya.
- SQL Injection Prevention Cheat Sheet — OWASP Foundation — Panduan OWASP untuk pencegahan injection SQL.