代码审计,是对应用程序源代码进行系统性检查的工作。它的目的是为了找到并且修复应用程序在开发阶段存在的一些漏洞或者程序逻辑错误,避免程序漏洞被非法利用给企业带来不必要的风险。
代码审计不是简单的检查代码,审计代码的原因是确保代码能安全的做到对信息和资源进行足够的保护,所以熟悉整个应用程序的业务流程对于控制潜在的风险是非常重要的。
安全问题所在:
从代码级别上,也就是应用层次上考虑代码安全的话(也就是不考虑底层的语言本身等问题的漏洞),脚本安全问题就是函数和变量的问题。变量直接或者间接的接收用户不安全的的输入,由于 php 本身的特性,在 php 中更容易发现这种变量的混乱(很多 php 程序都用来定义以及初始化以及接收变量,可以直接在程序中使用$id 这样的变量,初始化完全由 php 的设置来完成,如果稍不注意,就可能导致变量的混乱从而导致攻击)。
变量接收不安全的输入之后,没有做恰当的过滤又用在不同的地方,就可能造成不同的危害。如果直接进入数据库然后显示给用户就会导致跨站脚本攻击,如果用在 sql 语句中就可能导致 Sql 注射攻击,这几种攻击都是是与具体的脚本语言无关的,在各种脚本语言里都可能存在。由于 php 的变量很灵活,这些有害的变量如果用在一些逻辑语句中,就会导致关键代码的跳过如身份验证失败和跳过一些变量的初始化从而导致程序逻辑混乱而产生其他漏洞。如果这个变量用在了危险的函数如 include 等等当中,当然就会出现文件包含漏洞,出现在 fopen 函数里就会可能产生写文件的漏洞,出现在 mysql_query 函数中就是 Sql 注射漏洞,eval 以及 preg_replace 中可能导致代码的执行,出现在 htmlspecia 函数中可能导致出错而绝对路径泄露… 变量出现的环境决定了它可能的危害。
总结为:
思考了问题的存在,那么如何从代码级别上检查这种漏洞呢?当然熟悉熟悉 php 语言是最基本的,也应该是抓住函数和变量,危险的函数里如果有变量那么请确定这个变量的来源,是否正确的初始化,初始化之后是否能被用户注入敏感字符,在进入函数前这些敏感的字符是否得到了彻底的清除。对于代码审核工作的难点可能就在于对变量来源的确定,这需要对 php 特性以及你所审核的代码的熟悉,但也并不是所有的变量的来源都清晰可见,可能一些初始化的代码并没有像想象中运行,一些变量里的东西可能也来自于你并不想他来的地方,还有一些变量可能来自于数据库或者系统的配置文件,但是很可能数据库和配置文件在之前就已经被修改,或者在后面不安全的操作了这些变量,这些变量也是不可相信的。本文档就按照变量与函数的思路来思考脚本代码的安全。
echo
//输出一个字符串或变量,但是不能输出数组。
print_r()
//输出一个数组。
var_dump()
//输出一个变量的结构,这个变量包含普通变量,数组,对象等
get_defined_vars(void)
// 此函数返回一个包含当前可用的变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。在函数中使用此函数可以调试函数中的变量,而不会返回其他的变量。
$GLOBALS 变量
// 此函数返回所有的全局变量,当然函数中定义的变量不是全局变量。
get_defined_functions(void)
// 获取所有已经定义的函数,包含内部函数和用户定义的函数。输出用户定义的函数方法为:
// $hhh=get_defined_functions();var_dump($hhh['user']);
get_defined_constants(void)
// 返回所有可用的常量,包含系统常量和用户定义的常量。
get_declared_classes(void)
// 返回所有可用的类,包含系统类和用户定义的类。
get_included_files()
// 返回所有的包含的文件路径的数组,included 和 required 的包含文件
php 断点调试方法 // exit 或 die 输出一个消息并退出程序执行。
PHP 中的许多预定义变量都是“超全局的”,这意味着它们在一个脚本的全部作用域中都可用。在函数或方法中无需执行 global $variable;
就可以访问它们。
$GLOBALS
$_SERVER
$_REQUEST
$_POST
$_GET
$_FILES
$_ENV
$_COOKIE
$_SESSION
$GLOBALS 这种全局变量用于在 PHP 脚本中的任意位置访问全局变量(从函数或方法中均可)。PHP 在名为 $GLOBALS[index] 的数组中存储了所有全局变量。变量的名字就是数组的键。下面的例子展示了如何使用超级全局变量 $GLOBALS:实例
$x = 75;
$y = 25;
function addition() {
$GLOBALS['z'] = $GLOBALS['x'] + $GLOBALS['y'];
}
addition();
echo $z; ?>
运行结果:95
在上面的例子中,由于 z 是 $GLOBALS 数组中的变量,因此在函数之
外也可以访问它。
$_SERVER 这种超全局变量保存关于报头、路径和脚本位置的信息。
下面的例子展示了如何使用 $_SERVER 中的某些元素:
实例
echo $_SERVER['PHP_SELF'];
echo "
";
echo $_SERVER['SERVER_NAME'];
echo "
";
echo $_SERVER['HTTP_HOST'];
echo "
";
echo $_SERVER['HTTP_REFERER'];
echo "
";
echo $_SERVER['HTTP_USER_AGENT'];
echo "
";
echo $_SERVER['SCRIPT_NAME'];?>
运行结果:
/example/php/demo_php_global_server.php
www.0day5.com
www.0day5.com
http://www.0day5.com/tiy/s.asp?f=demo_php_global_server
Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (K
HTML, like Gecko) Chrome/34.0.1847.137 Safari/537.36
/example/php/demo_php_global_server.php
代码审计的目的是以挖掘到可以利用的漏洞,所以我们不必通篇的去将代码完全看懂,但是在开始之前做一些准备还是必须,就像渗透之前,我们也需要收集足够多的目标信息,利用工具和制定渗透计划一样。
通常情况下在刚开始练习审计时,拿到一套源码,马上做的事情就是,丢到工具里,去扫敏感的函数,然后去一个一个的回溯它,找到入口点。但是,这样审计了几套源码,会发现这个方法很浪费时间,因为每次都要在回溯过程中,不断的去寻找源码中定义的一些通用函数。由于不了解整个源码的流程,导致在找这些通用函数的过 程中浪费了很多的时间与精力。
所以,需要重新调整审计流程。在拿到源码之后,先从它开始的地方(一般是根目录下的 index 文件)按照执行的顺序去读代码,一直到它的初始化内容, 和基本功能实现完毕为止。这样,可以明确的了解整套源码的结构,哪一种函数文件放在哪个文件夹下;知道通用函数放在哪个文件中。这对我们在之后阅读“疑 似”有问题的代码时,有很好的帮助,例如,在看到一个通用函数时,我们可以快速的切换到通用函数文件,查找这个函数的实现代码。这个方法带来好处还有好多,这里就不一一列出了。
流程的优化可以帮助我们在之后审计的过程中,免去时间和精力上不必要的浪费。而在深入阅读代码之前,了解整套代码的每一个功能点,每一个输入框和他曾经出现的漏洞及相关修补方案,将会大大提高我们在之后的审计效率。
在了解源码的每个功能时,如果你能够注意以观察 url 的变化,也许能你在后面的阅读带代码过程中跳过很多没用的分支。而在测试每一个输入框时,如果你仔细观察 HTML 源码中输入框的 id或者 name,这也许能帮你在后面的审计过程中更快的定位到利用点。
尝试了解这套源码曾经出现过的漏洞,以及相关的修补方案,这是代码审计中的一条不错的捷径。因为一套源码虽然可能不是一个人完成的,但是它肯定是基于一 个框架的,为这套源码编码的程序员们都会围绕着这个框架进行开发,他们肯定必须要遵守框架的规则,而了解这些曾经出现过的这些漏洞,说不定可以发现他们所 共有的陋习。如果你能够了解这些漏洞修补的详细细节,那就更好了,因为随着 Web平台的升级变迁,或者新的技术出现,这些修补也许就会变成摆设。
有计划地做事,这是一个很好的习惯。计划可以帮助我们明确我们取得了什么样的成果就可以称之为成功,面临什么样的问题才可以称之为失败。这样可以避免我们可能因为某天的情绪不佳而“果断”的放弃,也可以避免我们将时间不断地投向一个不可能完成的任务。
我在代码审计学习过程中,总结有两点是在前期计划必须明确的。
明确找什么样的漏洞,能够方便我们在收集相关资料(如:引发问题的函数字典)时的目标更精准,收集资料更全面。确定整个审计的时间范围,一时间作为审计的量化标准,可以准确的
定位审计是否成功,当然,在不同的情况或者过程中,计划时间是可以调整的。
程序的本质是变量与函数,漏洞所依赖的也无法脱离这两个元素。让我们先来看下漏洞形成的条件
漏洞的利用效果最终也取决与函数的功能。所以我们在下面讲述漏洞挖掘的过程中,也将围绕着这两个元素来展开。
我们提到漏洞形成的两大元素是可控变量,和可控变量能够进入的函数。那么在漏洞挖掘中,我们也不外乎从这两个方向来开始。
所以在一般的人工代码审计过程中,大都会选择查找危险函数,然后根据危险函数中的变量回溯到传入变量的方式。
我们的审计方法也是偏向于通过函数查找变量,虽然这种方式效果很好,但是我们也不应放过变量跟踪。如果你拥有一款不错的变量跟踪自动化工具,那么你很幸运,不用花费很大的精力便可以完成这个任务。如果你是手工审计的话,我建议,你在跟踪函数之前,收集所有可控变量(参数)的“最终形态”(所谓最终形 态,就是用户通过各种方式传入进程序经过各种处理后,等待调用时的形态)。这样可以帮助我们能够在跟踪危险函数时,更快的确定,函数是否能被利用。
很多应用程序的官方都成立了安全部门,或者雇佣安全人员进行代码审计,因此出现了很多自动化商业化的代码审计工具。也就是这样的形势导致了一个局面:大公司的产品安全系数大大的提高,那些很明显的漏洞基本灭绝了,那些大家都知道的审计技术都无用武之地了。
没有绝对安全的代码,我们需要跳出传统的思维,来获得新的漏洞。这也就是所谓的“跳出画来看画”,但是如何跳出来,这是我们当前所要思考的地方。
在学习和练习代码审计的过程中,我们几乎没有发现一款能够进行变量跟踪的自动工具,大多数都是搜索危险函数的工具。传统的代码审计都是基于静态的,而变量跟踪需要动态的实现,这也是导致跟踪变量,工作量大的主要原因.
基于这个问题,目前有个想法,可以在一款代码调试工具中添加特定变量发生改变或进入某些函数之前运行暂停的功能。这样,我们在代码审计的过程中便可以设定我们需要跟踪的可控变量,当其值发生变化时,能够马上了解它的情况。
也可以制作一个脚本,能够罗列出特定变量所必须或者有可能经历的函数。这样我们可以结合危险函数跟踪的结果进行交集的查找,大大的提高了效率和效果。
古语有云:工欲善其事,必先利其器。所以我们接下来要介绍几款代码审计辅助工具给大家,可以让大家适当的减少工作量。
CodeScan:官方网站:www.codescan.com
这个比较老牌了。市面上流出的版本好像就是 1.6 和 1.9 的 crack,商业软件,比较蛋疼。不过 GUI 界面操作起来很方便。这里也不多说什么,主要是 Include 的提示设置,装过软件自己看看就知道了。
RIPS:官方网站:rips-scanner.sourceforge.net
PHP 写的,需要环境,直接解压到 wwwroot
就好了。不适合扫描整个文件夹项目,或者要修改 PHP 配置,把代码执行超时的时间设置大一点。
RIPS 对代码进行静态漏洞扫描的基本思想有两条:
mysql_query()
)RIPS 认为,所有的注入漏洞最终都要经过一些特定的数据库操作函数,mysql_query()
或程序自定义的类函数,这些函数是产生漏洞的导火索,只要对这些函数的控制流和参数流进行回溯扫描,就可以发($_GET,$_POST,$_COOKIE)
“用户输入的一切数据都有害”,大部分的注入漏洞,包括二次注入,究其原因都是因为对用户的输入数据没有做好过滤,RIPS 对这些敏感数据进行跟踪,并判断其在进入敏感函(mysql_query())之前有没有对其进行有效处理(addslashes()
)来判断这条数据流是否存在漏洞。动态扫描加上静态定位,最终使我们能更容易的发现一些漏洞并及时使其得到修补。PHPXref:官方网站:phpxref.com
严格的说 PHPxref 也是做开发的好帮手,它能将某一个程序(如Wordpress)中所有的函数、变量、常量等分类记录,生成一个 HTML 网页列表,你可以轻松地在这个列表中找到某个函数在什么位置被定义,在什么位置被引用。所以说非常适合大型项目。同时还是最主要的,It’s free。
Seay 源代码审计系统:官方网站:www.cnseay.com
这是一款结合白盒跟黑盒的半自动化国产代码安全审计系统。该版本只支持 PHP,近期会加上 ASPX、ASP、JSP 的代码审计功能,并且实现 4 套规则的配置,另外还会加上自定义审计的扩展名,方便灵活审计不同脚本代码。
开源的代码审计工具
https://github.com/wufeifei/cobra/