东北大学第三届 NEX CTF 题解
发表于更新于
竞赛CTF东北大学第三届 NEX CTF 题解
VLSMB本次参赛的最大感受就是AI变nb了,今年可以用AI干好多之前无法想象的事情…..
Misc
【简单】签到题
找作者:

【中等】奇思妙想聪明的小羊
用压缩软件打开图片,发现很明显是一个.git文件夹的结构,随便新建一个文件夹,将压缩包里的东西放进.git文件夹,然后用git status查看,没问题之后git log,发现有2次commit,最新一次是删除flag文件,用git reset还原即可

Crypto
【简单】Pyyyyyyyyyyyyyyyyyyyython
用AI应用秒了,解放双手

【简单】来自英仙座的怪兽 & 【中等】怪兽的最后反攻
每次出一个问题,就让DeepSeek补充代码计算即可。


代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
| import math
from math import gcd
def common_modulus_attack(n, e1, e2, c1, c2): """ 共模攻击:使用相同模数n和不同指数e1,e2加密的同一明文 """ if gcd(e1, e2) != 1: raise ValueError("e1和e2必须互质") g, s, t = extended_gcd(e1, e2) if s < 0: c1_inv = pow(c1, -1, n) part1 = pow(c1_inv, -s, n) else: part1 = pow(c1, s, n) if t < 0: c2_inv = pow(c2, -1, n) part2 = pow(c2_inv, -t, n) else: part2 = pow(c2, t, n) m = (part1 * part2) % n return m
def extended_gcd(a, b): """扩展欧几里得算法""" if a == 0: return b, 0, 1 gcd, x1, y1 = extended_gcd(b % a, a) x = y1 - (b // a) * x1 y = x1 return gcd, x, y
def mod_inverse(a, m): """求模逆元""" gcd, x, _ = extended_gcd(a, m) if gcd != 1: raise ValueError("逆元不存在") return x % m
def rsa_decrypt_manual(p, c, e=65537): """ 手动实现RSA解密(便于理解原理) """ phi = p - 1 d = mod_inverse(e, phi) m = pow(c, d, p) return m, d
def rsa_decrypt_complete(n, p, c, e=65537): """ 完整的RSA解密流程 """ if n % p != 0: raise ValueError(f"p={p} 不是 n={n} 的因子") q = n // p def is_probable_prime(num): if num < 2: return False for i in range(2, min(int(math.sqrt(num)) + 1, 10000)): if num % i == 0: return False return True if not is_probable_prime(p): print(f"警告: p={p} 可能不是素数") if not is_probable_prime(q): print(f"警告: q={q} 可能不是素数") phi = (p - 1) * (q - 1) try: d = pow(e, -1, phi) except ValueError: raise ValueError(f"e={e} 在模 φ(n)={phi} 下没有逆元") m = pow(c, d, n) return m
if __name__ == "__main__": mode = int(input()) if mode == 1: x = int(input()) print(int(x ** (1/5))) elif mode == 2: p = int(input('p: ')) c = int(input('c: '))
m, d = rsa_decrypt_manual(p, c) print(m) elif mode == 3: n = int(input('n: ')) p = int(input('p: ')) c = int(input('c: ')) m = rsa_decrypt_complete(n, p, c) print(m) elif mode == 4: n = int(input('n: ')) e1 = int(input('e1: ')) e2 = int(input('e2: ')) c1 = int(input('c1: ')) c2 = int(input('c2: ')) m = common_modulus_attack(n, e1, e2, c1, c2) print(m)
|
Reverse
【简单】办公达人
把Excel的隐藏表翻出来,然后根据隐藏表的信息写等效计算代码即可。


Web
【简单】签到
第一块是一个隐藏的input组件里,接下来沿着提示找就行

【简单】puzzle
js代码限制了鼠标右键和键盘,但浏览器可以通过其他方式只使用左键打开控制台,比如说FireFox,右上角的选项卡里就有

然后打印一下window对象,发现里面就一个是布尔类型的变量,改成true就可以了

不过需要在2s之内完成,时间很充裕,先复制好语句,刷新之后立刻执行就行

【简单】校园福利中心
首先看了一下他的main.js代码,发现有一个请求头X-Can-View,默认值是no

请求前改成yes就行了

【简单】简易签名的VIP计划
先自己转账一下,看一看请求的格式是什么,可以看到重要的是请求头的Authorization和请求体

之后看了一下js代码,发现参数校验仅在前端进行。(虽然这里是故意在前端校验的,但看Web开发视频的时候总有人说“参数在前端验证不就行了”之类的话,我估计生产环境真有这种代码~)

前端根据参数生成JWT,并发送POST请求。因为前端就有可以使用的方法,所以可以直接在控制台调用相应方法,伪造JWT,然后发送请求


【中等】NEX文档站
在用户名和密码里输入' OR 1=1 --就可以进去了,说明有SQL注入的漏洞。在没放提示之前我以为flag会在数据库中,因为ctf_docs.users表有三行数据,于是用了时间盲注,等了好久结果没看到有用信息

之后提示说是在某个地方的flag.txt里,登陆后观察地址,发现/book/*是用来访问文件的,所以让AI构造了几个可能存在的地方测试

【中等】世界时钟
我看这个网页的JS代码没有混淆,所以直接托管给AI做了

【中等】老虎机
跟上面一样

【简单】It’s MyGo!!!
阅读源码发现,发送POST请求,参数mygo的值会被当作命令处理


【中等】Ave Mujica
这道题与上面类似,但是网页没有回显,而且限制严格。观察上一个题目源码发现,./templates/assets文件夹的文件被挂载到了/assets路由,只要把输出重定向到这个文件夹里的文件就行。最开始我以为echo,ls这种命令都被限制了,但单独使用却没有问题,后来发现是限制了空格,可以用$IFS绕过去,不过还限制了>符号,使用b64编码绕过去了:
echo$IFS"cHJpbnRlbnYgPiAuL3RlbXBsYXRlcy9hc3NldHMvZW52LnR4dA=="$IFS|$IFS$@base64$IFS-d$IFS|sh


【简单】公开的秘密
一看就是DNS记录

【中等】扭曲的镜像
进去发现输入地址,回显是ping的输出,说明直接执行的终端命令,用&连接第二个命令就行,先ls /观察一下

【困难】变形的钥匙
阅读源码,发现Python后端虽然校验了输入,但仍然把原始b64传给给终端。正常来说后端已经获取了通过检验后的IPv4输入,直接把解码后的传给终端不就行了。所以说明相同的b64字符串,在Python端和终端表现会不一样。
标准base64会在长度不够的时候在结尾填充=,不会出现在中间,所以我觉得可能Python按照这个进行检验解码,而终端命令可能还会继续阅读接下来的b64编码,实践也证实了确实是这样:

发送构造好的b64字符串就可以了

【简单】第一次接触:咖啡店的暗号
解析pkl文件就可以了,不过要定义好相应的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import pickle
class Contact: def __init__(self, name=None, email=None, phone=None, flag=None): self.name = name self.email = email self.phone = phone self.flag = flag
try: with open('contact.pkl', 'rb') as file: contact = pickle.load(file) print("=== contact.pkl 文件内容 ===") print(f"姓名: {contact.name}") print(f"邮箱: {contact.email}") print(f"电话: {contact.phone}") print(f"标志: {contact.flag}") print("============================") except Exception as e: print(f"读取文件时出错: {e}")
|

本来还想接下来继续做完Web剩下的两道题目,但是纱布AI把我自己系统的环境变量打包成pkl,我也没仔细看,导致我系统环境里十多个API KEY全™上传到题目容器里了,害得我™花了30min一个一个去失效API Key😭😭