imctf 2021
Web
can you see cookie?
タイトル通り、cookieを確認します。。
flag:cnVuX3J1bl9ydW5fYXNfZmFzdF95b3VfY2FuIQ==
これをbase64でデコードするとflagが取れました。
for stranger of storage🤠
local storageを確認します。
woody says:54,47,56,30,49,47,31,6c,49,48,52,6c,62,47,77,67,65,57,39,31,49,47,46,69,62,33,56,30,49,48,52,6f,5a,53,42,7a,64,48,4a,68,62,6d,64,6c,49,48,52,6f,61,57,35,6e,63,79,42,68,63,6d,55,67,61,47,46,77,63,47,56,75,61,57,34,6e,49,48,52,76,49,47,31,6c,43,6c,4e,30,63,6d,46,75,5a,32,55,67,64,47,68,70,62,6d,64,7a,43,6d,6c,74,59,33,52,6d,65,33,4e,30,63,6d,46,75,5a,32,56,66,64,47,68,70,62,6d,64,7a,58,32,46,79,5a,56,39,6f,59,58,42,77,5a,57,35,70,62,69,64,66,64,47,39,66,62,57,56,39,43,6b,46,70,62,69,64,30,49,47,35,76,49,47,52,76,64,57,4a,30,49,47,46,69,62,33,56,30,49,47,6c,30
怪しい文字列を見つけました。
cyber chefでデコードしてみます。
解けなかった問題
DoScript🐣
配布コード
import os
import subprocess
import urllib.parse
from flask import Flask, request
app = Flask(__name__)
def js_sanitize(text):
#I've heard that this is enough to sanitize special characters in JavaScript.
return "".join([c for c in text if ord(c) > 0x1f]).replace("\\", "\\\\").replace("\"", "\\\"").replace("'", "\\'").replace("/", "\\/")
@app.route("/")
def doscript():
name = request.args.get("name")
if not name:
name = 'Nagoya-Hacker'
html = f"""<!DOCTYPE html>
<html lang="ja">
<head>
<title>DoScript</title>
<ScRiPt>
//Hack! Hack! Hack!
alert("Hacked by {js_sanitize(name)}!!");
alert("Is there any way to turn off the alerts?");
alert("Hehehehehe");
</ScRiPt>
</head>
<body>
<h1>DoScript</h1>
This site is a perfectly safe site that will not be hacked.<br>
It cannot be tampered with by a hacker!!!<br>
👻<br>
<a href="/tamperchecker">Tamper Checker 🧐</a><br>
</body>
</html>
"""
return html
@app.route("/tamperchecker", methods=["GET", "POST"])
def tamperchecker():
mysite = "http://118.27.104.46:31416/"
if request.method == "GET":
return f"""<!DOCTYPE html>
<html lang="ja">
<head>
<title>Tamper Checker 🧐</title>
</head>
<body>
<h1>Tamper Checker 🧐</h1>
<form action="/tamperchecker" method="post">
<label for="url">URL: </label>
<input type="text" style="width:350px;" id="url" name="url" placeholder="{mysite}?name=Nagoya-Hacker">
<button type="submit">Submit</button>
</form>
</body>
</html>
"""
else:
url = request.form["url"]
if f"{mysite}?name=" != url[0:len(mysite + "?name=")]:
return "Not my site!"
if len(url) > 100:
return "URL is too long!"
url = f"{mysite}?name=" + urllib.parse.quote(url.replace(f"{mysite}?name=", ""))
try:
cmd = f'chromium-browser --no-sandbox --headless --disable-gpu "{url}"'
subprocess.run(cmd, shell=True, timeout=3)
except:
return 'This site has been hacked 👾'#Alerts are detected.
#Flag呼び出し
return f'This site has not been hacked 👻\nFlag:{os.getenv("FLAG")}'#No alerts are detected.
if __name__ == "__main__":
app.run(debug=True, host='0.0.0.0', port=23142)
問題ページ
問題ページにアクセスすると、ポップアップが表示されます。
配布コードをみるに、ポップアップを表示させなければFlagが獲得できそうです。
js_sanitizeをbypassし、window.alert=function(){};などで、alert関数を上書きできればFlagが獲得できると考えました。
nameに与えた値は最初のalertに出力されます。
sanitizeされる文字列を再度確認します。
"".join([c for c in text if ord(c) > 0x1f]).replace("\\", "\\\\").replace("\"", "\\\"").replace("'", "\\'").replace("/", "\\/")
,',",/と制御文字はサニタイズされるようです。
しかし、、いくら試してもbypass出来ません。
ここでタイムアップでした。
以下writeupを見ての復習です。
XSSではなく、DoSを起こすのが正解だそうです。。
完全にXSSだと思い込んでいました。。
柔軟な思考ができなかったです。。悔しい。
以下のpayloadを送るとDoSになるとのことです。
<!--<script>
https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements
上記のリンクによると、これはスクリプトブロックには、<!--
のあとにスクリプトの開始タグを含められないことになっているそうです。
仮に含んだ場合、2つ目のスクリプトの終了タグが現れるまでスクリプトブロックが継続します。
記載通り、scriptタグが無効になりalert関数が実行されません。
このURLをコピーし、tampercheckerにPOSTすればFlagです。
<ScRiPt>
//Hack! Hack! Hack!
alert("Hacked by {js_sanitize(name)}!!");
alert("Is there any way to turn off the alerts?");
alert("Hehehehehe");
</ScRiPt>
Num-restaurant🍷
配布コード
import os
import random
import subprocess
import urllib.parse
from flask import Flask, request, Response
app = Flask(__name__)
def sanitize_price(price):
if not price.isnumeric():
return "10000000"
return price
def sanitize_meal(meal):
if not meal.isascii():
return "Kasumi"
#Is it possible to block XSS?
return "".join([c for c in meal if ord(c) > 0x1f]).replace("\\", "\\\\").replace("\"", "\\\"").replace("'", "\\'").replace("&", "&").replace("<", "<").replace(">", ">").replace("http", "num")
@app.route("/")
def num_restaurant():
niku = random.randint(1, 1000000)
sushi = random.randint(1, 1000000)
yasai = random.randint(1, 1000000)
price = request.args.get("price")
meal = request.args.get("meal")
if (not price) or (not meal):
script = ''
else:
script = f'''<script>
var text = "¥{sanitize_price(price)}{sanitize_meal(meal)}😋Yummy!!";
alert(text);
</script>
'''
html = f"""<!DOCTYPE html>
<html lang="ja">
<head>
<title>Num-restaurant</title>
{script}
</head>
<body>
<h1>Welcome to Num-restaurant 🔥</h1>
<a href="?price={niku}&meal=Niku"><p style="font-size:200%;">¥{niku} / Niku🍖</p></a>
<a href="?price={sushi}&meal=Sushi"><p style="font-size:200%;">¥{sushi} / Sushi🍣</p></a>
<a href="?price={yasai}&meal=Yasai"><p style="font-size:200%;">¥{yasai} / Yasai🥗</p></a>
</body>
</html>
"""
if request.args.get("encoding") == "shift_jis":
return Response(html, content_type='text/html; charset=shift_jis')
#No one uses it anymore, but I implemented it because I was born in 1997 and missed it.
else:
return Response(html, content_type='text/html; charset=utf-8')
@app.route("/admin", methods=["GET", "POST"])
def admin():
if request.method == "GET":
return """
<!DOCTYPE html>
<html lang="ja">
<head>
<title>Give the food to Admin !!</title>
</head>
<body>
<h1>Give the food to Admin !!</h1>
<form action="/admin" method="post">
Query:
<input type="text" style="width:100px;" id="price" name="price" placeholder="10000">
<input type="text" style="width:100px;" id="meal" name="meal" placeholder="Niku">
<input type="hidden" id="encoding" name="encoding" value="utf-8">
<button type="submit">Submit</button>
</form>
</body>
</html>
"""
# POST
else:
url = f'http://160.251.83.96:31415?price={urllib.parse.quote(request.form["price"])}\
&meal={urllib.parse.quote(request.form["meal"])}\
&encoding={urllib.parse.quote(request.form["encoding"])}'
try:
flag = os.getenv("FLAG")
cmd = f'chromium-browser --no-sandbox --headless --disable-gpu "{url}&flag={flag}"'
subprocess.run(cmd, shell=True, timeout=3)
except:
return "Admin🥰Yummy!!"
return "Admin🤤Yucky!!"
if __name__ == "__main__":
app.run(debug=True, host='0.0.0.0', port=23141)
前問同様、これもXSSの問題だと思い、どうにかしてサニタイズをBypassしようと考えました。
encoding指定ができるため、shift_jisを指定し、shift_jisに起因する問題を使い、bypassするのかと考えましたが、ascii以外はサニタイズされてしまうため、上手くいきません。
とすると次はisnumeric関数に問題があるのかと思い、調べてみました。
isnumericは数値であればunicodeでもローマ数字でもTrueが返されるようです。
これを悪用できないか考えていたところでタイムアップでした。
以下は復習です。
var text = "¥{sanitize_price(price)}{sanitize_meal(meal)}😋Yummy!!";
入力した値の出力箇所が上記のようになっていることから、priceに5c問題を起こすような文字を入力すればサニタイズを突破し、xssを起こせるとのことです。
シフトJIS(Shift_JIS)は漢字などを2バイトで表現するが、一部の文字で、2バイト目に\すなわち0x5Cが使用されている。
上記の二つにリンクから"十"はisnumericでTrueかつ5C問題に使えることがわかります。
入力したダブルクォートがエスケープされていませんので成功したと考えます。
alertを実行することができました。
あとはこれを加工して自前のサーバにリクエストを飛ばすだけです。
感想
良問が多く、5C問題やDoSなど非常に勉強になりました。
また、本Writeupには記載していませんが、printtextには痺れました。
ああいう閃きが出来るよう、引き続き精進します。
公式Write up