Back to IF4053 Keamanan Perangkat Lunak

Studi Kasus CVE - Stored XSS (CVE-2024-6886)

Questions/Cues

  • Apa inti CVE-2024-6886 dan versi Gitea mana yang terdampak serta yang menambalnya?
  • Apa akar penyebab (root cause) kerentanan stored XSS ini?
  • Bagaimana cara menemukannya secara manual, dengan SAST, dan dengan DAST?
  • Apa dampaknya dan bagaimana Gitea memperbaikinya?
  • Mengapa tidak ada satu tool tunggal yang menangkap semua bug?

Reference Points

  • IF4053 Keamanan Perangkat Lunak (W12 — CVE-2024-6886 Stored XSS Walkthrough)
  • IF4053 Keamanan Perangkat Lunak (W12 — bagian “Coverage Matrix & How It Was Fixed”)

CVE at a Glance

CVE-2024-6886 adalah kerentanan Stored Cross-Site Scripting (CWE-79) pada Gitea 1.22.0, ditambal pada Gitea 1.22.1. Severity-nya CVSS 3.1 = 6.7 (Medium). Vektor serangannya adalah field Description repository. Kerentanan ini membutuhkan autentikasi—dapat dipicu oleh pengguna mana pun yang memiliki hak edit repository. Karena payload disimpan di deskripsi, setiap pengunjung halaman repo (terutama repo publik) menjadi korban potensial. Sumber resmi: github.com/advisories/GHSA-4h4p-553m-46qh. Catatan etis: walkthrough ini hanya untuk tujuan edukasi—uji hanya pada container Docker milik sendiri.

Akar Penyebab — Titik Buta Sanitizer

Gitea memakai sanitizer Bluemonday yang mengizinkan tag <a> agar pengguna dapat menaruh tautan di deskripsi repo. Namun pada 1.22.0, ketika tidak ada daftar skema kustom yang dikonfigurasi (setting.Markdown.CustomURLSchemes == nil), kebijakan memanggil policy.AllowURLSchemesMatching(allowAllRegex) yang mengizinkan SEMUA skema URL—termasuk javascript:, vbscript:, dan data:. Akibatnya penyerang dapat menyisipkan tautan yang dapat diklik dan mengeksekusi JavaScript. Inti masalahnya: default sanitizer yang terlalu permisif.

// modules/markup/sanitizer_default.go (v1.22.0)
policy := bluemonday.UGCPolicy()
if setting.Markdown.CustomURLSchemes != nil {
    policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
} else {
    policy.AllowURLSchemesMatching(allowAllRegex)
    // ↑ bug: meloloskan javascript:/vbscript:/data:
}

Penemuan Manual (UI dan Proxy)

Langkah pertama menjalankan versi rentan: docker run -d --name gitea -p 3000:3000 -p 222:22 docker.gitea.com/gitea:1.22.0, selesaikan setup wizard, buat admin, lalu buat repo uji. Penemuan manual via UI: buka repo → Settings → field Description, tempel payload <a href=javascript:alert('XSS-CVE-2024-6886')>Click me for XSS</a>, klik Update Settings, kembali ke halaman repo, lalu klik tautan yang ter-render—JavaScript dieksekusi di sesi browser. Penemuan via intercepting proxy (Burp/ZAP): tangkap POST /{user}/{repo}/settings di HTTP History, kirim ke Repeater (Burp) atau Manual Request Editor (ZAP), ganti deskripsi dengan payload ter-URL-encode %3Ca+href%3Djavascript%3Aalert(1)%3Ex%3C%2Fa%3E, kirim, muat repo, klik tautan—alert muncul, kerentanan terkonfirmasi. Serangan nyata akan mencuri cookie, membajak sesi, atau menambahkan SSH key via API.

flowchart TD
    A["Penyerang: tanam payload di Description"] --> B["Gitea simpan tanpa sanitasi skema"]
    B --> C["Korban buka halaman repo"]
    C --> D["Klik tautan javascript:"]
    D --> E["JS dieksekusi: curi cookie/sesi"]

Penemuan dengan SAST

SAST (Static Application Security Testing) memeriksa kode sumber tanpa menjalankannya. Untuk CVE ini, rule Semgrep kustom dapat menandai pola konfigurasi sanitizer yang tidak aman dengan pattern $POLICY.AllowURLSchemesMatching($REGEX) (severity ERROR, language Go), dijalankan via semgrep --config=ci-rules/ modules/markup/. SAST menangkap: pemanggilan library berbahaya (AllowURLSchemesMatching), pola sink langsung (innerHTML, eval), kredensial hardcoded, dan dependensi Go usang. SAST melewatkan: bug yang bergantung pada konteks runtime, cacat logika (autentikasi, permission), keanehan parser browser, dan rantai serangan multi-langkah. SAST unggul pada bug berbasis pola, lemah pada logika dan perilaku.

Penemuan dengan DAST

DAST (Dynamic Application Security Testing) menguji aplikasi yang sedang berjalan—mengirim HTTP request nyata dan mengamati respons. Active Scanner ZAP dapat mendeteksi stored XSS ini secara otomatis, misalnya via Docker one-shot: zap-full-scan.py -t http://localhost:3000 -r xss-scan.html, lalu cari alert ‘Cross Site Scripting (Persistent)‘. Penting: ZAP membutuhkan sesi terautentikasi untuk mencapai form deskripsi—gunakan konteks Authentication-nya. DAST menangkap: reflected dan stored XSS, SQL/command injection, security header yang hilang, cookie yang salah konfigurasi. DAST melewatkan: bug di balik tembok autentikasi, endpoint yang tidak ditemukan crawler, cacat business logic, serta race condition.

Coverage Matrix, Dampak, dan Mitigasi

Coverage Matrix menunjukkan tidak ada tool tunggal yang menangkap semuanya: pengujian manual di browser (✓, effort rendah), Burp Repeater (✓, rendah), Semgrep default (⚠ mungkin), Semgrep rule kustom (✓, medium), gosec (✗), ZAP baseline pasif (✗), ZAP full active scan (✓, medium), dan Trivy image scan (✓ via versi, rendah). Dampak: pencurian cookie/sesi, pembajakan akun, hingga penambahan SSH key—diperparah pada repo publik. Mitigasi (Gitea 1.22.1): beralih dari regex allowlist yang luas ke daftar skema aman eksplisitpolicy.AllowURLSchemes("http", "https", "mailto", "ftp")—sehingga javascript:/vbscript:/data: tak lagi lolos bahkan bila konfigurasi hilang. Prinsipnya: allowlist mengalahkan denylist (enumerasi yang aman, bukan yang berbahaya).

Summary

CVE-2024-6886 adalah stored XSS (CWE-79) pada Gitea 1.22.0 (ditambal di 1.22.1, CVSS 6.7 Medium) melalui field Description repository, dipicu pengguna ber-hak edit. Akar penyebabnya: sanitizer Bluemonday dengan default AllowURLSchemesMatching(allowAllRegex) yang meloloskan skema javascript:. Penemuan manual dengan menanam payload <a href=javascript:...> lewat UI atau Burp/ZAP Repeater; SAST lewat rule Semgrep kustom yang menandai AllowURLSchemesMatching; DAST lewat ZAP Active Scanner (perlu sesi terautentikasi). Tidak ada satu tool yang menangkap semua—Trivy bahkan mendeteksi via versi. Perbaikannya mengganti regex luas dengan allowlist skema aman eksplisit, menegaskan prinsip allowlist beats denylist.