作者:nannet
信息来源:中国黑客联盟(www.chinawll.com) 今天我的决定和大家探讨一些我的一些在PHP代码审计方面一些脆弱性的方法的经验。 如果你记得,大约一年前,我写过这篇文章。 PHP代码审计的20种方法。 一段时间以前,“Stefan Esser” 做了一个关于PHP安全性的模板,我将以我的经验给大家简短的概括一下,PHP代码审计: 大多数PHP代码的漏洞: 1.跨站式脚本 2.跨站请求伪造 3.SQL注入 4.不安全的会话处理 5.session劫持 6.信息公开 7.头注入漏洞 8.不安全的配置 9.脆弱的随机性 想要知道更多如何从你的源码中找到问题的所在的信息,请阅读我的文章 http://www.abysssec.com/blog/2009/03/php_fuzz_audit/ 或者另一个描述(发现PHP注入漏洞) http://www.milw0rm.com/papers/381 这些问题由于代码编写的不正确 1--不安全的会话处理 接受用户一些没有被注入的输入 2--检测 检测功能可以用作是“修补”用户的输入,关于请求的严格性(比如:规定的数据类型,最大长度)来代替拒绝潜在危险的完整输入,总的来说检测功能的使用并不被支持,因为确定的项和结构体的安全过滤,很可能有他们的自己的安全隐患。另外由于打印错误的自动更正可能会使得输入在语法上或者语义上报错。举个例子: is_numeric()Checks a variable for numeric content检测一个数字型的变量 is_array()Checks if a variable is an array检测一个是否为数组的变量 strlen()Returns a string‘s length. 返回字符长度 strip_tags()Removes HTML and PHP tags. 3--溢出 这里有一些不同种类的溢出方式: 1.反斜杠前缀“\”定义了一些字符串不超过这个字符,举个例子:\t是一个tab空间,\n是一个另启一行的字符串,这个可以作为这个函数的特殊的功能,在对于另起一行的字符串有特殊的目的。比如header()。在这些规范的表达之内,反斜杠时通常被用作去溢出这些有特殊目的的字符,比如像\. or \*, 这些是和所有函数处理规范的表达所相关的。 2.HTML 加密翻译字符时通常被网页浏览器译成HTML他们的加密等价的。– e.g. < is < or < or < and > is > or > or >. HTML加密应该被用作是输出处理,而用户的输入应该被反映进HTML里面,同时没有注入代码。 3.URL 加密确保,每个字符没有被允许在URL内。根据RFC 1738,被适当的译成E.g. space converts to + or %20 and < is %3C. 这个溢出和函数处理URL是相关的。例如urlencode()和urldecode()。 4--配置 程序错误,包括逻辑程序。 好,我们知道有四点在过程中可以帮助我们。 1.我们PHP输入点: 我们需要发现他们和所有的函数功能和变量,而这些必须指定分配给他们 在PHP中的输入点是: 程序是: $_SERVER $_GET $_POST $_COOKIE $_REQUEST $_FILES $_ENV $_HTTP_COOKIE_VARS $_HTTP_ENV_VARS $_HTTP_GET_VARS $_HTTP_POST_FILES $_HTTP_POST_VARS $_HTTP_SERVER_VARS 2限制我们的理解 很好,第二点,我们的问题也就从这里开始,就像以前一样,我们不能从代码中发现一些错误。因为,程序设计者用了一些限制功能。举个例子,无论在哪里,我们可以看见接下来的函数可以控制输入变量,很可能当许多攻击的时候被执行。所有你只有两种方法:一你从你的逻辑代码中找出问题,二你从你的PHP代码中找出PHP错误。 A. 溢出和加密功能 A-1 (XSS dies = 90% 直接的翻译是一个梦想) : htmlspecialchars() ,溢出字符& < and > as HTML , 为了保护应用程序遭到安全漏洞XSS。正确的字符和模型 ENT_QUOTES 应该被使用。 1 <?php 2 echo "Hello " . htmlspecialchars( $_GET['name'], ENT_QUOTES); 3 ?> htmlentities(), 对所有的应用字符采用HTML实体加密程序来保护应用程序遭到安全漏洞XSS。正确的字符和模型 ENT_QUOTES 应该被使用。 1 <?php 2 echo "Hello " . htmlentities( $_GET['name'], ENT_QUOTES); 3 ?> ( htmlentities() 在特殊的事件中绕回 [utf7] : http://pstgroup.blogspot.com/2007/11/bypass-htmlentities.html ) urlencode() , 采用URL加密被看做是URL的查询部分。 1 <?php 2 $url = "http://www.example.com/" . "index.php?param=" . urlencode($_GET['pa']); 3 ?> A-2 : (SQL injection dies = 90% The direct transition is a dream) : addslashes() : 采用一个简单的反斜杠溢出,输入字符串被假设为单字节的密码。addslashes() 不能被用作是保护程序对抗SQL注入,直到所有的数据库系统运行了多字节的加密字符串。比如UTF-8。 addcslashes() : 采用一个简单的反斜杠溢出,这是可以被用来是字符串被用作在JavaScript 字符串。然而用这种函数来保护程序来对抗 HTML tag 注入也是没有可能性的。 (bypass addslashes() in special case : http://sirdarckcat.blogspot.com/2009/10/couple-of-unicode-issues-on-php-and.html) mysql_real_escape_string(), 溢出一个字符串用在mysql_query(),在当MySQL连接当中的字符是要被考虑到的,所以在多字节加密字符串上运行是很安全的。应用程序工具字符串溢出被用作抵抗SQL注入攻击是可以用这种函数的。 1 <?php 2 $sql = "SELECT * FROM user WHERE" . " login='" . mysql_real_escape_string( $_GET['login'], $db) . "'"; 3 ?> A-3 : (XSS , SQl Inject = 100% The direct transition is a dream) : preg_quote() , 应该被用作溢出用户输入被插入正规的表达,这种方法,正规的表达被很好的从语义操作上保护了。 1 <?php 2 $repl = preg_replace('/^' . preg_quote($_GET['part'], '/'). '-[0-9]{1,4}/', '', $str); 3 ?> 有问题的代码: 1 <?php 2 $h = $_GET['h']; 3 echo preg_replace("/test/e",$h,"jutst test"); 4 ?> It works like this: http://site.com/test.php?h=phpinfo() B- CType Extension : ? ctype_alnum()字母数字字符 – A-Z, a-z, 0-9 ? ctype_alpha()字母字符– A-Z, a-z ? ctype_cntrl() 控制字符– e.g. tab, line feed ? ctype_digit()数字字符 – 0-9 ? ctype_graph()字符创造一个可见的输出 e.g. no whitespace ? ctype_lower()小写字符 – a-z ? ctype_print()打印的字符 ? ctype_punct()标点字符 – printable characters, but not digits, letters or whitespace, e.g. .,!?:;*&$ ? ctype_space()白色空间字符 – e.g. newline, tab ? ctype_upper()大写字符 – A-Z ? ctype_xdigit() 十进制和十六进制 – 0-9, a-f, A-F 1 <?php 2 if (!ctype_print($_GET['var'])) { 3 die("User input contains ". "non-printable characters"); 4 } 5 ?> C – Filter Extension – ext/filter 从PHP 5.2.0 开始,过滤伸展提供了一个简单的API输入确证和输入过滤。 filter_input(): 调回GET, POST, COOKIE, ENV or SERVER 这些变量的内容,采用特殊的过滤。 1 <?php 2 $url = filter_input(INPUT_GET, 'url', FILTER_URL); 3 ?> filter_var(): 过滤一个变量用特殊过滤。 1 <?php 2 $url = filter_var($var, FILTER_URL); 3 ?> 所有过滤的的列表: ? FILTER_VALIDATE_INTChecks 无论输入是一个整数类型 ? FILTER_VALIDATE_BOOLEANChecks 无论输入是一个boolen类型 ? FILTER_VALIDATE_FLOATChecks 无论输入是一个浮点数 ? FILTER_VALIDATE_REGEXPChecks 输入不符合规定的输入法 ? FILTER_VALIDATE_URLChecks 无论输入是一个URL ? FILTER_VALIDATE_EMAILChecks 输入是一个email地址 ? FILTER_VALIDATE_IPChecks 无论输入是一个IPv4 or IPv6. Sanitising Filters ? FILTER_SANITIZE_STRING / FILTER_SANITIZE_STRIPPEDStrips and HTML-encodes characters according to flags and applies strip_tags(). ? FILTER_SANITIZE_ENCODEDApplies URL encoding. ? FILTER_SANITIZE_SPECIAL_CHARSEncodes ‘ ” < > & \0 and optionally all characters > chr(127) into numeric HTML entities. ? FILTER_SANITIZE_EMAILRemoves 所有的字符不能被用在email地址 ? FILTER_SANITIZE_URLRemoves 所有的地址不能被用在URL ? FILTER_SANITIZE_NUMBER_INTRemoves all characters except digits and + -. ? FILTER_SANITIZE_NUMBER_FLOATRemoves all characters not allowed in floating point numbers. ? FILTER_SANITIZE_MAGIC_QUOTESApplies addslashes(). Other Filters ? FILTER_UNSAFE_RAWIs a dummy filter. ? FILTER_CALLBACKCalls a userspace callback function defining the filter. D) HTTP Header Output HTTP 头文件可以被用在header() 函数中,用户输入在被送入header() 总是被先检查,否则会遇到代码安全性的问题。另起一行的字符不能被用在header() 中以至于防止HTTP头注入。被注入的头文件也可以被用作XSS和HTTP攻击。总的来说,用户输入必须被处理在一个区分上下文的方式当中。 1 <?php 2 if (strpbrk($_GET['type'], ";/\r\n")) die('invalid characters'); 3 header("Content-Type: text/" . $_GET['type'] . "; charset=utf-8;"); 4 ?> E)Secure File Handling: 查找和代替NULL字节 1 <?php 2 if (strpos($_GET["f"], "\0") === true) { 3 $file = str_replace("\0", "", $_GET["f"]); 4 } 5 ?> 防止远程文件 1 <?php 2 $file = "./".basename($_GET["f"]). ".php"; 3 ?> 1 <?php 2 if (in_array($_GET['action'], array('index', 'logout'))) { 3 include './'.$_GET['action'] . '.php'; 4 } else die('action not permitted'); 5 ?> 3)配置点 : 最后一点,在程序结构中的漏洞,源代码审计中最巧妙地一部分。 我们看到接下来的这些配置 源代码或者是PHP 【a】当服务器远程URL对这些文件进行处理功能 文件处理功能就像打开文件包括接受URL类似于文件参数。举个例子(fopen(‘http://www.example.com/’, ‘r’)) 尽管他们可以像HTTP URLs,这样就产生了一个巨大的安全危险,如果用户输入没有正确的语义,就相当于在服务器上对远程代码开启了一扇门。 Register Globals is ‘ON’ : 在PHP4.2.0通常用来输入值作为全局变量,这样就在注册的全局上被命名,这就在很多的网络的应用程序上对安全性负责任,因为这使得攻击者在很多情况下可以很轻松的控制全局变量,但是幸运的是从PHP4.2.0开始就不能了,因为在很多时候这是非常危险的。 1 <?php 2 if (ereg("test.php", $PHP_SELF)==true) 3 { 4 include $server_inc."/step_one_tables.php"; 5 } 6 ?> 真实的例子: Exp 1 : PHP Code Execution: <?php // Replace any usernames $ret = preg_replace("#\[:nom[^\]]*)\]#e", "username(0, trim(\"\\1\"))", $ret); ?> php code execution is possible via complex variable evaluation. [:nom:{${phpinfo()}}] or this code <?php if($globals['bbc_email']){ $text = preg_replace( array("/\[email=(.*?)\](.*?)\[\/email\]/ies", "/\[email\](.*?)\[\/email\]/ies"), array('check_email("$1", "$2")', 'check_email("$1", "$1")'), $text); } ?> 2-配置错误:审计迂回 <?php list($user,$hash) = unserialize(stripslashes($_val)); $user = trim(genc('get',$user)); $req = "SELECT user_nickname, user_password FROM {$jamroom_db['user']} WHERE user_nickname = '". dbEscapeString($user) ."' LIMIT 1"; $_rt = dbQuery($req,'SINGLE'); if (strlen($_rt['user_password']) === 0) { return(false); } if (md5($_rt['user_password'] . $sect) == $hash) { print_r($rt); return($_rt); } ?> 上面代码的问题是 $_val 是一个用户的值从$_COOKIE['JMU_Cookie']调用. <?php $data = array(); $user = 'admin'; // Target $data[0] = base64_encode(serialize($user)); $data[1] = (bool)0; echo "\n\n===[ 0 ] ========================\n\n"; echo 'Cookie: JMU_Cookie=' . urlencode(serialize($data)); $data[1] = (bool)1; echo "\n\n===[ 1 ] ========================\n\n"; echo 'Cookie: JMU_Cookie=' . urlencode(serialize($data)); ?> 上面的脚本是一个如何工作的例子,他将会创造一个cookis当做一个用户名登陆,对于更多的信息检查将会对比PHP ,特殊的验证。 3- new bug : http://www.sektioneins.com/en/advisories/advisory-022009-phpids-unserialize-vulnerability/index.html 另外,我还会出版一些 最近研究的关于浏览器的安全性。 本文摘自: 中国黑客联盟(http://www.chinawll.com/) 详细出处请参考:http://www.chinawll.com/forum.php?mod=viewthread&tid=1576&extra= |