常规
有多种减轻威胁的技巧:
[1] 如果可能,使用库调用而不是外部进程来重新创建所需功能。
[2] 策略:沙箱或监狱
在进程和操作系统之间强制实施严格边界的“监狱”或类似沙箱环境中运行代码。这可以有效限制您的软件可访问特定目录中的哪些文件或者可以执行哪些命令。操作系统级别的示例包括 Unix chroot jail、AppArmor 和 SELinux。通常,受管代码可提供一定的保护。例如,Java SecurityManager 中的 java.io.FilePermission 允许您指定针对文件操作的限制。但是,此解决方案可能很难实施,并且它仅限制了对操作系统的影响;您的应用程序的其余部分仍有可能受到损害。请注意避免 CWE-243 以及与监狱相关的其他弱点。
[3] 策略:库或框架
使用禁止此弱点出现的经过审核的库或框架,或至少提供更容易避免此弱点的构造。
例如,考虑使用 ESAPI 编码控件
http://www.owasp.org/index.php/ESAPI
或类似的工具、库或框架。这些有助于对输出编码。
[4] 策略:输入验证
假定所有输入都是恶意的。使用“接受已知善意”输入验证策略,即使用严格遵守规范的可接受输入的白名单。拒绝没有严格遵守规范的任何输入,或者将其变换为严格遵守规范的内容。不要专门依赖于查找恶意或格式错误的输入(即,不依赖于黑名单)。但是,黑名单可帮助检测潜在攻击,或者确定哪些输入由于格式严重错误而应直接拒绝。执行输入验证时,请考虑所有潜在相关的属性,包括长度、输入类型、可接受的值的完整范围、缺少或多余的输入、语法、在相关字段之间是否一致以及是否遵守了业务规则。作为业务规则逻辑的示例,“boat”可能在语法上有效(因为它仅包含字母数字字符),但如果预期为颜色(如“red”或“blue”),那么它就无效。构造操作系统命令字符串时,请使用严格的白名单以根据请求中参数的预期值来限制字符集。这将间接限制攻击的范围,但是此技巧的重要性不及适当的输出编码和转义。
请注意,适当的输出编码、转义和引用是防止操作系统命令注入的最有效解决方案,虽然输入验证可能会提供一定的深度防御。这是因为它会有效限制将在输出中出现的内容。输入验证并不总是能够防止操作系统命令注入,尤其是在您需要支持可包含任意字符的自由格式文本字段的情况下。例如,调用邮件程序时,您可能需要允许主题字段包含在其他情况下很危险的输入(如“;”和“>”字符),这些输入需要转义或以其他方式进行处理。在此情况下,除去该字符可能会降低操作系统命令注入的风险,但是这会产生不正确的行为,因为这样就不会按照用户的需要来记录主题字段。这可能看起来只是略有不便,但在程序依赖于结构良好的主题行以便向其他组件传递消息时,这种情况就更为重要。
这可能看起来只是略有不便,但在需要表示不等式的数学论坛中,这种情况就更为重要。即使在验证中出错(例如,在 100 个输入字段中忘记一个字段),相应的编码仍有可能针对基于注入的攻击为您提供防护。只要输入验证不是孤立完成的,便仍是有用的技巧,因为它可以大大减少攻击出现的机会,使您能够检测某些攻击,并提供正确编码所无法解决的其他安全性优势。[5] 策略:环境固化
使用完成必要任务所需的最低特权来运行代码。
https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/principles/351.html
. 如果可能,请使用仅用于单个任务的有限特权来创建孤立的帐户。这样,即使攻击成功,攻击者也无法立即访问软件或其环境的其余部分。例如,数据库应用程序很少需要以数据库管理员身份运行,特别是在日常操作中。
ASP.NET 提供多种方法来在打开文件之前对文件名进行验证。例如:[1] Server.MapPath() 方法:该方法接收路径(字符串),并将指定的相对或虚拟路径映射到服务器上对应的物理目录。
注意:AspEnableParentPaths 属性(Metabase 属性)的缺省值为 FALSE,以确保脚本将不具有对应用程序根目录以外的文件的访问权。将属性值更改为 TRUE 会构成潜在的安全风险。
[2] Path.GetFileName() 方法:该方法有效除去给定文件路径的最后一个元素,同时会返回由文件路径中直到(但不包括)最后一个分隔符字符的所有字符组成的字符串。
注意:如果该方法的参数包含诸如以下类型的“InvalidPathChars”,那么“ArgumentException”将会提高:
a. 引号(")
b. 小于(<)
c. 大于(>)
d. 竖线(|)
e. 退格(\b)
f. 空值(\0)
g. Unicode 字符 16 至 18、20 至 25。
出于安全考虑,我们建议在对文件名进行任何输入验证前使用 Server.MapPath() 方法。
** 文件路径验证:
有多种从“Servlet 容器”访问文件系统的方法。然而,由于这些方法中的一部分支持对 web 根以外的文件进行访问,所以很危险。有两种安全的 Servlet API 方法可访问在服务器的文档树中给定其虚拟路径的 web 资源。有两种安全的 Servlet API 方法可访问在服务器的文档树中给定其虚拟路径的 web 资源。当给定的文件名解析为 web 根以外的值时,这些 API 会返回空值。以下 API 应用于访问存储在 web 根下的配置文件或其他文件:
[1] ServletContext.getResource(或 ServletContext.getResourceAsStream)
[2] ServletContext.getRealPath
[1] ServletContext.getResource(或 ServletContext.getResourceAsStream)
ServletContext.getResource 或 ServletContext.getResourceAsStream 都可用于访问位于虚拟路径(相对于服务器的 web 根而言)的资源。该资源可能位于数据库或 .war 文件中的本地或远程文件系统上。
ServletContext.getResource 将 URL 返回给映射到指定路径的资源。如果没有映射到指定路径的资源,那么会返回空值。如果指定路径的格式不正确,那么会抛出 MalFormedURLException。
ServletContext.getResourceAsStream 将 InputStream 对象返回映射到指定路径的资源。如果没有映射到指定路径的有效资源,那么会返回空值。
装入 Servlet 配置文件(名称为 servlet.xml,且驻留于应用程序的 WEB-INF 目录中)的示例:
// Example to load the /WEB-INF/servlet.xml configuration file URL url = getServletContext().getResource("/WEB-INF/servlet.xml"); // Acquire an input stream to the config resource InputStream configInput = url.openStream(); ...
还可以使用 ServletContext.getResourceAsStream 方法直接获取配置文件的输入流,如以下示例所示:
// Example to acquire an input stream to a resource InputStream configInput = getServletContext().getResourceAsStream("/WEB-INF/servlet.xml"); ...
[2] ServletContext.getRealPath
该方法返回给定其虚拟路径(相对于服务器的 web 根而言)的资源的真实路径。返回的真实路径格式将适合于 Servlet 容器所运行的计算机和操作系统,包括适当的路径分隔符。
该方法不如 ServletContext.getResource(或 ServletContext.getResourceAsStream)常用,因为它不允许访问未存储在本地文件中的资源。如果 Servlet 容器因为任何原因(例如,当内容从 .war 归档中可用时)无法将虚拟路径转换为真实路径,那么它会返回空值。
该方法返回给定其虚拟路径(相对于服务器的 web 根而言)的资源的真实路径。 该方法可能没有在所有 Servlet 引擎中一致或正确实现。始终验证返回的路径,如以下示例所示:
// Example to access a resource using ServletContext.getRealPath // Get the virtual path parameter from the http request String virtualPath = request.getParameter("virtual_path"); String realPath = getServletContext().getRealPath(virtualPath); if (realPath != null) { // verify that realPath is valid File file = new File(realPath); if (!file.exists()) { // oops, invalid file path ... } }
推荐使用的 JAVA 工具
不适用。
参考资料
http://java.sun.com/products/servlet/2.2/javadoc/javax/servlet/ServletContext.html#getResource(java.lang.String) http://java.sun.com/products/servlet/2.2/javadoc/javax/servlet/ServletContext.html#getResourceAsStream(java.lang.String) http://java.sun.com/products/servlet/2.2/javadoc/javax/servlet/ServletContext.html#getRealPath(java.lang.String)
** 输入数据验证:** 输入数据验证:虽然为方便用户而在客户端层上提供数据验证,但仍必须使用 Servlet 在服务器层上执行数据验证。客户端验证本身就不安全,因为这些验证可轻易绕过,例如,通过禁用 Javascript。
一份好的设计通常需要 Web 应用程序框架,以提供服务器端实用程序例程,从而验证以下内容:
[1] 必需字段
[2] 字段数据类型(缺省情况下,所有 HTTP 请求参数都是“字符串”)
[3] 字段长度
[4] 字段范围
[5] 字段选项
[6] 字段模式
[7] cookie 值
[8] HTTP 响应好的做法是将以上例程作为“验证器”实用程序类中的静态方法实现。以下部分描述验证器类的一个示例。
[1] 必需字段“始终”检查字段不为空,并且其长度要大于零,不包括行距和后面的空格。如何验证必需字段的示例:
// Java example to validate required fields public Class Validator { ... public static boolean validateRequired(String value) { boolean isFieldValid = false; if (value != null && value.trim().length() > 0) { isFieldValid = true; } return isFieldValid; } ... } ... String fieldValue = request.getParameter("fieldName"); if (Validator.validateRequired(fieldValue)) { // fieldValue is valid, continue processing request ... }
[2] 输入的 Web 应用程序中的字段数据类型和输入参数欠佳。例如,所有 HTTP 请求参数或 cookie 值的类型都是“字符串”。开发者负责验证输入的数据类型是否正确。使用 Java 基本包装程序类,来检查是否可将字段值安全地转换为所需的基本数据类型。
验证数字字段(int 类型)的方式的示例:
// Java example to validate that a field is an int number public Class Validator { ... public static boolean validateInt(String value) { boolean isFieldValid = false; try { Integer.parseInt(value); isFieldValid = true; } catch (Exception e) { isFieldValid = false; } return isFieldValid; } ... } ... // check if the HTTP request parameter is of type int String fieldValue = request.getParameter("fieldName"); if (Validator.validateInt(fieldValue)) { // fieldValue is valid, continue processing request ... }
好的做法是将所有 HTTP 请求参数转换为其各自的数据类型。例如,将请求参数的“integerValue”存储在请求属性中,并按以下示例所示来使用:
// Example to convert the HTTP request parameter to a primitive wrapper data type // and store this value in a request attribute for further processing String fieldValue = request.getParameter("fieldName"); if (Validator.validateInt(fieldValue)) { // convert fieldValue to an Integer Integer integerValue = Integer.getInteger(fieldValue); // store integerValue in a request attribute request.setAttribute("fieldName", integerValue); } ... // Use the request attribute for further processing Integer integerValue = (Integer)request.getAttribute("fieldName"); ...
应用程序应处理的主要 Java 数据类型:
- Byte
- Short
- Integer
- Long
- Float
- Double
- Date
[3] 字段长度“始终”确保输入参数(HTTP 请求参数或 cookie 值)有最小长度和/或最大长度的限制。以下示例验证 userName 字段的长度是否在 8 至 20 个字符之间:
// Example to validate the field length public Class Validator { ... public static boolean validateLength(String value, int minLength, int maxLength) { String validatedValue = value; if (!validateRequired(value)) { validatedValue = ""; } return (validatedValue.length() >= minLength && validatedValue.length() <= maxLength); } ... } ... String userName = request.getParameter("userName"); if (Validator.validateRequired(userName)) { if (Validator.validateLength(userName, 8, 20)) { // userName is valid, continue further processing ... } }
[4] 字段范围
始终确保输入参数是在由功能需求定义的范围内。
以下示例验证输入 numberOfChoices 是否在 10 至 20 之间:
// Example to validate the field range public Class Validator { ... public static boolean validateRange(int value, int min, int max) { return (value >= min && value <= max); } ... } ... String fieldValue = request.getParameter("numberOfChoices"); if (Validator.validateRequired(fieldValue)) { if (Validator.validateInt(fieldValue)) { int numberOfChoices = Integer.parseInt(fieldValue); if (Validator.validateRange(numberOfChoices, 10, 20)) { // numberOfChoices is valid, continue processing request ... } } }
[5] 字段选项 Web 应用程序通常会为用户显示一组可供选择的选项(例如,使用 SELECT HTML 标记),但不能执行服务器端验证以确保选定的值是其中一个允许的选项。请记住,恶意用户能够轻易修改任何选项值。始终针对由功能需求定义的受允许的选项来验证选定的用户值。以下示例验证用户针对允许的选项列表进行的选择:
// Example to validate user selection against a list of options public Class Validator { ... public static boolean validateOption(Object[] options, Object value) { boolean isValidValue = false; try { List list = Arrays.asList(options); if (list != null) { isValidValue = list.contains(value); } } catch (Exception e) { } return isValidValue; } ... } ... // Allowed options String[] options = {"option1", "option2", "option3"); // Verify that the user selection is one of the allowed options String userSelection = request.getParameter("userSelection"); if (Validator.validateOption(options, userSelection)) { // valid user selection, continue processing request ... }
[6] 字段模式
始终检查用户输入与由功能需求定义的模式是否匹配。例如,如果 userName 字段应仅允许字母数字字符,且不区分大小写,那么请使用以下正则表达式:^[a-zA-Z0-9]*$
Java 1.3 或更早的版本不包含任何正则表达式包。建议将“Apache 正则表达式包”(请参阅以下“资源”)与 Java 1.3 一起使用,以解决该缺乏支持的问题。执行正则表达式验证的示例:
// Example to validate that a given value matches a specified pattern // using the Apache regular expression package import org.apache.regexp.RE; import org.apache.regexp.RESyntaxException; public Class Validator { ... public static boolean matchPattern(String value, String expression) { boolean match = false; if (validateRequired(expression)) { RE r = new RE(expression); match = r.match(value); } return match; } ... } ... // Verify that the userName request parameter is alpha-numeric String userName = request.getParameter("userName"); if (Validator.matchPattern(userName, "^[a-zA-Z0-9]*$")) { // userName is valid, continue processing request ... }
Java 1.4 引进了一种新的正则表达式包 (java.util.regex)。以下是使用新的 Java 1.4 正则表达式包的 Validator.matchPattern 修订版:
// Example to validate that a given value matches a specified pattern // using the Java 1.4 regular expression package import java.util.regex.Pattern; import java.util.regexe.Matcher; public Class Validator { ... public static boolean matchPattern(String value, String expression) { boolean match = false; if (validateRequired(expression)) { match = Pattern.matches(expression, value); } return match; } ... }
[7] cookie 值使用 javax.servlet.http.Cookie 对象来验证 cookie 值。适用于 cookie 值的相同的验证规则(如上所述)取决于应用程序需求(如验证必需值、验证长度等)。验证必需 cookie 值的示例:
// Example to validate a required cookie value // First retrieve all available cookies submitted in the HTTP request Cookie[] cookies = request.getCookies(); if (cookies != null) { // find the "user" cookie for (int i=0; i<cookies.length; ++i) { if (cookies[i].getName().equals("user")) { // validate the cookie value if (Validator.validateRequired(cookies[i].getValue()) { // valid cookie value, continue processing request ... } } } }
[8] HTTP 响应
[8-1] 过滤用户输入要保护应用程序免遭跨站点脚本编制的攻击,请通过将敏感字符转换为其对应的字符实体来清理 HTML。HTML 敏感字符包括:
< > " ' % ; ) ( & +
以下示例通过将敏感字符转换为其对应的字符实体来过滤指定字符串:
// Example to filter sensitive data to prevent cross-site scripting public Class Validator { ... public static String filter(String value) { if (value == null) { return null; } StringBuffer result = new StringBuffer(value.length()); for (int i=0; i<value.length(); ++i) { switch (value.charAt(i)) { case '<': result.append("<"); break; case '>': result.append(">"); break; case '"': result.append("""); break; case '\'': result.append("'"); break; case '%': result.append("%"); break; case ';': result.append(";"); break; case '(': result.append("("); break; case ')': result.append(")"); break; case '&': result.append("&"); break; case '+': result.append("+"); break; default: result.append(value.charAt(i)); break; } return result; } ... } ... // Filter the HTTP response using Validator.filter PrintWriter out = response.getWriter(); // set output response out.write(Validator.filter(response)); out.close();
Java Servlet API 2.3 引进了“过滤器”,它支持拦截和转换 HTTP 请求或响应。
以下示例使用 Validator.filter 来用“Servlet 过滤器”清理响应:
// Example to filter all sensitive characters in the HTTP response using a Java Filter. // This example is for illustration purposes since it will filter all content in the response, including HTML tags! public class SensitiveCharsFilter implements Filter { ... public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { PrintWriter out = response.getWriter(); ResponseWrapper wrapper = new ResponseWrapper((HttpServletResponse)response); chain.doFilter(request, wrapper); CharArrayWriter caw = new CharArrayWriter(); caw.write(Validator.filter(wrapper.toString())); response.setContentType("text/html"); response.setContentLength(caw.toString().length()); out.write(caw.toString()); out.close(); } ... public class CharResponseWrapper extends HttpServletResponseWrapper { private CharArrayWriter output; public String toString() { return output.toString(); } public CharResponseWrapper(HttpServletResponse response){ super(response); output = new CharArrayWriter(); } public PrintWriter getWriter(){ return new PrintWriter(output); } } } }
[8-2] 保护 cookie
在 cookie 中存储敏感数据时,确保使用 Cookie.setSecure(布尔标志)在 HTTP 响应中设置 cookie 的安全标志,以指导浏览器使用安全协议(如 HTTPS 或 SSL)发送 cookie。
保护“用户”cookie 的示例:
// Example to secure a cookie, i.e. instruct the browser to // send the cookie using a secure protocol Cookie cookie = new Cookie("user", "sensitive"); cookie.setSecure(true); response.addCookie(cookie);
推荐使用的 JAVA 工具用于服务器端验证的两个主要 Java 框架是:
[1] Jakarta Commons Validator(与 Struts 1.1 集成)Jakarta Commons Validator 是 Java 框架,定义如上所述的错误处理机制。Jakarta Commons Validator 是一种强大的框架,用来实现所有以上数据验证需求。这些规则配置在定义表单字段的输入验证规则的 XML 文件中。在缺省情况下,Struts 支持在使用 Struts“bean:write”标记撰写的所有数据上,过滤 [8] HTTP 响应中输出的危险字符。可通过设置“filter=false”标志来禁用该过滤。
Struts 定义以下基本输入验证器,但也可定义定制的验证器:
required:如果字段包含空格以外的任何字符,便告成功。
mask:如果值与掩码属性给定的正则表达式相匹配,便告成功。
range:如果值在 min 和 max 属性给定的值的范围内((value >= min) & (value <= max)),便告成功。
maxLength:如果字段长度小于或等于 max 属性,便告成功。
minLength:如果字段长度大于或等于 min 属性,便告成功。
byte、short、integer、long、float、double:如果可将值转换为对应的基本类型,便告成功。
date:如果值代表有效日期,便告成功。可能会提供日期模式。
creditCard:如果值可以是有效的信用卡号码,便告成功。
e-mail:如果值可以是有效的电子邮件地址,便告成功。
使用“Struts 验证器”来验证 loginForm 的 userName 字段的示例:
<form-validation> <global> ... <validator name="required" classname="org.apache.struts.validator.FieldChecks" method="validateRequired" msg="errors.required"> </validator> <validator name="mask" classname="org.apache.struts.validator.FieldChecks" method="validateMask" msg="errors.invalid"> </validator> ... </global> <formset> <form name="loginForm"> <!-- userName is required and is alpha-numeric case insensitive --> <field property="userName" depends="required,mask"> <!-- message resource key to display if validation fails --> <msg name="mask" key="login.userName.maskmsg"/> <arg0 key="login.userName.displayname"/> <var> <var-name>mask</var-name> <var-value>^[a-zA-Z0-9]*$</var-value> </var> </field> ... </form> ... </formset> </form-validation>
[2] JavaServer Faces 技术
“JavaServer Faces 技术”是一组代表 UI 组件、管理组件状态、处理事件和验证输入的 Java API(JSR 127)。
JavaServer Faces API 实现以下基本验证器,但可定义定制的验证器:
validate_doublerange:在组件上注册 DoubleRangeValidator。
validate_length:在组件上注册 LengthValidator。
validate_longrange:在组件上注册 LongRangeValidator。
validate_required:在组件上注册 RequiredValidator。
validate_stringrange:在组件上注册 StringRangeValidator。
validator:在组件上注册定制的 Validator。
JavaServer Faces API 定义以下 UIInput 和 UIOutput 处理器(标记):
input_date:接受以 java.text.Date 实例格式化的 java.util.Date。
output_date:显示以 java.text.Date 实例格式化的 java.util.Date。
input_datetime:接受以 java.text.DateTime 实例格式化的 java.util.Date。
output_datetime:显示以 java.text.DateTime 实例格式化的 java.util.Date。
input_number:显示以 java.text.NumberFormat 格式化的数字数据类型(java.lang.Number 或基本类型)。
output_number:显示以 java.text.NumberFormat 格式化的数字数据类型(java.lang.Number 或基本类型)。
input_text:接受单行文本字符串。
output_text:显示单行文本字符串。
input_time:接受以 java.text.DateFormat 时间实例格式化的 java.util.Date。
output_time:显示以 java.text.DateFormat 时间实例格式化的 java.util.Date。
input_hidden:允许页面作者在页面中包括隐藏变量。
input_secret:接受不含空格的单行文本,并在输入时,将其显示为一组星号。
input_textarea:接受多行文本。
output_errors:显示整个页面的错误消息,或与指定的客户端标识相关联的错误消息。
output_label:将嵌套的组件显示为指定输入字段的标签。
output_message:显示本地化消息。
使用 JavaServer Faces 来验证 loginForm 的 userName 字段的示例:
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> ... <jsp:useBean id="UserBean" class="myApplication.UserBean" scope="session" /> <f:use_faces> <h:form formName="loginForm" > <h:input_text id="userName" size="20" modelReference="UserBean.userName"> <f:validate_required/> <f:validate_length minimum="8" maximum="20"/> </h:input_text> <!-- display errors if present --> <h:output_errors id="loginErrors" clientId="userName"/> <h:command_button id="submit" label="Submit" commandName="submit" /><p> </h:form> </f:use_faces>
引用
Java API 1.3 -
http://java.sun.com/j2se/1.3/docs/api/
Java API 1.4 -
http://java.sun.com/j2se/1.4/docs/api/
Java Servlet API 2.3 -
http://java.sun.com/products/servlet/2.3/javadoc/
Java 正则表达式包 -
http://jakarta.apache.org/regexp/
Jakarta 验证器 -
http://jakarta.apache.org/commons/validator/
JavaServer Faces 技术 -
http://java.sun.com/j2ee/javaserverfaces/
** 文件路径验证:
PHP 提供多种方法来在打开文件之前对文件名进行验证。例如:例如:[1] file_exists() 方法:该方法检查是否存在文件或目录。如果文件或目录存在,那么该方法将会返回 True,否则将返回 false。
<?php $filename = '/path/to/foo.txt'; if (file_exists($filename)) { echo "The file $filename exists"; } else { echo "The file $filename does not exist"; } ?>
[2] is_file() 方法:该方法告知文件名是否是常规文件,如果是,便会返回 True。
<?php $filename = '/path/to/foo.txt'; if (is_file($filename)) { echo "The file $filename is a regular file"; } else { echo "There is a problem with the file: $filename"; } ?>
[3] 在执行涉及用户输入的文件操作时,您应该始终检查指定的文件名和路径是否符合特定限制。具体地说,您应确保仅允许在预定义的目录树内执行文件操作。具体地说,您应确保仅允许在预定义的目录树内执行文件操作。只检查给定路径名是否以所需的目录名称开头是不够的,因为攻击者可使用路径遍历攻击来欺骗应用程序,使其提供所需目录之外的文件,例如 -“../../../../”(注意:该字符串还能以多种其他格式编码,以绕过字符串匹配算法)。
以下是两个 PHP 函数,在尝试验证用户提供的文件名时可能有用:
realpath() - 返回标准化绝对路径名
basename() - 返回路径的文件名部分
引用[1] PHP 手册 - realpath():
[2] PHP 手册 - basename():
[3] PHP 手册 - file_exists():
http://www.php.net/file_exists
[4] PHP 手册 - is_file():
文件路径验证:
PHP 提供多种方法来在打开文件之前对文件名进行验证。例如:例如:[1] file_exists() 方法:该方法检查是否存在文件或目录。如果文件或目录存在,那么该方法将会返回 True,否则将返回 false。
<?php $filename = '/path/to/foo.txt'; if (file_exists($filename)) { echo "The file $filename exists"; } else { echo "The file $filename does not exist"; } ?>
[2] is_file() 方法:该方法告知文件名是否是常规文件,如果是,便会返回 True。
<?php $filename = '/path/to/foo.txt'; if (is_file($filename)) { echo "The file $filename is a regular file"; } else { echo "There is a problem with the file: $filename"; } ?>
[3] 在执行涉及用户输入的文件操作时,您应该始终检查指定的文件名和路径是否符合特定限制。具体地说,您应确保仅允许在预定义的目录树内执行文件操作。具体地说,您应确保仅允许在预定义的目录树内执行文件操作。只检查给定路径名是否以所需的目录名称开头是不够的,因为攻击者可使用路径遍历攻击来欺骗应用程序,使其提供所需目录之外的文件,例如 -“../../../../”(注意:该字符串还能以多种其他格式编码,以绕过字符串匹配算法)。
以下是两个 PHP 函数,在尝试验证用户提供的文件名时可能有用:
realpath() - 返回标准化绝对路径名
basename() - 返回路径的文件名部分
引用[1] PHP 手册 - realpath():
[2] PHP 手册 - basename():
[3] PHP 手册 - file_exists():
http://www.php.net/file_exists
[4] PHP 手册 - is_file():
** 输入数据验证:** 输入数据验证:虽然为方便用户而在客户端层上提供数据验证,但仍必须始终在服务器层上执行数据验证。客户端验证本身就不安全,因为这些验证可轻易绕过,例如,通过禁用 Javascript。
一份好的设计通常需要 Web 应用程序框架,以提供服务器端实用程序例程,从而验证以下内容:
[1] 必需字段
[2] 字段数据类型(缺省情况下,所有 HTTP 请求参数都是“字符串”)
[3] 字段长度
[4] 字段范围
[5] 字段选项
[6] 字段模式
[7] cookie 值
[8] HTTP 响应好的做法是实现一个或多个验证每个应用程序参数的函数。以下部分描述一些检查的示例。
[1] 必需字段“始终”检查字段不为空,并且其长度要大于零,不包括行距和后面的空格。如何验证必需字段的示例:
// PHP example to validate required fields function validateRequired($input) { ... $pass = false; if (strlen(trim($input))>0){ $pass = true; } return $pass; ... } ... if (validateRequired($fieldName)) { // fieldName is valid, continue processing request ... }
[2] 输入的 Web 应用程序中的字段数据类型和输入参数欠佳。例如,所有 HTTP 请求参数或 cookie 值的类型都是“字符串”。开发者负责验证输入的数据类型是否正确。[3] 字段长度“始终”确保输入参数(HTTP 请求参数或 cookie 值)有最小长度和/或最大长度的限制。[4] 字段范围
始终确保输入参数是在由功能需求定义的范围内。
[5] 字段选项 Web 应用程序通常会为用户显示一组可供选择的选项(例如,使用 SELECT HTML 标记),但不能执行服务器端验证以确保选定的值是其中一个允许的选项。请记住,恶意用户能够轻易修改任何选项值。始终针对由功能需求定义的受允许的选项来验证选定的用户值。[6] 字段模式
始终检查用户输入与由功能需求定义的模式是否匹配。例如,如果 userName 字段应仅允许字母数字字符,且不区分大小写,那么请使用以下正则表达式:^[a-zA-Z0-9]+$
[7] cookie 值
适用于 cookie 值的相同的验证规则(如上所述)取决于应用程序需求(如验证必需值、验证长度等)。
[8] HTTP 响应[8-1] 过滤用户输入要保护应用程序免遭跨站点脚本编制的攻击,开发者应通过将敏感字符转换为其对应的字符实体来清理 HTML。这些是 HTML 敏感字符:< > " ' % ; ) ( & +
PHP 包含一些自动化清理实用程序函数,如 htmlentities():
$input = htmlentities($input, ENT_QUOTES, 'UTF-8');
此外,为了避免“跨站点脚本编制”的 UTF-7 变体,您应该显式定义响应的 Content-Type 头,例如:
<?php header('Content-Type: text/html; charset=UTF-8'); ?>
[8-2] 保护 cookie
在 cookie 中存储敏感数据且通过 SSL 来传输时,请确保先在 HTTP 响应中设置 cookie 的安全标志。这将会指示浏览器仅通过 SSL 连接来使用该 cookie。
为了保护 cookie,您可以使用以下代码示例:
<$php $value = "some_value"; $time = time()+3600; $path = "/application/"; $domain = ".example.com"; $secure = 1; setcookie("CookieName", $value, $time, $path, $domain, $secure, TRUE); ?>
此外,我们建议您使用 HttpOnly 标志。当 HttpOnly 标志设置为 TRUE 时,将只能通过 HTTP 协议来访问 cookie。这意味着无法用脚本语言(如 JavaScript)来访问 cookie。该设置可有效地帮助减少通过 XSS 攻击盗用身份的情况(虽然并非所有浏览器都支持该设置)。
在 PHP 5.2.0 中添加了 HttpOnly 标志。
引用[1] 使用 HTTP 专用 cookie 来减轻“跨站点脚本编制”的影响:
http://msdn2.microsoft.com/en-us/library/ms533046.aspx
[2] PHP 安全协会:
[3] PHP 和 Web 应用程序安全博客 (Chris Shiflett):