“没有绝对的安全”之文件上传漏洞

448 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情

欢迎来到“没有绝对的安全”系列。最近一个名为“你安全吗”的网络剧比较火,看了几集以后,我的感悟就是:在互联网上,没有绝对的安全,只有更加安全。黑客永远会想到更先进的攻击方式,而我们也要随之做好防御,知其黑,守其白,保障网络安全。而文件上传漏洞是黑客经常利用的一个漏洞,下面我来对这个漏洞做个总结。

图片.png

概念:

“这个漏洞在DVBBS6.0时代被黑客们利用的最为猖獗,利用上传漏洞可以直接得到WEBSHELL,危害等级超级高,入侵中上传漏洞也是常见的漏洞。 导致该漏洞的原因在于代码作者没有对访客提交的数据进行检验或者过滤不严,可以直接提交修改过的数据绕过扩展名的检验” :这是百度百科对文件上传漏洞的概念。 这个概念即使萌新也是比较好理解的。大家可以看下面这个图。

图片.png

用大白话来说就是一个网站有一个让人上传头像(或者其他)的功能,但是不法分子,也就是我们所说的黑客上传了木马(webshell)然后获得了对该站点的权限。 存在文件上传功能的地方都有可能存在文件上传漏洞,比如相册头像上传,视频、照片分享。论坛发帖和邮箱等可以上传附件的地方也是上传漏阔的高危地带,另外像文件管理器这样的功能也有可能被攻击者所利用。这是非常可怕的,所以网站要做好过滤,防止黑客利用这个漏洞进行犯罪活动。

DVWA

下面我们开始用dvwa的靶场来实战一下,我是用服务器在docker搭建的靶场,不会的可以百度一下dvwa靶场的搭建。在buu的basic里也有现成的靶场。

low(前端js检查)

我试了一下,可以上传png图片,查看一下源码

<?php
if( isset( $_POST[ 'Upload' ] ) ) {
    // Where are we going to be writing to?
    $target_path  = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";//target——path是目标目录,就是上传成功后的目录。这里是上传到hackable/uploads
    $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );///,basename() 函数返回路径中的文件名部分。在这里表示,文件名还是原来的文件名。
    // Can we move the file to the upload folder?
    if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
        // 检测临时文件是否上传到需要上传的目录
        echo '<pre>Your image was not uploaded.</pre>';
    }
    else {
        // Yes!
        

target——path是目标目录,就是上传成功后的目录。这里是上传到hackable/uploads, basename() 函数返回路径中的文件名部分。在这里表示,文件名还是原来的文件名。 move——uploaded函数是关键函数,检测临时文件是否上传到需要上传的目录

这个过程没有作任何的过滤,我们就可以直接上传一句话木马

图片.png

 <?php @eval($_POST['attack']);?>
                    

上传以后就显示成功上传

然后用蚁剑连接一下,就获得了对该服务器的权限

图片.png

medium (文件类型mime)

我们再上传一下一句话木马文件,发现,上传失败。

图片.png 并提示我们,只能上传jpg文件和png文件,可以看到,这里有个js检测。

File information
    $uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];///从http头中提取信息
    $uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
    $uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];

    // Is it an image?
    if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
        ( $uploaded_size < 100000 ) )

,那么我们该怎么绕过呢。

因为这个是客户端检测,所以我们打开burpsuite,抓个包

图片.png

图片.png

这个是菜鸟教程对content type的解释,清晰明了。它决定浏览器将以什么方式读取文件,你把content-type设置成jpg,那么即使你是个php文件,打开,可能也是一个图片。而这里利用的就是这个点。

把content-type改成:image/jpeg.再一看,已经成功上传了。

图片.png 再用蚁剑,成功连接。拿到了对服务器的权限。

high(文件头图片马)

我们先看一下源码,发现大同小异,只有一处不同。

图片.png

这里有两个函数:substr strrpos

substr函数是返回字符串的一部分,比如上面这个栗子,返回字符串第六个字节以后的内容。

strpos() 函数查找字符串在另一字符串中第一次出现的位置。

这一句就是返回小数点之后的内容必须为jpeg或者png。因此上一关的方法已经不适用了。需要用到图片马。 图片马:就是在图片中隐藏一句话木马。利用.htaccess等解析图片为PHP或者asp文件。达到执行图片内代码目的。

  • 单纯的图片马并不能直接和蚁剑连接,
  • 因为该文件依然是以image格式进行解析,
  • 只有利用文件包含漏洞,才能成功利用该木马
  • 所谓文件包含漏洞,是指在代码中引入其他文件作为php文件执行时,未对文件进行严格过滤,导致用户指定任意文件,都作为php文件解析执行。

制作图片马有三种方式。

第一种,利用cmd命令行

图片.png

jpg/b这个b就是byte,是字节的意思。因为照片是二进制文件。php/a后面是a是因为php文件是Ascii文件。

第二种,利用文本编辑工具

我们用文本编辑工具。比如notepad++,记事本打开,然后在后面粘贴上一句话木马就行了。

图片.png

第三种方式,用010editor或者winhex这样的十六进制编辑软件打开,在后面贴上一句话木马,也是可以的。

上传一下,成功。连接蚁剑,ok。

impossible

<?php

if( isset( $_POST[ 'Upload' ] ) ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );


    // File information
    $uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
    $uploaded_ext  = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
    $uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
    $uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
    $uploaded_tmp  = $_FILES[ 'uploaded' ][ 'tmp_name' ];
## 从http头中获得一些信息

    // Where are we going to be writing to?
    $target_path   = DVWA_WEB_PAGE_TO_ROOT . 'hackable/uploads/';
    //$target_file   = basename( $uploaded_name, '.' . $uploaded_ext ) . '-';
    $target_file   =  md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;//利用uniqid函数制作一个独一无二的id然后与文件的id结合,生成一个新id然后进行md5哈希。md5函数是把任意长度的字符串变成一个等长字符串。保障了文件的名字不能重复。
    $temp_file     = ( ( ini_get( 'upload_tmp_dir' ) == '' ) ? ( sys_get_temp_dir() ) : ( ini_get( 'upload_tmp_dir' ) ) );
    $temp_file    .= DIRECTORY_SEPARATOR . md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
	

    // Is it an image?
    if( ( strtolower( $uploaded_ext ) == 'jpg' || strtolower( $uploaded_ext ) == 'jpeg' || strtolower( $uploaded_ext ) == 'png' ) &&//针对后缀名做检验
        ( $uploaded_size < 100000 ) &&//针对大小做检验
        ( $uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png' ) &&上传文件类型做检验
        getimagesize( $uploaded_tmp ) ) {//是否为图片

        // Strip any metadata, by re-encoding image (Note, using php-Imagick is recommended over php-GD)
        if( $uploaded_type == 'image/jpeg' ) {
            $img = imagecreatefromjpeg( $uploaded_tmp );
            imagejpeg( $img, $temp_file, 100);
        }
        else {
            $img = imagecreatefrompng( $uploaded_tmp );
            imagepng( $img, $temp_file, 9);
        }
        imagedestroy( $img );

        // Can we move the file to the web root from the temp folder?
        if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) {
            // Yes!
            echo "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>";
        }
        else {
            // No
            echo '<pre>Your image was not uploaded.</pre>';
        }
//利用imagecratefrompng函数生成一个新的图片并把原来的图片的注释去掉。防止上传图片马。
        // Delete any temp files
        if( file_exists( $temp_file ) )
            unlink( $temp_file );
    }
    else {
        // Invalid file
        echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
    }
}

// Generate Anti-CSRF token
generateSessionToken();

我们可以看到,一开始做了了防token的泄露。然后还是先从http头获取到类型,名字大小等信息。规定我们上传文件最终的目的目录。因为用了unicid函数和md5哈希,就避免了上传到相同的目录(也许不对)。接下来检验上传的是不是一个图片。这里多了个image gestmagesize函数

图片.png

来检测是否是真的图片。还用到了两个函数,imagecreatefromjpeg ,imagejpeg 图片.png

图片.png

用这两个函数将原来的图像生成一个新的图像,然后用unlink函数删去原来图像所有的注释,这就直接杜绝了图片马这种方式。

后缀名绕过

dvwa靶场是没有uploadlabs全的,有一部分是没有涉及到的,比如后缀名绕过,后缀名绕过分为黑名单绕过,和白名单绕过。

黑名单检测

通常是针对文件的扩展名后缀进行检测,主要是通过黑白名单进行过滤检测,如果不符全过滤规则则不允许上传。 绕过方法: 大小写绕过

比如:aSppHp之类。

黑名单扩展名的漏网之鱼

比如: asacer之类 asp: asa cer aspx jsp: jspx jspf php: php php3 php4 php5 phtml pht exe: exee

白名单检测

一般有个专门的 whitelist 文件,里面会包含的正常文件:Jpg png GIF,就是说,允许上传的五年间类型。

绕过方式:00截断

搜一下00截断的原理

C语言中,空字符有一个特殊含义,代表字符串的拼接结束。   这里我们使用的是php语言,属于高级语言,底层靠C语言来实现的,也就是说空字符的字符串拼接结束功能在PHP中也能实现。但是我们在URL中不能直接使用,这样会造成无法识别;我们通过查看ASCII对照表,发现ASCII对照表第一个就空字符,它对应的16进制是00,这里我们就可以用16进制的00来代替空字符,让它截断后面的内容。 简单来说,就是通过编码的方式,进行截断