PHP审计之POP链挖掘

前言

续上文中的php反序列化,继续来看,这个POP的挖掘思路。在其中一直构思基于AST去自动化挖掘POP链,迫于开发能力有限。没有进展,随后找到了一个别的师傅已经实现好的项目。

魔术方法

__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发,file_exists()判断也会触发
__invoke() //当脚本尝试将对象调用为函数时触发


​__call​​与​​__callstatic​

现实情况下​​__call​​的利用居多,该魔术方法触发的条件是在对象上下文中调用不可访问的方法时触发。

调用流程如下:

$this->a() ==> 当前类a方法 ==> 父类a方法 ==> 当前类__call方法 ==> 父类__call方法


如果触发​​__call​​方法,那么a,即方法名,会作为​​__call​​的方法的第一个参数,而参数列表会作为​​__call​​的方法第二个参数。

来看到代码

function __destruct(){
$this->a->b();
}


这里有2个利用路径,一个是​​$this->a​​中构造一个存在方法的实例化类,另一种方式是找一个不存在b方法并且存在​​__call​​方法的类,当b不存在时,即自动调用​​__call​​。

​__callstatic​​方法只有在调用到静态方法的时候才能触发

​__get​​与​​__set​

不存在该类变量或者不可访问时,则会调用对应的​​__get​​方法

$this->a ==> 当前类a变量 ==> 父类a变量 ==> 当前类__get方法 ==> 父类__get方法


​__get​​代码案例

function __destruct(){
echo $this->a;
}


调用不存在变量a,即会自动触发​​__get​​方法,

数据写入不可访问的变量或不存在的变量即调用​​__set​

function __destruct(){
$this->a = 1;
}


​__toString​

把类当作字符串使用时触发

$this->_adapterName = $adapterName;
$adapterName = 'xxx' . $adapterName;


POP链挖掘

此前构思的自动化挖掘POP链的功能已经被其他师傅实现了,在此就不班门弄斧了,直接拿现成的来用。

按照个人理解反序列化入口点一般为​​__wakeup​​、 ​​__destruct​​、 ​​__construct​​等

思路其实就是寻找​​__destruct​​方法,作为入口点,然后寻找一个回调函数作为末端。而中间需要寻找各种中间链,将其串联起来。串联的方法基本上就是一些魔术方法和一些自定义的方法。

项目地址:​​https://github.com/LoRexxar/Kunlun-M​

cp Kunlun_M/settings.py.bak Kunlun_M/settings.py

python kunlun.py init initialize

python kunlun.py config load

python kunlun.py plugin php_unserialize_chain_tools -t C:\kyxscms-1.2.7


PHP审计之POP链挖掘_魔术方法

结果:

 [20:28:51] [PhpUnSerChain] New Source __destruct() in thinkphp#library#think#Process_php.Class-Process
[20:28:51] thinkphp#library#think#Process_php.Class-Process
newMethod Method-__destruct()
[20:28:51] thinkphp#library#think#Process_php.Class-Process.Method-__destruct
MethodCall Variable-$this->stop()
[20:28:51] thinkphp#library#think#Process_php.Class-Process.Method-stop
MethodCall Variable-$this->updateStatus('Constant-false',)
[20:28:51] thinkphp#library#think#Process_php.Class-Process.Method-updateStatus
MethodCall Variable-$this->readPipes('Variable-$blocking', '\ === Constant-DIRECTORY_SEPARATOR ? 627')
[20:28:51] thinkphp#library#think#Process_php.Class-Process.Method-readPipes
MethodCall Variable-$this->processPipes->readAndWrite('Variable-$blocking', 'Variable-$close')
[20:28:51] thinkphp#library#think#console#Output_php.Class-Output
newMethod Method-__call('$method', '$args')
[20:28:51] thinkphp#library#think#console#Output_php.Class-Output.Method-__call.If
FunctionCall call_user_func_array("Array-['Variable-$this', 'block']", 'Variable-$args')
[20:28:51] [PhpUnSerChain] UnSerChain is available.


这其实利用链就清晰了

Process->__destruct ==>Process->stop ==>Process->updateStatus ==> Process->readPipes ==> Output->readAndWrite ==> Output->__call==> call_user_func_array()


结尾

但该工具并没有达到我个人的预期,因为该工具中只是使用​​__destruct​​这单个方法作为反序列化的入口点。