[TOC]
2023 TUCTF & NBCTF
TUCTF-Crypto
网址:https://ctfd.tuctf.com/
Custom ECB Cipher | SOLVED
题面:
I have designed a simple algorithm which I believe it’s secure. I am confident you cannot compromise my message with the x value.
题目:
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 Crypto.Util import numberflag = b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" def convert (msg ): msg = msg ^ msg >> x msg = msg ^ msg << 13 & 275128763 msg = msg ^ msg << 20 & 2186268085 msg = msg ^ msg >> 14 return msg def transform (message ): assert len (message) % 4 == 0 new_message = b'' for i in range (int (len (message) / 4 )): block = message[i * 4 : i * 4 +4 ] block = number.bytes_to_long(block) block = convert(block) block = number.long_to_bytes(block, 4 ) new_message += block return new_message c = transform(flag[6 :-1 ]).hex () print ('c =' , c)''' c = e34a707c5c1970cc6375181577612a4ed07a2c3e3f441d6af808a8acd4310b89bd7e2bb9 '''
考点:ECB 异或移位算法的逆向
解题:
该题的加密逻辑比较清晰,主要通过transform函数进行块加密,每4个字节的数据进行一次加密,进入到convert函数,我们观察convert函数,可以发现,这里的异或和移位操作全部都是可逆的,所以写出一个逆向解密函数
其中bits代表比特位数,4个字节为一组,一个字节占8bit,所以bits设置为32
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 from Crypto.Util import numberdef inverse_right (res, shift, bits=32 ): tmp = res for i in range (bits // shift): tmp = res ^ tmp >> shift return tmp def inverse_right_mask (res, shift, mask, bits=32 ): tmp = res for i in range (bits // shift): tmp = res ^ tmp >> shift & mask return tmp def inverse_left (res, shift, bits=32 ): tmp = res for i in range (bits // shift): print ("test:" ,i) tmp = res ^ tmp << shift return tmp def inverse_left_mask (res, shift, mask, bits=32 ): tmp = res for i in range (bits // shift): tmp = res ^ tmp << shift & mask return tmp def convert (msg,x ): msg = msg ^ msg >> x msg = msg ^ msg << 13 & 275128763 msg = msg ^ msg << 20 & 2186268085 msg = msg ^ msg >> 14 return msg def inv_convert (msg,x ): msg = inverse_right(msg,14 ) msg = inverse_left_mask(msg,20 ,2186268085 ) msg = inverse_left_mask(msg,13 ,275128763 ) msg = inverse_right(msg,x) return msg def inv_transform (message,x ): old_message = b'' for i in range (int (len (message) / 4 )): block = message[i * 4 : i * 4 +4 ] block = number.bytes_to_long(block) block = inv_convert(block,x) block = number.long_to_bytes(block, 4 ) old_message += block return old_message c = 'e34a707c5c1970cc6375181577612a4ed07a2c3e3f441d6af808a8acd4310b89bd7e2bb9' a = bytes .fromhex(c) print (len (a))for x in range (1 ,20 ): print (inv_transform(a,x))
flag:TUCTF{sRta^s90s-#VgsnzPsta-TjLx7s8Txgs-*Ko}
Keyboard Cipher | SOVLED
题面:
I designed an algorithm to encrypt a message using my keyboard. No one can decrypt it without any information about my algorithm. Note: Wrap the flag in TUCTF{}.
题目:
虽然提供了一个enc后缀的文件 但是直接改成txt后缀即可成功打开
1 0x636a56355279424b615464354946686b566942794e586c4849455279523359674d47394d49486845643045675a316b315569426163304d675a316c715469426163314567616b6c7354534268563252594947745063434178643045675332395149466c6e536d343d
考点:基础编码转换 键盘密码
解题:
两步转换
得到:
r5yG Ji7y XdV r5yG DrGv 0oL xDwA gY5R ZsC gYjN ZsQ jIlM aWdX kOp 1wA KoP YgJn
类似围棋 四个键围在中间的就是flag的值
Table Encryption | SOLVED
题面:
You can’t crack my file! I am the exclusive owner of the encryption key!
题目:
题目给出了一个文件,其文件名为:table_encryption.xml.enc
考点: 文件解密
解题:
其后缀为enc 表明文件进行了加密
首先分析一下这个enc的加密模式,他是采用了某种加密方式对于文件里面的内容使用一个密钥进行加密,所以我们推测是采用了异或的加密模式,现在可以直接使用python中对于文件进行二进制流进行读取,相当于读取的内容是密文,然后因为文件后缀是xml文件 所以该文件开头部分我们是已知的,相当于明文已知,所以可以对密钥进行恢复
用010打开文件 获得前段部分的密文
数量可以先多一点 然后得到密钥的值后进行微调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 f = open ('table_encryption.xml.enc' ,'rb' ).read() pp = '<?xml version="1.0" encoding="UTF-8"?>' print (len (pp))pm = '79 52 17 07 05 00 3B 0A 00 1A 07 08 4E 6E 56 50' .split(' ' ) print (len (pm))key = "" for i in range (len (pm)): key += chr (ord (pp[i])^int (pm[i],16 )) print (key)x = open ('flag.xml' ,'w' ) for i in range (len (f)): x.write(chr (f[i]^ord (key[i%len (key)])))
拿到解密后的文件:
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 <?xml version="1.0" encoding="UTF-8" ?> <root > <table name ="users" > <column name ="id" type ="int" /> <column name ="name" type ="varchar" /> <column name ="email" type ="varchar" /> <column name ="password" type ="varchar" /> </table > <table name ="posts" > <column name ="id" type ="int" /> <column name ="user_id" type ="int" /> <column name ="title" type ="varchar" /> <column name ="content" type ="varchar" /> </table > <table name ="comments" > <column name ="id" type ="int" /> <column name ="post_id" type ="int" /> <column name ="user_id" type ="int" /> <column name ="content" type ="varchar" /> </table > <users > <user > <id > 1</id > <name > John Doe</name > <email > john@tuctf.com</email > <password > TUCTF{x0r_t4bl3s_R_fun!!!11!}</password > </user > </users > </root >
Bludgeon the Booty | SOLVED
题面:
You have found me treasure chest, but can you crack its code?
1 2 3 4 5 ___________ / \ /__|0|0|0|0|__\ | @ | |_____________|
“This here lock be cursed by the shaman of the swamp to change keys for each attempt” nc chal.tuctf.com 30002
考点:连接服务器暴力破解
解题:
3位密码锁 从000到999全部尝试一遍暴力破解
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 socket import time def main (): client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_address = ('chal.tuctf.com' , 30002 ) client_socket.connect(server_address) for i in range (10 ): client_socket.sendall(b'1\n' ) response = client_socket.recv(1024 ) print ("服务器响应: " , response.decode()) client_socket.sendall(b'1\n' ) response = client_socket.recv(1024 ) if "The chest is still locked!" not in response.decode(): print ("服务器响应: " , response.decode()) client_socket.sendall(b'+\n' ) if "The chest is still locked!" not in response.decode(): print ("服务器响应: " , response.decode()) for i in range (10 ): client_socket.sendall(b'1\n' ) response = client_socket.recv(1024 ) print ("服务器响应: " , response.decode()) client_socket.sendall(b'2\n' ) response = client_socket.recv(1024 ) print ("服务器响应: " , response.decode()) client_socket.sendall(b'+\n' ) response = client_socket.recv(1024 ) print ("服务器响应: " , response.decode()) for i in range (10 ): client_socket.sendall(b'1\n' ) response = client_socket.recv(1024 ) print ("服务器响应: " , response.decode()) client_socket.sendall(b'3\n' ) response = client_socket.recv(1024 ) print ("服务器响应: " , response.decode()) client_socket.sendall(b'+\n' ) if "The chest is still locked!" not in response.decode(): print ("服务器响应: " , response.decode()) client_socket.close() if __name__ == '__main__' : main()
该脚本从000进行遍历,可能耗时会长一点,但是只要抛出结果,脚本就会自动结束
NBCTF
网址:https://nbctf.com/challs
crypto/Rivest Shamir forgot Adleman | SOLVED
题面:
Exponents took too long. I decided to use an alternative. It won’t about the same right? https://en.wikipedia.org/wiki/RSA*(cryptosystem)*
考点:异或
题目:
chall.py :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from Crypto.Util.number import *p = getPrime(1024 ) q = getPrime(1024 ) n = p*q e = 123589168751396275896312856328164328381265978316578963271231567137825613822284638216416 m = bytes_to_long(b"nbctf{[REDACTED]}" ) ct = (m^e) % n print ("n = " , n)print ("e = " , e)print ("ct = " , ct)
out.txt:
1 2 3 n = 13431294979312769345517878088407659222785929563176888493659632690735510803353339825485161776891929296355466082338185199541755546384591261208929371208286410559187299345800125302598147388467283782373829399059130130575707550536466670447022349923395822077916588516101640779751198703879200863153859677174339078186779847910915309774616338231020176817201080756103027200290903849975398368943087868140010448011002364291104062990443568049879169811274854879262048473842331319786127894828031613201122015559660817797429013884663990368453887433480357502012963127000535358820517096295714967262963843868885674823702064175405493435873 e = 123589168751396275896312856328164328381265978316578963271231567137825613822284638216416 ct = 159269674251793083518243077048685663852794473778188330996147339166703385101217832722333
解题:
在模n域中进行异或
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from Crypto.Util.number import *e = 123589168751396275896312856328164328381265978316578963271231567137825613822284638216416 n = 13431294979312769345517878088407659222785929563176888493659632690735510803353339825485161776891929296355466082338185199541755546384591261208929371208286410559187299345800125302598147388467283782373829399059130130575707550536466670447022349923395822077916588516101640779751198703879200863153859677174339078186779847910915309774616338231020176817201080756103027200290903849975398368943087868140010448011002364291104062990443568049879169811274854879262048473842331319786127894828031613201122015559660817797429013884663990368453887433480357502012963127000535358820517096295714967262963843868885674823702064175405493435873 e = 123589168751396275896312856328164328381265978316578963271231567137825613822284638216416 ct = 159269674251793083518243077048685663852794473778188330996147339166703385101217832722333 re = (ct^e) % n print (long_to_bytes(re))
crypto/32+32=64 | SOLVED
题面:
64 is too much, but 32 isn’t. 32+32=64?
考点:base64
题目:
32_1.txt
32_2.txt
两个文件都达到了一万多行 就不贴了
解题:
这么多数据 就是因为不断使用了base64进行循环加密,所以不断进行base64解密
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import base64flag = open ("32_1.txt" ).read() while 1 : flag = base64.b64decode(flag) print (flag) flag = open ("32_2.txt" ).read() while 1 : flag = base64.b64decode(flag) print (flag)
crypto/SBG-ABW’s Insanity | SOLVED
题面:
“Skill Issue” - AnonymousBlueWhale
题目:
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 from Crypto.Util.number import getPrime, bytes_to_long, isPrime, long_to_bytesfrom Crypto.Cipher import AESfrom Crypto.Util.Padding import padimport hashlibm = bytes_to_long(b'we give you this as a gift!' ) p = getPrime(1096 ) q1 = getPrime(1096 ) q2 = getPrime(1096 ) n1 = p*q1 n2 = p*q2 e = 11 ct1 = pow (m,e,n1) ct2 = pow (m,e,n2) key = hashlib.sha256(long_to_bytes(q1)).digest() cipher = AES.new(key, AES.MODE_ECB) enc_flag = cipher.encrypt(pad(b"nbctf{[REDACTED]}" , 16 )) print ("ct1 =" , ct1)print ("ct2 =" , ct2)print ("enc_flag =" , enc_flag.hex ())
考点:gcd 整数分解 AES ECB
解题:
对题目进行分析,因为AES.ECB是直接根据密钥进行逆向解密的
所以我们的目的就是获得q1
所以观察q1的生成方式,看看在哪里出现了q1,准确定位到对应代码
1 2 3 4 5 6 7 n1 = p*q1 n2 = p*q2 e = 11 ct1 = pow (m,e,n1) ct2 = pow (m,e,n2)
其中ct1和ct2、m、e均已知,下面进行数学推导
1 2 3 4 5 6 7 8 9 ct1 = m^e + k1 * n1 = m^e + k1 * p * q1 ct2 = m^e + k2 * n2 = m^e + k2 * p * q2 => k1 * p * q1 = ct1 - m^e k2 * p * q2 = ct2 - m^e => 然后我们发现对上面式子中求出来的两个数求gcd公约数即可获得p => 然后有p了之后对1式两边同时除p k1 * q1 = (ct1 - m^e) / p =>最后一步是要获得q1 显然是有k1的干扰两个数相乘,所以借助一点工具 对这个数进行分解
法1:本地跑yafu
法2:在线网站分解https://www.alpertron.com.ar/ECM.HTM
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 from Crypto.Util.number import getPrime, bytes_to_long, isPrime, long_to_bytesfrom Crypto.Cipher import AESfrom Crypto.Util.Padding import padimport hashlibimport gmpy2m = bytes_to_long(b'we give you this as a gift!' ) e = 11 ct1 = 196150896308015382573408099004515975466540094705348761587854272630906023749083911008835478259767648401709605726136589590310666858430120235218762651641330953170392784645631801449432061363776229651965539255255795373230255852992805188205639228954217034390731460194284731845705855212209524525682241998203303747513174581513168217999505436596818091279091144718119512522929858750349220346765422769476003604849600128605208123474607256344535541843454810706150705449483256361736428064150792476736751093915251743882647862500622465233906844054109281842278362125589335774364236155483783338907105809549449368926475631824722919958889450225026843225780470131268709445293157749 ct2 = 83507921327913521142443934492559420944369023782917085618978768157512494136296269338031471193927727958060037960270530278173852027186606624474398269053920321522448607751539858355179998108075848593814112217098612017462222420001262248144471923306139580601814218471659969728514600258330312623506466116333593434744460773476488134792248490905628242447603788884700795677619881924772996081377617066055448888668800826281711315468059146518373888252421991592124071284411947405472003802863596010724784730366575751160333120162778945930063499020829492960600318519615351417595308518636794008603089224459556289944808655338805251676963828327517925911000528943113536807796285824 tmp = pow (m,e) flag1 = tmp - ct1 flag2 = tmp - ct2 p = gmpy2.gcd(flag1,flag2) print (gmpy2.gcd(p,flag2))print (p)print (flag1 // p)k1q1 = flag1 // p q1 = 603701201822386830907144477326706640694145605732107023753674808182665696931502012989218558077472289899849882120737934821898165435847435044518846871242860227586749788240998624721376490806164324545522115137075097300642534248374378375756928831273442124872283671893345317220496457140852434166575343690062190540448032738970711476061243 key = hashlib.sha256(long_to_bytes(q1)).digest() cipher = AES.new(key, AES.MODE_ECB) enc_flag = 'ac2289b707b174c541cf0952bf3b2057561b0872451444a5bbecf18c007ea20fa2b7c8a1707a74a1657e5adb5c1a417f' enc_flag = bytes .fromhex(enc_flag) if b"nbctf" in cipher.decrypt(enc_flag): print (cipher.decrypt(enc_flag))
题面:
My computer crashed generating my keys. :( Can you recover them for me?
题目:
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 *p = getPrime(512 ) q = getPrime(512 ) n = p*q e = 65537 m = bytes_to_long(b"nbctf{[REDACTED]}" ) ct = pow (m,e,n) print (f"{ct = } " )print (f"{e = } " )print (f"{n = } " )hint = (p+q) >> 200 print (f"{hint = } " )ct = 20030315247290021934293927354887580426070566017560641204155610658927917290198737029903594702064351773446005018155094643288125396810753014936800515440652855824038470725838848349666236623899089094953181436465435270989651491997801177943499187812270081592263331832916362349716591828106306150603120693022149233534 e = 65537 n = 90166344558664675592644684556355545187373291859609367810958775310181360193141550862577281658089332577942193823477148064165061303827534169112815736618901965700400798345371758370344207077280925015891945591352156370597957742921722432314582261224366498475465730899163137511778647694175484386010210005826793007961 hint = 12227137598952006551839416663729660224872609953685427677011433223002140448682395830146750981200
考点:移位 CopperSmith
解题:
前置知识点:
对移位这次一定要弄明白了:
对于十进制来说:
做除运算 等价于hint // 2**200
对于二进制来说:
移位前0b10111011100100110101010000111011001111110110110110000001101011110000000101100100000100111001110101000011001001110 11000100101100011111011100110110000110011011100100011111110111110000111110000010100001001111001100001100110000111011011011001011111111101000010111101000011010011110001011111100111000111010010001010000
移位后0b10111011100100110101010000111011001111110110110110000001101011110000000101100100000100111001110101000011001001110
相当于直接截取掉低200位二进制数据
对于十进制来说:
做乘运算 等价于hint * 2**200
对于二进制来说:
移位前
0b1011101110010011010101000011101100111111011011011000000110101111000000010110010000010011100111010100001100100111011000100101100011111011100110110000110011011100100011111110111110000111110000010100001001111001100001100110000111011011011001011111111101000010111101000011010011110001011111100111000111010010001010000
移位后
0b101110111001001101010100001110110011111101101101100000011010111100000001011001000001001110011101010000110010011101100010010110001111101110011011000011001101110010001111111011111000011111000001010000100111100110000110011000011101101101100101111111110100001011110100001101001111000101111110011100011101001000101000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 在低200位填充0 相当于把原来位置的权值抬高了
目前题目中给出的是
p+q >> 200
相当于p+q的低200位丢失了 需要恢复
在低位确实的运算中有这样一个特点:
n = p * q
n = p * (p + q - p)
得到关于p的一个方程
p*((p+q)-p) - n = 0
因为p+q是缺失低200位的
所以该方程解出的结果也是p也是部分数据 虽然结果也是512bit位的 但是存在部分数据丢失,具有相同的MBS(最高有效位)
1 2 3 4 5 p_ = var('p_' ) approx_p_plus_q = hint << 200 approx_p = int ((p_*(approx_p_plus_q - p_) - n).roots()[0 ][0 ]) print (approx_p)
那么恢复丢失的数据 => CopperSmith
假设缺失的数据为x bit设置为200位
1 2 3 4 5 PR.<x> = PolynomialRing(Zmod(n)) f = x + p3 x0 = f.small_roots(X=2 ^200 , beta=0.4 )[0 ] print (x0)p = p3+x0
到此完美结束
调试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 hint = 12227137598952006551839416663729660224872609953685427677011433223002140448682395830146750981200 n = 90166344558664675592644684556355545187373291859609367810958775310181360193141550862577281658089332577942193823477148064165061303827534169112815736618901965700400798345371758370344207077280925015891945591352156370597957742921722432314582261224366498475465730899163137511778647694175484386010210005826793007961 p_ = var('p_' ) print (bin (hint))print (len ('''1011101110010011010101000011101100111111011011011000000110101111000000010110010000010011100111010100001100100111011000100101100011111011100110110000110011011100100011111110111110000111110000010100001001111001100001100110000111011011011001011111111101000010111101000011010011110001011111100111000111010010001010000''' ))approx_p_plus_q = hint << 200 approx_p = int ((p_*(approx_p_plus_q - p_) - n).roots()[0 ][0 ]) print (approx_p)p3=7304778595800693962623587470016492934656897172638927993210828812588740492194432727308053205092654458616385476570357533214681567145360876311971890757777044 print (bin (p3))print (len ('''10001011011110010000001110010110100000110110010101111000001110011010100001001001011001110100100111010000101100111000111000100111110011100011111110110111011000010110101111110101110111011100000001011010010111111010100010111001100101011110000110000100111100101100110010111101000001100010111001100010011001010101101011010010110100000100001010100100110000111011001101100111100111110110101011010111000010111111100000011010001000111001111010101101011100001010000001001010111100101001100001000100101101100011011010010100''' ))PR.<x> = PolynomialRing(Zmod(n)) f = x + p3 x0 = f.small_roots(X=2 ^200 , beta=0.4 )[0 ] print (x0)p = p3+x0 print ("p: " , hex (int (p)))print (p)q = n // int (p) print (q)ct = 20030315247290021934293927354887580426070566017560641204155610658927917290198737029903594702064351773446005018155094643288125396810753014936800515440652855824038470725838848349666236623899089094953181436465435270989651491997801177943499187812270081592263331832916362349716591828106306150603120693022149233534 from Crypto.Util.number import *import gmpy2phi = (p-1 )*(q-1 ) e = 65537 d = gmpy2.invert(e,int (phi)) print (long_to_bytes(int (pow (ct,d,n))))print ("############test#####################" )print (bin (approx_p))print (bin (p))
flag:b'nbctf{cr34t1v3_fl4gs_4r3_s0_h4rd_t0_m4k3...}'
web/Inspector Gadget
题面:
While snooping around this website, inspector gadet lost parts of his flag. Can you help him find it?
题目:
inspector-gadget.chal.nbctf.com
考点:信息收集
F12
直接在控制台跑一下getflag()
函数
图片遮盖
下面有很多点击功能点
挨个试一试
进去之后
title中发现flag
信息泄露 robots.txt
flag:nbctf{G00d_J06_D3tect1v3_G4dg3t352}
web/walter’s crystal shop
题面:
My buddy Walter is selling some crystals, check out his shop!
题目:
walters-crystal-shop.chal.nbctf.com
给出源码 直接打开app.js
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 const express = require ("express" );const sqlite3 = require ("sqlite3" );const fs = require ("fs" );const app = express ();const db = new sqlite3.Database (":memory:" );const flag = fs.readFileSync ("./flag.txt" , { encoding : "utf8" }).trim ();const crystals = require ("./crystals" );db.serialize (() => { db.run ("CREATE TABLE crystals (name TEXT, price REAL, quantity INTEGER)" ); const stmt = db.prepare ("INSERT INTO crystals (name, price, quantity) VALUES (?, ?, ?)" ); for (const crystal of crystals) { stmt.run (crystal["name" ], crystal["price" ], crystal["quantity" ]); } stmt.finalize (); db.run ("CREATE TABLE IF NOT EXISTS flag (flag TEXT)" ); db.run (`INSERT INTO flag (flag) VALUES ('${flag} ')` ); }); app.get ("/crystals" , (req, res ) => { const { name } = req.query ; if (!name) { return res.status (400 ).send ({ err : "Missing required fields" }); } db.all (`SELECT * FROM crystals WHERE name LIKE '%${name} %'` , (err, rows ) => { if (err) { console .error (err.message ); return res.status (500 ).send ('Internal server error' ); } return res.send (rows); }); }); app.get ("/" , (req, res ) => { res.sendfile (__dirname + "/index.html" ); }); app.listen (3000 , () => { console .log ("Server listening on port 3000" ); });
考点:代码审计 SQL注入
解题:
通过代码审计 得到路由和参数
路由:crystals
参数:name
明确参数数量为3
明确flag在数据库中的位置
明确闭合方式 单引号闭合
flag:nbctf{h0p3fuLLy_7h3_D3A_d035n7_kn0w_ab0ut_th3_0th3r_cRyst4l5}
web/secret tunnel
题面:
Can you find the flag on the other end of my secret tunnel?
题目:
secret-tunnel.chal.nbctf.com
main.py
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 flask import Flask, render_template, request, Responseimport requestsapp = Flask(__name__, static_url_path='' , static_folder="static" ) @app.route("/fetchdata" , methods=["POST" ] ) def fetchdata (): url = request.form["url" ] if "127" in url: return Response("No loopback for you!" , mimetype="text/plain" ) if url.count('.' ) > 2 : return Response("Only 2 dots allowed!" , mimetype="text/plain" ) if "x" in url: return Response("I don't like twitter >:(" , mimetype="text/plain" ) if "flag" in url: return Response("It's not gonna be that easy :)" , mimetype="text/plain" ) try : res = requests.get(url) except Exception as e: return Response(str (e), mimetype="text/plain" ) return Response(res.text[:32 ], mimetype="text/plain" ) @app.route("/" , methods=["GET" ] ) def index (): return render_template("index.html" ) if __name__ == "__main__" : app.run()
flag.py
1 2 3 4 5 6 7 8 9 10 11 12 from flask import Flask, Responseapp = Flask(__name__) flag = open ("flag.txt" , "r" ).read() @app.route("/flag" , methods=["GET" ] ) def index (): return Response(flag, mimetype="text/plain" ) if __name__ == "__main__" : app.run(port=1337 )
考点:SSRF
解题:
明确4个限制
大体就知道是想让我们去访问内网的flag路由
那么在linux系统中
代替127.0.0.1的有很多
比如
0
localhost
然后根据flag.py知道对于端口为1337
同时flag需要转换 采用URL编码
换一个f就行了
payload:http://0:1337/%66lag
法二:
用自己的服务器利用重定向跳转
输入框 => 自己的服务器 => 重定向访问到内网的flag路由
写在服务器的内容
1 2 3 <?php header ("Location:http://127.0.0.1:1337/flag" )?>
web/Galleria
题面:
Put up some fun images for everyone in this amazing image gallery!
题目:
app.py
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 from flask import Flask, render_template, request, redirect, url_for, send_fileimport osfrom pathlib import Pathfrom werkzeug.utils import secure_filenameapp = Flask(__name__) app.config['UPLOAD_FOLDER' ] = 'uploads' @app.route('/' ) def index (): return render_template('index.html' ) def allowed_file (filename ): allowed_extensions = {'jpg' , 'jpeg' , 'png' , 'gif' } return '.' in filename and filename.rsplit('.' , 1 )[1 ].lower() in allowed_extensions @app.route('/upload' , methods=['POST' ] ) def upload (): file = request.files['image' ] if file and allowed_file(file.filename): file.seek(0 , os.SEEK_END) if file.tell() > 1024 * 1024 * 2 : return "File is too large" , 413 file.seek(0 ) filename = secure_filename(os.path.basename(file.filename)) file.save(os.path.join(app.config['UPLOAD_FOLDER' ], filename)) return redirect(url_for('gallery' )) def check_file_path (path ): _path = Path(path) parts = [*Path.cwd().parts][1 :] for part in _path.parts: if part == '.' : continue if part == '..' : parts.pop() else : parts.append(part) if len (parts) == 0 : return False _path = os.path.join(os.getcwd(), path) _path = Path(_path) return _path.exists() and _path.is_file() @app.route('/gallery' ) def gallery (): if request.args.get('file' ): filename = os.path.join('uploads' , request.args.get('file' )) if not check_file_path(filename): return redirect(url_for('gallery' )) return send_file(filename) image_files = [f for f in os.listdir( app.config['UPLOAD_FOLDER' ])] return render_template('gallery.html' , images=image_files) if __name__ == '__main__' : app.run(debug=False , port=5000 , host='0.0.0.0' )
dockerfile:
1 2 3 4 5 6 7 8 9 10 11 12 13 FROM python:3.11 -slim AS appWORKDIR /var/www/html RUN pip3 install --no-cache-dir flask RUN mkdir uploads COPY app.py . COPY templates ./templates COPY flag.txt /tmp/flag.txt CMD ["python3" , "app.py" ]
考点:代码审查
解题:
我们先没有看源码的情况下 上传一张图片 然后发现看到其他人上传的照片全部显示出来了
这引起一丝警觉
然后对代码进行审查
找到这个路由
参数是file
对file指定的文件进行读取
然后我们又在dockerfile中看到flag文件的位置
读!