Back to IF4053 Keamanan Perangkat Lunak

Studi Kasus CVE - Broken Access Control (CVE-2025-68945)

Questions/Cues

  • Apa inti CVE-2025-68945 dan versi Gitea mana yang terdampak serta yang menambalnya?
  • Apa akar penyebab kerentanan broken access control ini?
  • Bagaimana cara menemukannya secara manual, dengan SAST, dan dengan DAST?
  • Mengapa logic bug sulit ditangkap scanner otomatis?
  • Apa dampaknya dan bagaimana Gitea memperbaikinya?

Reference Points

  • IF4053 Keamanan Perangkat Lunak (W12 — CVE-2025-68945 Broken Access Control Walkthrough)
  • IF4053 Keamanan Perangkat Lunak (W12 — bagian “SAST Why Logic Bugs Are Hard & How It Was Fixed”)

CVE at a Glance

CVE-2025-68945 adalah kerentanan Broken Access Control (CWE-200, CWE-862) pada Gitea < 1.21.2, ditambal pada Gitea 1.21.2. Severity-nya Moderate. Vektor serangannya adalah route /{user}/-/projects (baik web maupun API). Yang membuatnya serius: tidak membutuhkan autentikasi sama sekali—eksploitasi bekerja tanpa auth, memungkinkan akses anonim ke project board milik user privat. Sumber resmi: github.com/advisories/GHSA-7xq4-mwcp-q8fx. Catatan etis: hanya untuk edukasi—uji hanya pada container Docker milik sendiri.

Akar Penyebab — Middleware yang Hilang

Route /{user}/-/projects memuat user dengan middleware UserAssignmentWeb(), tetapi middleware itu hanya mengisi ctx.ContextUser dan TIDAK menegakkan visibility. Handler profil (UsernameSubRoute) memiliki pengecekan visibility internalnya sendiri, sedangkan handler project (Projects, ViewProject) tidak punya. Inilah pola khas logic bug: ia mengelompok di sekitar penambahan fitur—kode lama berasumsi, kode baru lupa.

// routers/web/web.go (v1.21.1)
m.Group("/{username}", func() {
    m.Get("", user.UsernameSubRoute)        // ✓ cek visibility internal
    m.Group("/-", func() {
        m.Get("/projects", org_project.Projects)          // ✗ TIDAK ada cek
        m.Get("/projects/{id}", org_project.ViewProject)  // ✗ TIDAK ada cek
    })
}, ignSignIn, context_service.UserAssignmentWeb())

Penemuan Manual

Set the stage: jalankan versi rentan docker run -d --name gitea -p 3000:3000 -p 222:22 docker.gitea.com/gitea:1.21.1, buat admin, buat user korban (mis. ceo), login sebagai ceoSettings → Profile → Visibility → Private, lalu buat project bernama ‘Secret Roadmap’. Penting: profil HARUS ditandai Private, jika tidak privasi memang tidak diharapkan. Test sebagai anonim: logout total, lalu coba URL berikut—/ceo (profil) → Blocked ✓, /ceo/projects (repo-list) → Blocked ✓, namun /ceo/-/projects (project board) → LOADS ✗ dan /ceo/-/projects/1 (project spesifik) → LOADS ✗. Bug-nya: profil Private seharusnya menyembunyikan SEMUA konten user, tetapi project board bocor—terlihat oleh siapa pun yang punya URL, tanpa auth, tanpa log, tanpa audit trail. Bukti via Burp/ZAP: saat login, tangkap request ke /ceo/-/projects (berisi header Cookie), kirim ke Repeater, duplikat tab, HAPUS seluruh header Cookie pada tab kedua, kirim keduanya, bandingkan dengan Burp Comparer—bila keduanya mengembalikan 200 berisi konten project, bypass terkonfirmasi.

flowchart TD
    A["User 'ceo' set profil Private"] --> B["Buat project 'Secret Roadmap'"]
    C["Penyerang anonim (tanpa cookie)"] --> D["GET /ceo/-/projects"]
    D --> E["Middleware isi ContextUser tapi tak cek Visibility"]
    E --> F["200 OK: project privat bocor"]

Penemuan dengan SAST

Logic bug adalah titik lemah SAST—rule bawaan tidak menangkapnya. Rule Semgrep kustom (heuristik) dapat menandai handler yang membaca ctx.ContextUser tanpa memeriksa Visibility (pattern func $FN(ctx *context.Context) { ... ctx.ContextUser ... } dengan pattern-not untuk ContextUser.Visibility), tetapi outputnya hanya kandidat, bukan bukti—butuh review manusia. SAST menangkap: handler yang kehilangan pemanggilan fungsi tertentu, middleware auth yang hilang di route, dan akses langsung ke field sensitif tanpa guard. SAST melewatkan: intent (apakah resource INI seharusnya privat?), alur logika multi-file, inkonsistensi antar dua code path, dan regresi akibat refactor. Logic bug paling baik ditangkap lewat code review dan behavior testing, bukan pola statis.

Penemuan dengan DAST

DAST jauh lebih baik untuk bug access control—ASALKAN kita memberi tahu sesi mana yang ber-privilege tinggi dan mana yang anonim. Ekstensi Burp Autorize mengotomatiskan perbandingan ini di latar belakang: konfigurasikan high-priv = cookie sesi, low-priv = kosong (anonim), set scope, aktifkan Autorize, jelajahi Gitea normal, lalu tinjau tabel hasil—entri merah ‘Bypassed!’ adalah bug-nya. Padanan ZAP: Replacer add-on untuk menghapus cookie secara global lalu menjelajah. Yang berhasil: Burp Autorize, ZAP Replacer, perbandingan dua-tab manual di Repeater, dan skrip kustom yang mem-diff respons. Yang tidak menemukan: ZAP baseline/full scan tanpa konteks auth, Nikto/Nuclei (cek generik), serta crawler tanpa kesadaran privasi. Insight kunci: ‘has access control’ adalah informasi yang HARUS kita ajarkan ke scanner.

Coverage Matrix, Dampak, dan Mitigasi

Coverage Matrix: URL browsing anonim manual (✓, rendah), perbandingan Burp/ZAP Repeater manual (✓, rendah), code review definisi route (✓, medium), Semgrep default (✗), Semgrep heuristik kustom (⚠ mungkin, medium), ZAP baseline/full scan (✗), Burp Autorize (✓, medium), Trivy image scan (✓ via versi). Yang paling sederhana—mengetik URL—mengalahkan setiap scanner otomatis di sini. Dampak: kebocoran data project privat ke publik tanpa jejak audit. Mitigasi (Gitea 1.21.2): middleware eksplisit individualPermsChecker yang, bila ContextUser adalah individu dengan Visibility == VisibleTypePrivate dan pengakses bukan pemilik/admin, mengembalikan ctx.NotFound (404, bukan 403)—diterapkan ke seluruh grup route agar route baru tidak bisa lupa. Penggunaan 404 (bukan 403) menyembunyikan keberadaan user privat dan mencegah enumerasi.

Summary

CVE-2025-68945 adalah broken access control (CWE-200, CWE-862) pada Gitea < 1.21.2 (ditambal di 1.21.2, Moderate) pada route /{user}/-/projects (web + API), tanpa perlu auth, sehingga project board user Private bocor ke anonim. Akar penyebab: middleware UserAssignmentWeb() hanya mengisi ContextUser tanpa menegakkan visibility, dan handler project tak punya cek sendiri—kode baru lupa yang diasumsikan kode lama. Penemuan manual paling efektif (cukup buka URL sebagai anonim atau bandingkan dua-tab di Repeater); SAST hanya menghasilkan kandidat lewat rule heuristik; DAST kuat lewat Burp Autorize/ZAP Replacer yang membandingkan sesi auth vs anonim. Perbaikannya berupa middleware individualPermsChecker yang mengembalikan 404 untuk mencegah enumerasi—dan baik path web maupun API harus ditambal bersama.