티스토리 뷰
문제 개요
express cookie-parser 모듈로 object 파싱하고 XSS 기법으로 관리자의 세션을 탈취하는 문제
문제 풀이
우선 app.js 상단 코드는 아래와 같습니다.
const express = require("express");
const cookieParser = require('cookie-parser');
const goose = require("./goose");
const clean = require('xss');
const app = express();
app.use(cookieParser());
app.use(express.urlencoded({extended:false}));
const PORT = process.env.PORT || 9988;
const headers = (req, res, next) => {
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-Content-Type-Options', 'nosniff');
return next();
}
app.use(headers);
app.use(express.static('public'))
goose.js 에는 단순히 관리자가 puppeteer 모듈로 사용자가 보낸 url 에 세션값에 FLAG 값을 설정한 뒤에 접근하는 코드가 정의되어 있습니다.
위 코드에서 특별하게 볼 부분은 xss 모듈인데요. xss 모듈 버전은 최신버전으로 보였고, 만약 이 모듈로 막혀있다면 우회하기는 쉽지 않아보였습니다.
나머지 코드는 아래와 같습니다.
const template = (goosemsg, goosecount) => `
<html>
<head>
<style>
H1 { text-align: center }
.center {
display: block;
margin-left: auto;
margin-right: auto;
width: 50%;
}
body {
place-content:center;
background:#111;
}
* {
color:white;
}
</style>
</head>
${goosemsg === '' ? '': `<h1> ${goosemsg} </h1>`}
<img src='/images/goosevie.png' width='400' height='700' class='center'></img>
${goosecount === '' ? '': `<h1> You have honked ${goosecount} times today </h1>`}
<form action="/report" method=POST style="text-align: center;">
<label for="url">Did the goose say something bad? Give us feedback.</label>
<br>
<input type="text" id="site" name="url" style-"height:300"><br><br>
<input type="submit" value="Submit" style="color:black">
</form>
</html>
`;
app.get('/', (req, res) => {
if (req.cookies.honk){
//construct object
let finalhonk = {};
if (typeof(req.cookies.honk) === 'object'){
finalhonk = req.cookies.honk
} else {
finalhonk = {
message: clean(req.cookies.honk),
amountoftimeshonked: req.cookies.honkcount.toString()
};
}
res.send(template(finalhonk.message, finalhonk.amountoftimeshonked));
} else {
const initialhonk = 'HONK';
res.cookie('honk', initialhonk, {
httpOnly: true
});
res.cookie('honkcount', 0, {
httpOnly: true
});
res.redirect('/');
}
});
app.get('/changehonk', (req, res) => {
res.cookie('honk', req.query.newhonk, {
httpOnly: true
});
res.cookie('honkcount', 0, {
httpOnly: true
});
res.redirect('/');
});
app.post('/report', (req, res) => {
const url = req.body.url;
goose.visit(url);
res.send('honk');
});
app.listen(PORT, () => console.log((new Date())+`: Web/honksay server listening on port ${PORT}`));
보면 알겠지만 조건문에서 req.cookies.honk 가 object 타입이라면 honk 객체를 finalhonk 변수에 그대로 대입하고 있는 것을 알 수 있습니다. 만약 object가 아닌 string 타입이라면 finalhonk 변수에 message 키값과 amountoftimeshonked 키값에 각각 xss 필터링을 한 req.cookies.honk 값과 req.cookies.honkcount 값을 문자열로 변환해서 대입합니다.
이 대목에서 단순히 보면 req.cookies.honkcount 값은 xss 필터링을 하지 않았기 때문에 XSS 공격에 취약하다는 것을 알 수 있습니다. 다만 애초에 req.cookies.honk 타입이 object 라면 xss필터링을 하지 않기 때문에 express 의 cookie parser 모듈이 object 타입으로 인식하게만 해주면 문제는 풀립니다.
j:{"message":"<script>공격페이로드</script>","amountoftimeshonked":"1"}
위와 같은 형태로 cookie parser 에 보내면, cookie parser 는 아래 항목에서 j: 로 시작하는 문자열이 들어올 경우에 JSON object 로 파싱하게 됩니다.
// https://github.com/expressjs/cookie-parser/blob/1.4.6/index.js#L84
function JSONCookie (str) {
if (typeof str !== 'string' || str.substr(0, 2) !== 'j:') {
return undefined
}
try {
return JSON.parse(str.slice(2))
} catch (err) {
return undefined
}
}
이 원리를 이용해서 object 를 만들어주고, template 에 message와 amountoftimeshonked 값이 그대로 반영되는 점을 이용해서 XSS(Cross Site Scripting) 공격을 할 수 있게 됩니다.
로컬호스트 상에서 관리자의 세션인 flag를 잘 가져온 것을 확인할 수 있습니다.
본 문제는 MapleCTF2022에서 웹 분야 중 제일 쉬운 문제에 속한 문제입니다. 또한 SSTF2022에서 출제된 JWT Decoder 문제와도 유사했던 문제였던 것 같습니다.
- 끝 -
'보안 > CTF' 카테고리의 다른 글
[SekaiCTF] [Web] Sekai Game Start (0) | 2022.10.04 |
---|---|
[SekaiCTF] [Web] Bottle Poem (0) | 2022.10.04 |
[SSTF] [Web] JWT Decoder Writeup(문제풀이) (0) | 2022.08.27 |
[SSTF] [Web] Imageium Writeup(문제풀이) (0) | 2022.08.26 |
[corCTF2022] - [Forensics] whack-a-frog Writeup(문제풀이) (0) | 2022.08.14 |