2025 XYCTF
又来学 Web 了
碎碎念
没憋住,还是看了下密码。
唉
Web
Signin
白盒,给了个 main.py
,直接看代码:
1 | # -*- encoding: utf-8 -*- |
应该是先用文件读拿到 secret.txt
,过滤很简单,既然不允许 ../../
,那直接 ./.././../
bypass 即可。
构造 ?filename=./.././../secret.txt
拿到 secret 为 'Hell0_H@cker_Y0u_A3r_Sm@r7'
。
观察了下 bottle 的源码,这个 secret 是用来签名的,只有验签通过他才会执行 pickle.loads()
。
1 | def get_cookie(self, key, default=None, secret=None, digestmod=hashlib.sha256): |
搜索得知 pickle
存在反序列化漏洞,最终代码如下:
1 | import base64, hmac, os, hashlib |
实现 RCE,试了半天怎么都弹不出 shell,都开始怀疑我命令到底有没有执行了,搞了个 rm -rf /
,再读 secret.txt
,发现文件没了,说明命令确实有执行,同时发现 /etc/passwd
也有权限删,直接把 flag 读到 /etc/passwd
里了hhh
后来执行一下 ls /bin
才发现根本没有 curl
,要弹 shell 的话估计得换个方法。
/bin/
内容如下
1 | arch ash base64 bbconfig busybox cat chattr chgrp chmod chown cp date dd df dmesg dnsdomainname dumpkmap echo egrep false fatattr fdflush fgrep fsync getopt grep gunzip gzip hostname ionice iostat ipcalc kbd_mode kill link linux32 linux64 ln login ls lsattr lzop makemime mkdir mknod mktemp more mount mountpoint mpstat mv netstat nice pidof ping ping6 pipe_progress printenv ps pwd reformime rev rm rmdir run-parts sed setpriv setserial sh sleep stat stty su sync tar touch true umount uname usleep watch zcat |
ezsql(手动滑稽)
随便试几下发现 username
字段可以注入,password
输什么都会被转义,且存在空格过滤,参考这篇文章使用 tab
作为空格绕过,payload 如下:
1 | username='%09OR%091=1%09#&password=1 |
跳转到 doublecheck.php
,搞了一下发现也妹得注入
用 bp 看了一下,跳转之前 login.php
是有响应的,可以使用布尔盲注,注意到逗号也被过滤了,使用 substr(xxx from x for y)
代替 substr(xxx, x, y)
,脚本如下:
1 | import requests as r |
一点点把东西全注出来,然后用拿到的帐号密码还有 secret 直接登录,进入一个命令执行的页面,无回显,测试了一下存在空格过滤,于是用 ${IFS}
作为空格绕过,试了一下 sleep${IFS}2
,发现响应明显变慢了,说明可以执行,执行 ls${IFS}/${IFS}>${IFS}flag.txt
,看到 flag.txt
了,直接 cp${IFS}/flag.txt${IFS}flag.txt
,再访问 /flag.txt
即可。
puzzle
打开是一个拼图网页,刷新几次发现并不能随机到初始即正确的情况,按 F12
发现被拦截,在 bp 看到逻辑是在 index.html
里的 JS 加入了对 contextmenu
和 keydown
的监听,同时发现网页引用了 /js/puzzle.js
。
那么我们直接访问 /js/puzzle.js
,即可绕过拦截打开控制台。
然后一看,我嘞个一大坨 JS 啊,直接把代码丢给 DeepSeek,花了几毛钱直接判断出变量 ogde564hc3f4
控制是否完成,本地修改一下 ogde564hc3f4
的值为 true
,然后在 2 秒内随便点一下就出 flag 了:flag{Y0u__aRe_a_mAsteR_of_PUzZL!!@!!~!}
Crypto
勒索病毒
给了一个 task.exe
和 flag.txt.enc
,用 IDA 一看都是 modules 之流,用 python-exe-unpacker 解包,得到的 task
文件就是 task.sage
,代码如下:
1 | # @author: Crypto0 |
但是按理说这个应该是不能跑的,不知道是不是我解包有问题,poly_terms = terms(poly_str)
得到的是一个 list,按理说 binascii.unhexlify()
是不能传的,应该是少了个 gen_key()
的处理。
题目总体逻辑比较简单,但是命名非常混乱,priv_key.txt
并不是私钥,而是一个随机的多项式,然后这个多项式被 NTRU 加密了,注意到解包出来的还有公钥 pub_key.txt
,和加密后的多项式 enc.txt
。
用轮子搞个私钥出来:
1 | from Crypto.Util.number import * |
然后存到 priv_key.txt
里,把 main()
函数改成
1 | poly_str = read_polynomial_from_file("priv_key_1.txt") |
得到 flag:XYCTF{Crypto0_can_n0t_So1ve_it}
Division
1 | # -*- encoding: utf-8 -*- |
MT19937 小练习,上轮子梭
1 | from pwn import * |
reed
呃,丢给 DeepSeek 就行了,只要存在 a 就能解出来,根本不用管 PRNG
1 | import string |
会跑出不同结果,肉眼筛选一下即可。
Complex_signin
Coppersmith 小练习,直接把式子拆出来,然后套上多元轮子即可
1 | from sage.all import * |
prng_xxxx
我就说这个问题肯定有人想过的,搜到这篇文章
我大概翻译一下,问题形式如下:
$$
X_{i+1} = (AX_i + C) \mod 2^{n} \\
Y_i = (X_i/2^{n/2}) \oplus (X_i\mod 2^{n/2})
$$
解法:
Step 1
先找到 $|w_i|<W$ 使得
$$
\sum_{i=1}^{m} w_i A^i \equiv 0 \pmod{2^{n/2+k}}
$$
这里可以用一个如下形式的格做 LLL 求解:
$$
\begin{pmatrix}
1 & 0 & 0 & \cdots & 0 & KA \\
0 & 1 & 0 & \cdots & 0 & KA^2 \\
\vdots & \vdots & \vdots & \ddots & \vdots & \vdots \\
0 & 0 & 0 & \cdots & 1 & KA^m \\
0 & 0 & 0 & \cdots & 0 & K\cdot 2^{n/2+k} \\
\end{pmatrix}
$$
其中 $K$ 取适合大的值。
Step 2
然后猜 $X_0$ 的低 $k$ 位,若 C 未知,同步猜测 $C$ 的低 $k$ 位,依据这俩把整个 $X_i$ 推出来。
Step 3
此时可以理解成 $X_0$ 和 $C$ 的低 $k$ 位已知,用 $Y_i$ 把 $X_i$ 左半部分的低 $k$ 位也还原出来(这里没看懂的的去看鸡块哥的文章),把这些位记为 $X_i^*$,即有 $X_i^* = 2^{n/2} \times \mathrm{guess}$
Step 4
推出下面的式子:
$$
\sum_{i=1}^{m} w_i [X_{i+1} - X_i] \equiv 0 \pmod{2^{n/2+k}}
$$
Step 5
计算
$$
Z = \sum_{i=1}^{m} w_i [X_{i+1}^* - X_i^*] \pmod{2^{n/2+k}}
$$
记 $\Delta$ 为 $Z$ 与 0 或 $2^{n/2+k}$ 的差值(选最小的一个)
Step 6
若 $\Delta \ge 2mW\cdot 2^{n/2}$,那对于 $X_i$ 的低 $k$ 位的猜测肯定是错的,否则就有 $1-2mW\cdot 2^{-k}$ 的概率是对的。
Step 7
对不同的 $Y_i$ 尝试所有 $2^k$ 的猜测,直接只剩下最后一个猜测,就是 $X_0$ 的低 $k$ 位。
Step 8
重复以上步骤,直到 $X_0$ 的所有位都被还原。
$k$ 应该比 $\log_2mW$ 大很多,不然 step 6 的判定就很难起作用。
按以上的方法搞一搞,最终 flag 为 XYCTF{0h_3v3n_X0R_c@n't_s@v3_LCG!}
代码一坨屎就先不放了,就注意一下 step 7 的意思是不用选全部的 output,比如第一轮用 $Y_0$ 到 $Y_{31}$,第二轮用 $Y_1$ 到 $Y_{32}$,第三轮用 $Y_2$ 到 $Y_{33}$,依次类推,而且实际上也不会只剩下一个,会剩下 4 至 16 个左右。
改好了,端上巧克力味的史,为了方便读者理解整个流程,没有做太多的简化
1 | from sage.all import * |
choice
又是 MT19937 小练习,复用 Division 的代码就行了
1 | import sys |
复复复复数
问问 DeepSeek,直接就逆出了 hints
,但是 flag 还是还原不出来,一看原来 $e$ 跟 $\varphi(n)$ 不互素,呃呃了,感觉国内出题都喜欢这样
再看看能不能偷鸡,一看 $e$ 和每个素因子的 phi
GCD 都是 3,好吧,老老实实开个根
先还原出 $m^9$,再开两次三次方根即可,多拷打两下 DeepSeek 就把代码给出来了
1 | from sage.all import * |
1 | # getroot3.py |
最终得到 flag:flag{Quaternion_15_ComComComComplexXXX!!!?}
参考
https://www.ek1ng.com/SEKAICTF2022.html
https://ucasers.cn/python%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E4%B8%8E%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8/
https://blog.csdn.net/xuxingzhuang/article/details/117108502
https://www.freebuf.com/articles/web/426189.html