Layanan Anda memiliki API key ke cloud storage. Seorang pengguna mengirimkan request. Layanan Anda dengan patuh menulis sebuah file. File tersebut mendarat di bucket penyerang, bukan milik Anda, dan sekarang mereka memiliki data Anda. Anda baru saja menjadi kaki tangan yang tidak menyadari.

Ini adalah serangan confused deputy. Penyerang tidak dapat menulis ke target bucket secara langsung. Mereka tidak memiliki izin. Tapi mereka dapat menipu layanan Anda, yang memiliki izin, untuk melakukannya atas nama mereka. Layanan Anda adalah “deputy.” Ia “bingung” tentang untuk siapa tindakan tersebut sebenarnya dilakukan.

Seperti Apa Serangan Confused Deputy Sebenarnya

Contoh klasik adalah compiler. Compiler berjalan dengan privilese pengguna, sehingga ia dapat membaca file source mereka dan menulis file objek ke home directory mereka. Pengguna jahat memberikan compiler sebuah source file yang berisi direktif untuk menulis output ke /etc/passwd alih-alih ke build directory. Compiler memiliki otoritas. Pengguna tidak. Compiler bingung tentang niat siapa yang sedang ia jalankan.

Layanan web modern merekreasi pola ini secara konstan. Pertimbangkan sebuah file upload service yang memungkinkan pengguna menyediakan callback URL:

# The user provides a URL. We fetch it and store it.
def upload_from_url(user_id: str, source_url: str, dest_path: str):
    response = requests.get(source_url, timeout=30)
    s3.put_object(Bucket="my-app-bucket", Key=dest_path, Body=response.content)
    audit_log.info(f"User {user_id} uploaded to {dest_path}")

Ini terlihat tidak berbahaya. Pengguna menyediakan URL. Anda mengambilnya. Anda menyimpannya di bucket Anda. Tapi bagaimana jika source_url adalah http://169.254.169.254/latest/meta-data/iam/security-credentials/? Sekarang Anda sedang mengeksfiltrasi AWS credentials Anda sendiri dan menulisnya ke path yang dinamai penyerang. IAM role Anda memiliki otoritas. Penyerang tidak. Anda adalah confused deputy.

Serangan ini digeneralisasi ke sistem apa pun di mana komponen yang memiliki privilese melakukan tindakan atas nama requester yang memiliki privilese lebih rendah, dan requester dapat mempengaruhi parameter dari tindakan tersebut.

Mengapa Pengecekan Identitas Tidak Cukup

Insting untuk memperbaiki adalah memeriksi siapa requester. “Apakah pengguna ini terautentikasi?” Ya. “Apakah pengguna ini berwenang untuk mengunggah file?” Ya. Kedua pengecekan lolos, dan serangan masih berhasil.

Masalahnya adalah pengecekan otorisasi memverifikasi apakah requester dapat melakukan sebuah tindakan. Mereka tidak memverifikasi apakah target dari tindakan tersebut sesuai untuk requester itu. Pengguna diizinkan untuk mengunggah. Mereka tidak diizinkan membuat layanan Anda mengambil metadata-nya sendiri dan menyimpannya di bawah kontrol mereka. Model izin mengaburkan “dapat menggunakan upload endpoint” dengan “dapat membuat layanan melakukan tindakan privilese arbitrer.”

Ini adalah mode kegagalan inti: deputy memegang sebuah capability (AWS credentials, file system access, database write access) dan menggunakannya berdasarkan instruksi dari orang lain. Tanpa mengikat capability ke niat spesifik dari pemegang otoritas asli, kebingungan tidak dapat dihindari.

Capability-Based Security: Mengikat Otoritas dengan Niat

Capability-based security memperbaiki ini dengan membuat otoritas tidak dapat dipalsukan dan eksplisit. Alih-alih bertanya “siapa kamu?” dan kemudian mencari apa yang boleh kamu lakukan, sebuah capability adalah token yang secara langsung menyampaikan hak untuk melakukan tindakan spesifik pada resource spesifik.

Dalam sistem capability, upload service tidak akan memegang blanket S3 write key. Ia akan memegang capability untuk menulis ke path spesifik, yang di-scope ke pengguna spesifik. Request pengguna akan menyertakan capability token yang menamai destinasi yang tepat. Layanan akan memverifikasi tanda tangan kriptografis token dan scope-nya, bukan identitas pengguna terhadap global permission table.

Berikut contoh sederhana menggunakan scoped tokens:

import hashlib
import hmac
import secrets

SECRET = secrets.token_bytes(32)

def mint_capability(principal: str, action: str, resource: str) -> str:
    """Create an unforgeable capability token scoped to a specific action and resource."""
    payload = f"{principal}:{action}:{resource}"
    sig = hmac.new(SECRET, payload.encode(), hashlib.sha256).hexdigest()[:16]
    return f"{payload}:{sig}"

def verify_capability(token: str, expected_action: str, expected_resource: str) -> bool:
    """Verify a capability token matches the expected action and resource."""
    try:
        principal, action, resource, sig = token.rsplit(":", 3)
    except ValueError:
        return False
    payload = f"{principal}:{action}:{resource}"
    expected_sig = hmac.new(SECRET, payload.encode(), hashlib.sha256).hexdigest()[:16]
    return hmac.compare_digest(sig, expected_sig) and action == expected_action and resource == expected_resource

# The user receives a capability that only allows writing to their own prefix.
user_id = "alice"
scoped_token = mint_capability(user_id, "write", f"uploads/{user_id}/report.pdf")

# Later, the service verifies the capability before acting.
if not verify_capability(scoped_token, "write", "uploads/alice/report.pdf"):
    raise PermissionError("Capability does not match requested action or resource")

s3.put_object(Bucket="my-app-bucket", Key="uploads/alice/report.pdf", Body=data)

Insight kunci: capability token scoped_token tidak dapat direplay untuk menulis ke uploads/bob/salary.xlsx. Ia terikat ke resource spesifik pada saat pembuatan. Layanan tidak memutuskan apa yang diizinkan berdasarkan siapa yang bertanya. Ia memutuskan berdasarkan apa yang secara eksplisit diotorisasi oleh token.

Kapan Sistem Capability Tidak Praktis

Kebanyakan dari kita tidak sedang membangun operating system dengan arsitektur capability murni. Kita memanggil AWS APIs, berkomunikasi dengan PostgreSQL, dan menulis HTTP handlers. Capability-based security penuh adalah sebuah design philosophy, bukan drop-in library.

Jalan tengah yang praktis adalah menerapkan capability thinking ke service boundaries Anda. Alih-alih memberikan layanan Anda satu IAM role dengan broad S3 permissions, gunakan scoped presigned URLs. Alih-alih membiarkan pengguna menamai destinasi arbitrer, validasi dan sanitize target resource terhadap apa yang diizinkan pengguna itu sentuh. Tujuannya bukan capability OS formal. Tujuannya adalah menghilangkan implicit authority.

Berikut adalah upload endpoint yang sama dengan scoped validation alih-alih blanket trust:

import re

# A regex that constrains what a user can name as a destination.
ALLOWED_DEST_PATTERN = re.compile(r"^uploads/(?P<user_id>[a-z0-9_-]+)/[a-zA-Z0-9_.-]+$")

def upload_from_url(user_id: str, source_url: str, dest_path: str):
    match = ALLOWED_DEST_PATTERN.match(dest_path)
    if not match or match.group("user_id") != user_id:
        raise ValueError("Invalid destination path")

    # The source URL is also restricted. No internal IPs, no metadata endpoints.
    parsed = urlparse(source_url)
    if parsed.hostname in ("169.254.169.254", "localhost", "127.0.0.1"):
        raise ValueError("Forbidden source URL")

    response = requests.get(source_url, timeout=30)
    s3.put_object(Bucket="my-app-bucket", Key=dest_path, Body=response.content)

Ini bukan capability system dalam pengertian formal. Tapi ini menerapkan prinsip yang sama: otoritas layanan bukan blank check. Ia di-scope ke kumpulan tindakan spesifik yang diizinkan requester panggil.

Mengapa Pola Ini Terus Muncul Kembali

Kerentanan confused deputy muncul di OAuth flows, serverless functions, webhook handlers, dan supply-chain pipelines. Kapan pun komponen dengan privilese yang lebih tinggi memproses user input dan bertindak berdasarkannya, risiko hadir.

Pola SSRF-to-metadata-exfiltration begitu umum sehingga cloud provider sekarang memblokir IP IMDS secara default di VPCs. Itu adalah mitigasi untuk satu gejala, bukan penyakitnya. Penyakitnya adalah komponen privilese yang tidak dapat membedakan niatnya sendiri dari niat requester.

Mencegah Serangan Confused Deputy dalam Praktik

Anda tidak perlu menulis ulang arsitektur Anda. Tiga kebiasaan mencakup kebanyakan kasus.

Scope credentials Anda. Jika layanan Anda berbicara ke S3, berikan IAM policy yang hanya dapat menulis ke prefix spesifik. Jika berbicara ke database, gunakan row-level security atau separate credentials per tenant. Semakin kecil blast radius dari confused deputy, semakin sedikit kerusakan yang dapat dilakukannya.

Validasi target, bukan hanya aktor. Pengecekan otorisasi harus menjawab dua pertanyaan: apakah requester diizinkan melakukan tindakan ini, dan apakah target dari tindakan tersebut berada dalam scope requester? Seorang pengguna dapat mengunggah file. Dapatkah mereka mengunggah file ke direktori pengguna lain? Dapatkah mereka membuat layanan Anda memanggil URL arbitrer? Pertanyaan kedua adalah tempat confused deputy hidup.

Hindari ambient authority. Ambient authority adalah ketika sebuah proses memiliki izin hanya karena siapa dirinya, bukan karena apa yang sedang dilakukannya. Berjalan sebagai root. Menggunakan master API key. Memegang database superuser connection. Ini nyaman dan berbahaya. Pisahkan privilese Anda. Gunakan temporary credentials dengan expiration eksplisit. Semakin granular otoritas Anda, semakin sulit untuk membingungkannya.

FAQ

Apakah CSRF adalah serangan confused deputy?

Ya, dalam pengertian tertentu. Browser pengguna adalah deputy. Ia memegang session cookie (capability) dan melakukan tindakan yang mengubah state karena situs jahat menyuruhnya. Browser bingung apakah request tersebut mewakili niat pengguna. CSRF tokens bekerja dengan mengikat capability (cookie) ke action context spesifik, yang tepat adalah capability-based fix.

Apakah ini berlaku untuk microservices?

Tentu saja. Service-to-service calls sering menggunakan shared service account dengan broad permissions. Jika Service A memanggil Service B dengan parameter yang disediakan pengguna, dan Service B menggunakan privileged account-nya untuk bertindak berdasarkan parameter tersebut, Service B adalah confused deputy. Gunakan scoped service tokens atau request-specific authorization.

Bagaimana dengan SQL injection?

SQL injection adalah serangan confused deputy pada database Anda. Database adalah deputy. Ia memiliki otoritas untuk membaca dan menulis tabel. Penyerang memberinya instruksi yang disamarkan sebagai input. Parameterized queries memperbaiki ini dengan memisahkan otoritas database (query structure) dari input pengguna (parameters).

Audit Deputy Anda

Telusuri layanan Anda dan identifikasi yang memegang privilese yang tidak dimiliki pengguna. Untuk masing-masing, tanyakan: dapatkah pengguna menipu layanan ini untuk menggunakan otoritasnya dengan cara yang menguntungkan penyerang? Jika jawabannya ya, Anda memiliki confused deputy. Perbaikannya jarang eksotis. Ini biasanya masalah scoping apa yang layanan bersedia lakukan, dan menolak request yang berada di luar scope tersebut.

Layanan Anda tidak boleh menjadi kaki tangan. Pastikan ia tahu perintah siapa yang sedang diikutinya.