PF的代码审计日记_CTF篇_DAY_3


前言

怠惰啊,不想看代码

D3CTF ez_upload

当时在比赛的时候没有做出来,现在重新来审计一遍

总共只有一个文件index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (!isset($_POST['action']) || !isset($_POST['url']) || !isset($_POST['filename'])){
highlight_file(__FILE__);
die();
}
$dir = new dir($_POST['url'],$_POST['filename']);
if($_POST['action'] === "upload") {
$dir->upload();
}
elseif ($_POST['action'] === "remove") {
$dir->remove();
}
elseif ($_POST['action'] === "count") {
if (!isset($_POST['dir'])){
echo $dir->count('');
} else {
echo $dir->count($_POST['dir']);
}
}

在没有POST参数时,就会给出源码,反之则创建一个dir对象,分别有upload,remove,count,三个功能

class dir内容分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public $userdir;
public $url;
public $filename;
public function __construct($url,$filename) {
$this->userdir = "upload/" . md5($_SERVER["REMOTE_ADDR"]);
$this->url = $url;
$this->filename = $filename;
if (!file_exists($this->userdir)) {
mkdir($this->userdir, 0777, true);
}
}
public function __toString() {
return implode(" ",scandir(__DIR__."/".$this->userdir));
}
public function __destruct() {
$string = "your file in : ".$this->userdir;
file_put_contents($this->filename.".txt", $string);
echo $string;
}

共有三个魔术方法:

  • __construct
    1. 根据你的remote_addr创建文件夹
      2.根据你输入的两个参数,赋值
  • __toString:
    1. 输出当前目录下的所有文件(夹)名
  • __destruct:

    1. 输出你的userdir
    2. 将你的userdir写入$filename.txt文件中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public function upload(){
$this->checkdir();
$this->checkurl();
$this->checkext();
$content = file_get_contents($this->url,NULL,NULL,0,2048);
if (preg_match("/\<\?|value|on|type|flag|auto|set|\\\\/i", $content)){
die('hacker!!!');
}
file_put_contents($this->userdir."/".$this->filename,$content);
}
public function remove(){
$this->checkdir();
$this->checkext();
if (file_exists($this->userdir."/".$this->filename)){
unlink($this->userdir."/".$this->filename);
}
}
public function count($dir) {
if ($dir === ''){
$num = count(scandir($this->userdir)) - 2;
}
else {
$num = count(scandir($dir)) - 2;
}
if($num > 0) {
return "you have $num files";
}
else{
return "you don't have file";
}
}
  • upload:

    1. 检验用户文件夹,检验url,检验扩展名
    2. 从url中读取内容,如果包含非法关键词就结束
    3. 将内容写入用户文件夹下的名为$filename的文件中
  • remove:

    1. 检验文件夹和扩展名
    2. 删除用户文件夹
  • count:

    1. 扫描当前用户或用户指定文件夹下的文件数量,减去(..和.)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public function checkdir(){
if ($this->userdir != "upload/" . md5($_SERVER["REMOTE_ADDR"])) {
die('hacker!!!');
}
}
public function checkurl(){
$r = parse_url($this->url);
if (!isset($r['scheme']) || preg_match("/file|php/i",$r['scheme'])){
die('hacker!!!');
}
}
public function checkext(){
if (stristr($this->filename,'..')){
die('hacker!!!');
}
if (stristr($this->filename,'/')){
die('hacker!!!');
}
$ext = substr($this->filename, strrpos($this->filename, ".") + 1);
if (preg_match("/ph/i", $ext)){
die('hacker!!!');
}
}

检测内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public function checkdir(){
if ($this->userdir != "upload/" . md5($_SERVER["REMOTE_ADDR"])) {
die('hacker!!!');
}
}
public function checkurl(){
$r = parse_url($this->url);
if (!isset($r['scheme']) || preg_match("/file|php/i",$r['scheme'])){
die('hacker!!!');
}
}
public function checkext(){
if (stristr($this->filename,'..')){
die('hacker!!!');
}
if (stristr($this->filename,'/')){
die('hacker!!!');
}
$ext = substr($this->filename, strrpos($this->filename, ".") + 1);
if (preg_match("/ph/i", $ext)){
die('hacker!!!');
}
}

  1. userdir 必须和自己的remote_addr相匹配
  2. url必须要有协议且不能以php或file开头
  3. 文件名不能有..\,扩展名不能有ph

整个文件就那么点内容,我们来整理一遍

  1. 我们url可以传入data协议,一次来控制content
  2. 整个文件是基于class dir进行的,且存在file_get_contents,因此可以触发反序列化
  3. 无法写入php结尾的文件,且文件内容不能含有<?|flag等,简单的说我们必须要让其他的文件解析成php文件,因为是apache,所以不难思考到需要写入.htaccess文件,写入需要用到的大致有三种方法

    • FileMatch 配合 SetHandler,这里set被过滤了
    • autoappend 再把木马卸载htaccess中,这里auto没了
    • AddType 这里可用
      由此我们可以将任意的后缀作为php执行
  4. 在对象销毁时我们也有一次写入的机会,不过是写入一个txt文件,但是这里的写入内容是userdir且这里没有任何过滤,如果我们有机会可以控制userdir那么我们就可以写入一句话,然后通过htaccess,来进行将txt执行为php脚本来运行

解决问题

  • 在所有魔术方法中只有一个很奇怪,那就是__toString这个我们从来没有用过,很明显,这就是这道题的突破点

    1. echo $string 在__destruct
    2. upload中存在phar反序列化
      3.. __toString输出目录下的文件名

现在就很明显了,我们可以让userdir是一个dir对象,我们通过反序列化,触发destruct,输出目录下文件,并写入$filename.txt中,这时候,我们只需要先写入一个文件名为eval($_GET['pf']);的文件,然后套娃class dir即可

有些问题

1
file_put_contents($this->filename.".txt", $string);

这里我们想要写入文件中,必须要文件的绝对路径,但是不幸的是,这题不是默认路径,所以这题我们还需要泄露路径:

1. 我们可以通过写入最后将一句话写入文件的思路,我们可以将套娃中的`$this->userid=./` => `$this->userid=../`
2. 通过有一个我们没用到的东西,就是count,我们通过通配符来判断文件夹下的文件数量,然后逐位爆破即可