[CTF Writeup / Web] LA CTF 2024 - flaglang
[CTF Writeup / Web] LA CTF 2024 - flaglang
안녕하세요. 이번 글에서는 LA CTF 2024에 출제된 Web 문제 flaglang의 writeup을 작성해보겠습니다.
Do you speak the language of the flags?
해당 문제 설명이고, URL과 문제 파일이 첨부되어 있습니다.
문제 URL에 접근 시 나타나는 화면입니다. Hello World를 선택한 나라의 언어로 번역해주는 서비스입니다.
소스코드를 확인해보겠습니다.
1 2 3 4 5 6 7 | Flagistan: iso: FL msg: "<REDACTED>" password: "<REDACTED>" deny: ["AF","AX","AL","DZ","AS","AD","AO","AI","AQ","AG","AR","AM","AW","AU","AT","AZ","BS","BH","BD","BB","BY","BE","BZ","BJ","BM","BT","BO","BA","BW","BV","BR","IO","BN","BG","BF","BI","KH","CM","CA","CV","KY","CF","TD","CL","CN","CX","CC","CO","KM","CG","CD","CK","CR","CI","HR","CU","CY","CZ","DK","DJ","DM","DO","EC","EG","SV","GQ","ER","EE","ET","FK","FO","FJ","FI","FR","GF","PF","TF","GA","GM","GE","DE","GH","GI","GR","GL","GD","GP","GU","GT","GG","GN","GW","GY","HT","HM","VA","HN","HK","HU","IS","IN","ID","IR","IQ","IE","IM","IL","IT","JM","JP","JE","JO","KZ","KE","KI","KR","KP","KW","KG","LA","LV","LB","LS","LR","LY","LI","LT","LU","MO","MK","MG","MW","MY","MV","ML","MT","MH","MQ","MR","MU","YT","MX","FM","MD","MC","MN","ME","MS","MA","MZ","MM","NA","NR","NP","NL","AN","NC","NZ","NI","NE","NG","NU","NF","MP","NO","OM","PK","PW","PS","PA","PG","PY","PE","PH","PN","PL","PT","PR","QA","RE","RO","RU","RW","BL","SH","KN","LC","MF","PM","VC","WS","SM","ST","SA","SN","RS","SC","SL","SG","SK","SI","SB","SO","ZA","GS","ES","LK","SD","SR","SJ","SZ","SE","CH","SY","TW","TJ","TZ","TH","TL","TG","TK","TO","TT","TN","TR","TM","TC","TV","UG","UA","AE","GB","US","UM","UY","UZ","VU","VE","VN","VG","VI","WF","EH","YE","ZM","ZW"] | cs |
contries.yaml 파일 일부 내용입니다. Flagistan의 msg를 읽어야 할 것 같습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | app.get('/', (req, res) => { const template = fs.readFileSync(path.join(__dirname, 'index.html')).toString(); const iso = req.signedCookies.iso || 'US'; const country = isoLookup[iso]; res .status(200) .type('html') .send(template .replaceAll('$msg$', country.msg) .replaceAll('$name$', country.name) .replaceAll('$iso$', country.iso) .replaceAll('$countries$', countryList) ); }); | cs |
메인 URL 접근 시 쿠키 iso가 없으면 US로 설정됩니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | app.get('/switch', (req, res) => { if (!req.query.to) { res.status(400).send('please give something to switch to'); return; } if (!countries.has(req.query.to)) { res.status(400).send('please give a valid country'); return; } const country = countryData[req.query.to]; if (country.password) { if (req.cookies.password === country.password) { res.cookie('iso', country.iso, { signed: true }); } else { res.status(400).send(`error: not authenticated for ${req.query.to}`); return; } } else { res.cookie('iso', country.iso, { signed: true }); } res.status(302).redirect('/'); }); | cs |
다른 나라를 선택하면 switch로 요청되는데, Flagistan을 요청하려면 password 값을 알아야 합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | app.get('/view', (req, res) => { if (!req.query.country) { res.status(400).json({ err: 'please give a country' }); return; } if (!countries.has(req.query.country)) { res.status(400).json({ err: 'please give a valid country' }); return; } const country = countryData[req.query.country]; const userISO = req.signedCookies.iso; if (country.deny.includes(userISO)) { res.status(400).json({ err: `${req.query.country} has an embargo on your country` }); return; } res.status(200).json({ msg: country.msg, iso: country.iso }); }); | cs |
view 페이지를 보면 country 파라미터로 요청한 나라의 msg를 출력해줍니다. 여기로 바로 접근하면 Flagistan의 msg를 확인할 수 있을 것 같습니다.
/view?country=Flagistan 로 요청하여 msg에 있는 플래그를 획득했습니다.
lactf{n0rw3g7an_y4m7_f4ns_7n_sh4mbl3s}


댓글
댓글 쓰기