best_profile

先分析源码,发现在app.py中存在如下代码

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

geoip2_reader = geoip2.database.Reader("GeoLite2-Country.mmdb")
@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)

...

@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

这里逻辑是先访问http://127.0.0.1/get_last_ip/{username},将获得的res.text进行了模板渲染,可以在这里构造ssti。根据最后的set_last_ip()可知last_ip可以通过XFF获取,主要问题就是request.get请求时不会带上session访问,导致每次访问都是You need to login first.

注意到nginx.conf中代码

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

在处理这些后缀的文件时,会缓存在本地,所以在请求时就不会调用flask应用而是直接调用本地缓存文件,那么这样就避免了未授权问题。

首先正常注册登录,username2.swf(列表中任意后缀即可),进了主页之后访问get_last_ip/2.swf,先抓包加上XFF访问一个错误的路由,触发set_last_ip(),注意这里是由于缓存机制,所以是一次性的,如果操作错了就只有重新注册登录。

image-20250712185604548

然后用正确的路由重新访问一次,让XFF进入last_ip

image-20250712185651611

这样last_ip就设置好了,访问ip_detail/2.swf就能成功渲染,之后同理即可

image-20250712185812492

在进行ssti时测出来过滤了'',旁路注入绕过即可

X-Forwarded-For:{{lipsum.__globals__[request.args.a].popen(request.args.b).read()}}
a=os&b=tac /flag

image-20250712183545237

image-20250712183632051

L3HCTF{y0ur_1P_1s_my_t3mpl4t3}