0%

[HarekazeCTF2019]Easy Notes

[HarekazeCTF2019]Easy Notes

PHP题,感觉好久没见了,buu上也不说明一下这个题给不给源码,导致我盲测了一个多小时受不了了看wp告诉我是有源码的,然后去GitHub翻源码做。。。害人

卑微盲测

随便登录一下,发现add,delete,view,export几个基本功能,flag功能获取flag但是要求我是admin,登录名改成admin显然无效,不知道是想让我怎么攻击,很令人在意的就是访问时是?page=add这种形式,妥妥的文件包含,开始用filter读源码,被重定向回去,然后访问add.php,报404,那就估计是有一个次级目录专门放包含的文件,尝试跳目录包含自己,不行,简单的跳目录也不行,最后试了试page=/add都不行。放弃包含,估计是已经写死了所有能包含的内容了,后来看源码的确如此。

文件包含不行也有其他的攻击点,这个note添加进去之后会分配一个随机id,还能回显,这种东西可能会通过数据库维护文件系统,note的标题可能存在二次注入,打了一波过去,无果。。。然后查看note的时候get又会多提交一个参数id,也是一波注入,再打一轮,无果。。。心态爆炸

能感觉到比较题目最关键的那个export模块还没用过,但是测试一下就是把note内容作为json文件压缩一下发下来,不知道可利用的点在哪,随便打了一波奇奇怪怪的字符过去还全被用-给替代了
最重要的就是这一些和我是不是admin也没什么太大的关系。

开始找是不是有隐藏起来的源码,robots.txt www.zip,www.tar.gz,.git目录,全部无效,开始找wp

题解

wp上来就告诉你给了源码,卑微去GitHub上翻源码继续做,哭了
在init.php里面有一句非常令人在意的话session_save_path(TEMP_DIR);
刻意修改了session的存放位置,而看了一眼flag.php,对admin的判断就是看isset($_SESSION['admin']),是的话就是true了,这显然是要把session放到一个我们可控制的目录下,构造一个session获取flag
再去看令人在意的export功能,刚好也是用的这个TEMP_DIR
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;
$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);

在这个session存放的文件夹下创建一个文件,压缩写的note进去,然后下载下来,文件名的组成是登录用户名-随机十六进制字符串.后缀,session文件的格式刚好是sess_[a-zA-Z0-9-_]*,一开始我看见那个短斜杠的时候还以为没得机会了,结果也能用
session文件是不能包含点的,但是刚好他为了防止目录穿越,将..替换为空,而type提交的类型可以是任意值,值不是tar的时候用zip压缩罢了,因此提交type=. ,就可以构造出一个很完美的session文件,而且我们还能得到这个session文件的名字,用他作为cookie登录

不过还是有一定的难度,毕竟使用的是压缩算法,压缩进去的东西乱七八糟的,但是进过尝试,note的标题会完整的在文件内容中出现,但需要保证session反序列化的时候不出问题,还需要构造一下

session的默认序列化引擎是php,序列化方法是 键|序列化数据;使用构造一个内容为|N;admin|b:1;的数据,|N;将前面的杂乱的数据作为一个键解决掉,防止序列化出错,然后给admin这个值赋个值就行了,提交该标题的note,在export.php提交type=.,得到的文件名就是sessionID,改掉访问page=flag获取flag