前言
参加了XYCTF,没写几道题,但还是在此记录一下。
有什么不对的地方欢迎指正。
(吐槽一下,比赛做了几天后就做不动了,一个月时间是真的长。不想做了,不过这篇WP还是定在了比赛结束再发)
Misc
## 1.签到
题目:扫描二维码关注公众号,发送“XYCTF2024,启动启动启动!!!”获取flag
### 解题过程:
没什么好说的
flag:
XYCTF{WELCOME_TO_XYCTF}
## 2.game
题目:adwa最近迷恋上了一款游戏,他给我们发了这款游戏里的一个解密项目,请你根据这张图片,找出这个游戏的英文名,并用XYCTF{}包括 (每个单词开头要大写,例如XYCTF{Dead Cells})
图片:
### 解题过程:
直接在谷歌上对图片进行搜索,直接找到答案。
flag为:
XYCTF{Papers Please}
## 3.熊博士
题目:熊大熊二在森林里玩耍的时候捡到了一张小纸条,可能事关森林的安危,但是上面的字他们看不懂,你能帮他们看看这些神秘的字符是什么意思吗?
附件:给了一张图片和一个小纸条txt文件如下:
纸条内容:
CBXGU{ORF_BV_NVR_BLF_CRZL_QQ}
提示:
- flag头为大写
- 大写不对要不要试试小写呢
### 解题过程:
由于给了一张图片,以为作者会在图片上做文章,就先对图片进行了一系列分析操作,结果一无所获(图片啥也没有,小丑-->我)
无果后对纸条上的密码进行了凯撒如下:
转换后 —> XWSBP{JMA_WQ_IQM_WGA_XMUG_LL}
发现不符合XYCTF头。但觉得也不像其他密码,尝试栅栏也不对,没有思路了。。。
后来我想 CB和XY都是相邻字母,与凯撒很像。
于是我让B -> Y 的话,发现可以尝试用以下规则进行解码:
A -> Z
B -> Y
...
...
...
Y -> B
Z -> A
成功解出flag:
XYCTF{liu_ye_mei_you_xiao_jj}
#不过这个flag我也是无语了(你可以尝试拼写一下)。
## 4.zzl的护理小课堂
题目:网安学累了吧,zzl说给大家出点护理题放松放松
### 解题过程:
进入网址,如下让我们填选择题:
直接看网页源码,在源码最后看到关键,如下:
一眼看到了主要的代码:
if (score == 100) {
document.getElementById('scoreDisplay').innerText = "你的分数是: " + score + "/100 杂鱼,怎么才100分啊";
} else if (score < 100) {
document.getElementById('scoreDisplay').innerText = "你的分数是: " + score + "/100 noooooob!!";
}
else{
...
...
...
}
就是说不管我们做选择题是否满分,都没有用。
也就是说,我们应该让score>100;才能继续执行有关flag的代码。
那就先点击提交,bp抓包,然后尝试修改响应包,看是否可行。
如何修改响应包:抓到数据包后右键->Do intercept -> 点击Response to this request,然后放包,直到该请求返回响应包。
响应包如图:
将0修改为1000(大于100的数字都可以)。放包即可。看回显:
没想到直接返回flag了。
flag为:
XYCTF{ZZL_TelI_yOU_83fd07835126}
# Crypto
## 1.Sign1n[签到]
题目:看看密码签到题吧 :D
附件:给了一个txt文件,内容如下:
from Crypto.Util.number import *
from tqdm import *
import gmpy2
flag=b'XYCTF{uuid}'
flag=bytes_to_long(flag)
leak=bin(int(flag))
while 1:
leak += "0"
if len(leak) == 514:
break
def swap_bits(input_str):
input_list = list(input_str[2:])
length = len(input_list)
for i in range(length // 2):
temp = input_list[i]
input_list[i] = input_list[length - 1 - i]
input_list[length - 1 - i] = temp
return ''.join(input_list)
input_str = leak
result = swap_bits(input_str)
a=result
def custom_add(input_str):
input_list = list(input_str)
length = len(input_list)
for i in range(length):
input_list[i] = str((int(input_list[i]) + i + 1) % 10)
result = ''.join(input_list)
return result
input_str = a
result = custom_add(input_str)
b=result
print(b)
#12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567891134567799023445779912234577900124456899023346779001344577801223566780112445788912335667990234457780122355788001244568990133566780013445778902335578800133467889112356679902344567991223557880013455788902335667990234457799023355788001235568990133566789113445679912235577801123457889112456678911245578801233467789112355779912234577990233556780113
### 解题过程:
首先代码审计:
上面代码就是将flag转换成数字,再转成二进制,然后在字符串后面补0凑成长度为514位的字符串,再用swap_bits函数将字符串首尾翻转,最后再用custom_add函数将每一位上的数字X(这里设每一位上的数字为X),让(X+i+1)%10取模,之后输出了模的字符串。
分析完之后就很清晰了,写解码脚本:
from Crypto.Util.number import *
b = 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567891134567799023445779912234577900124456899023346779001344577801223566780112445788912335667990234457780122355788001244568990133566780013445778902335578800133467889112356679902344567991223557880013455788902335667990234457799023355788001235568990133566789113445679912235577801123457889112456678911245578801233467789112355779912234577990233556780113
input_list = list(str(b))
length = len(str(b))
for i in range(length):
for x in range(10):
if (x + i + 1 - int(input_list[i])) % 10 == 0:
input_list[i] = str(x)
break
leak = ''.join(input_list)
print(leak)
input_list = list(leak)
length = len(input_list)
for i in range(length // 2):
temp = input_list[i]
input_list[i] = input_list[length - 1 - i]
input_list[length - 1 - i] = temp
leak = ''.join(input_list)
#由于不知道有效长度是多少,所以就把可能的长度都遍历了一遍
for i in range(352, 514):
list = leak[:i]
#print(leak)
flag = int(list, 2)
#print(flag)
flag = long_to_bytes(flag)
print(flag)
运行结果为:
解得flag:
XYCTF{d90e444d-e967-4919-a24b-9972c93fb459}
## 2.happy_to_solve1
题目:so happy
附件:happy_to_solve.py内容如下:
from Crypto.Util.number import *
import sympy
from secrets import flag
def get_happy_prime():
p = getPrime(512)
q = sympy.nextprime(p ^ ((1 << 512) - 1))
return p, q
m = bytes_to_long(flag)
p, q = get_happy_prime()
n = p * q
e = 65537
print(n)
print(pow(m, e, n))
# 24852206647750545040640868093921252282805229864862413863025873203291042799096787789288461426555716785288286492530194901130042940279109598071958012303179823645151637759103558737126271435636657767272703908384802528366090871653024192321398785017073393201385586868836278447340624427705360349350604325533927890879
# 14767985399473111932544176852718061186100743117407141435994374261886396781040934632110608219482140465671269958180849886097491653105939368395716596413352563005027867546585191103214650790884720729601171517615620202183534021987618146862260558624458833387692782722514796407503120297235224234298891794056695442287
### 解题过程:
先看一下代码,意思就是说
m为flag,p是随机生成的一个512位的质数,q是等于p和全1的异或运算,也就是q是p的按位取反~p(如果不清楚为什么,可能是因为你还不知道 "<<" ,“ ^ ”是什么意思,上网一查就知道了,这里不在赘述)
接着往下看,n = p*q ,e = 65537 , c =pow(m, e, n) (这里我们用c来表示加密后的密文)。
了解RSA加密的都知道,接下来应该求什么。如果不知道的话,可以先去网上学习一下。
那么现在求m的话代码如下:
m = pow(c, d, n) #因为e为加密钥,这里d就是解密钥。(公钥加密 私钥解,私钥加密 公钥解)
那么就要求解密钥d:
path = (p-1)*(q-1)
d = gmpy2.invert(e, path) #不懂这个函数的可以上网查一下
这里就闭环了,想求d就需要知道p和q。
p是一个随机质数,q为p的取反,我们只需要求出其中一个数另一个就可以算出来。但很可惜,我并没有想出什么好的方法,于是尝试暴力破解,成功解出flag,就是过程有些繁琐。(如果有更好的方法希望能告知我)
虽说是暴力,但也不可能直接无脑遍历,毕竟p也是一个10进制有155位的大素数。
所以我用的是趋近地方法,我先随机生成一个p,找到一个p*q接近n的值。尝试多次发现了以11开头的p,结果比较接近n。
之后就每次往后取4~5位进行遍历其余位数就为0,一直往后找趋近于n的值。代码例如:
import sympy
n = 24852206647750545040640868093921252282805229864862413863025873203291042799096787789288461426555716785288286492530194901130042940279109598071958012303179823645151637759103558737126271435636657767272703908384802528366090871653024192321398785017073393201385586868836278447340624427705360349350604325533927890879
for i in range(1000,2000):
p = "1"+ str(i) + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
p = int(p)
q = sympy.nextprime(p ^ ((1 << 512) - 1))
N = p*q
#print("p: ", p)
#print("N: ", N)
#print("n: ", n)
if N < n:
j = i - 1
#print(j)
break
之后就把遍历出来的j,取前三位添加到”1后面“,后面的0去掉三个,将j的个位数字作为下次i遍历的范围数。
到最后五位数就可以直接判断N == n,得到最终的p值。
p = 11186104509771514497905845277525414324848282003961401424656189058347435564219816052776565548550435972724614541793612778837623695399649245354880712765072167
(其实应该写一个全自动化的脚本,就不用自己手动输入了,感兴趣的可以自己试一试。我写题写累了,脑子不想再思考了。@.@)
最后运行:
import gmpy2
from Crypto.Util.number import *
import sympy
p = 11186104509771514497905845277525414324848282003961401424656189058347435564219816052776565548550435972724614541793612778837623695399649245354880712765072167 #getPrime(512)
q = sympy.nextprime(p ^ ((1 << 512) - 1))
c = 14767985399473111932544176852718061186100743117407141435994374261886396781040934632110608219482140465671269958180849886097491653105939368395716596413352563005027867546585191103214650790884720729601171517615620202183534021987618146862260558624458833387692782722514796407503120297235224234298891794056695442287
n = 24852206647750545040640868093921252282805229864862413863025873203291042799096787789288461426555716785288286492530194901130042940279109598071958012303179823645151637759103558737126271435636657767272703908384802528366090871653024192321398785017073393201385586868836278447340624427705360349350604325533927890879
e = 65537
path = (p-1)*(q-1)
d = gmpy2.invert(e, path)
m = pow(c, d, n)
print(long_to_bytes(m))
解得flag为:
XYCTF{3f22f4efe3bbbc71bbcc999a0a622a1a23303cdc}
# Web
## 1.ezhttp
题目:非常ez的http
### 解题过程:
进入网址是一个登录界面,先查看一下源代码
看到提示直接扫目录。
发现了君子协议:robots.txt
打开发现了新的文件
查看 l0g1n.txt文件,找到用户名和密码
那么回到登录界面输入信息,看回显。
那么就bp抓包修改referer请求头:
看回显:
再修改User-Agent头:
User-Agent: XYCTF
继续看回显:
那就用回环地址(127.0.0.1)添加到 xff(X-Forwarded-For)头中:
X-Forwarded-For: 127.0.0.1
回显为:
不让用xff,这里就尝试用Client-ip头代替xff:
Client-ip: 127.0.0.1
成功到下一步:
还需要设置代理,由于xff被禁止,这里用Via头来代替:
Via: ymzx.qq.com
再再再看回显:
这里提示修改cookie,在cookie中添加一个XYCTF参数即可:
cookie: XYCTF=1;GZCTF=CfDJ8NASxn5J2mJJj3Gt7corBUbPAjd7UvBwf-62HPYgrC-I2XsrOFg0aV-HfCev82gylbus2CJSDEvMW1PE7Rvri7CPsTPcpUnRqei0nkyh2fbCwY9gDLJ3UArjOWRtYGyWV5ZgqZi8yF_cqTNp02pL4mmj5sttLXQn1miA8PAgMXZrkLejzVIzC43CTWRtPzKCPEwVrstgYI5FoXUhbx7zUL1Fxv8jckaQuTMLjjU53RArcH9OYMR3B5QpNLxiGW214N5NPjmcG_ZdKhFhRbeGEUCjrcUX_GZXcYaMCCAjApAl5vAMXmAn_7I73YqoATACP7FNvUPciYmVZjWE5Fa70v5RW7c9URntXbHvyiG2e2WvD_5cuqM8ai3fDbMZoaYvntEp6CQS-4R2OfOao3ElVz5wrffpsXXv4DP9EU124QIEMc-cDZnJzQOPnEC3zOnnE0VKmxYCJCPaeRW875zRLxLj6kr-XyB1f3TPE5czsVqtR_Rz2i8arZwnVu6xHg2b7lEup7WS6Awng0MySvZ2pDttcXTrp7-oW2TeUbk_sJvAiVhkAcRlIr_VDxU75EG6BTJK8lNNJ5O1YSo7zAF8oVHQ_twUrDZsCPzx3HaXKBwyz2zeQTqo2A7HcOeqWUS5EMDGrRU4R3Gw58u6qP2KSnY43g1afxX_SlIV5qRgYLFrQe5KNZOV_rdzzEKGaZ8vJGk9mV1as2PDAIl9HKaL5J9-8ftz0GEC_67AhdE9I1i1
拿到flag:
XYCTF{a75074c3-44e8-4d0d-948d-6c5e162b58e3}
## 2.warm up
题目:刚起床没什么状态做题,先简单热个身吧
### 解题过程:
打开网址,页面如下:
看看完代码,是让我们先绕过if判断最后执行输出$level2.
那么就先看看如何绕过过滤:
**==第一个if判断:==**
get方式传入val1,val2两个参数,且两个参数不相等,还要MD5值相等。
这里考的是MD5弱类型绕过。不会的可以看看这篇文章:[https://blog.csdn.net/weixin_43332695/article/details/119349204](https://)
这里可以使用0e绕过:
payload:?val1=QNKCDZO&val2=240610708
//意思就是使两个参数MD5编码后都以0e开头,因为这样会把两个参数当作科学计数法来表示。
//而0的无论多少次方都为0,这样两个参数就相等了。
也可以使用数组绕过:
payload:?val1[]=1&val2[]=2
//MD5()会将数组类型的参数报错并返回false,使两个参数都为false,从而绕过。
看回显(数组的话会有报错,但不影响结果。为了美观我用的0e绕过,):
**==第二个if判断:==**
第二的if是让参数md5等于自身的MD5值,这里有一个字符串是满足的,网上也能找到。
payload:&md5=0e215962017
//“0e215962017”的MD5值也是以oe开头的所以能绕过过滤。
回显为:
**==第三个if以及嵌套if判断:==**
这里是让XY==XYCTF,而XYCTF="Warm up"。
只要让XY=Warm up就能绕过,但重点是内层的判断,需要让XY不等于“XYCTF_550102591",且需要与"XYCTF_550102591"的MD5值相等。
用在线MD5加密网站加密字符串"XYCTF_550102591"发现它的MD5值是0e开头的。
所以我们需要让XY加密之后也是0e开头。这样的话XY=Warm up就显然不合适了。
**这里就需要再拓展一个知识点,php变量覆盖:**
利用php代码开头的extrace()函数来绕过。简单来讲extrace()是在当前符号表中创建对应的一个变量,后传入的参数值可以覆盖掉之前就已存在的同名参数的值。
可以看看这篇文章:[https://www.cnblogs.com/zzjdbk/p/12985530.html](https://)
这里通过修改XYCTF的值使XY可以传入任意的值。
所以就可以构造:
payload:&XYCTF=QNKCDZO&XY=QNKCDZO
//QNKCDZO的MD5值是0e开头
看回显:
发现了一个新的php文件。访问它发现:
看完代码,知道需要post传入参数a,利用intval()使if判断为真后,通过preg_replace()函数进行命令执行。
前置知识点:
intval()绕过,可以看这篇文章:[https://blog.csdn.net/wangyuxiang946/article/details/131156104](https://)
preg_replace命令执行,看这里:[https://blog.csdn.net/m0_64815693/article/details/130327529](https://)
咱们一步一步来,
首先传参,
再用preg_match()进行了正则过滤并在前面加了!就是说传参不出现正则匹配的字符就为真,
随后用intval()将参数转为整型。
用HackBar进行post传参,payload:
a[]=123 //这里123有无都无所谓,只要是数组就会报错并执行 返回真。
之后再通过命令执行应该就结束了。
直接看payload:
http://xyctf.top:40064/LLeeevvveeelll222.php?a=/(\S*)/e&b=\1&c=`$_POST[1]`
这里我用$_POST[1],再用POST传参1来传入要执行命令。(因为这道题对GET传参有一些字符上的过滤)
POST传参:
a[]=123&1=ls //回显:LLeeevvveeelll222.php index.php next.php
a[]=123&1=ls ../../../ //回显:bin core dev etc flag home lib linuxrc media mnt proc root run sbin srv sys tmp usr var
a[]=123&1=cat ../../../flag //拿到flag
所以flag为:
XYCTF{4e8b09cf-4c24-461d-84b6-34027a86721f}