Some web applications will XML Files are used for a variety of purposes, from configuration to full database functionality. User input is typically passed
to these files to customize the configuration or update the application database. This can become a security risk if user input is not sanitized or validated before consuming it. When no precautions are taken, malicious users can change the configuration instructions and add new users (if the user list passes
XML file for maintenance), obtain higher privileges, and more. The following demonstrates a vulnerable J2EE application:
Document doc = docBuilder.newDocument(); Element rootElement = doc.createElement("root"); doc.appendChild(rootElement); Element firstElement = doc.createElement("first"); rootElement.appendChild(firstElement); Element childElement = doc.createElement("child"); childElement.appendChild(doc.createTextNode(--User_Supplied_Text--)); // un-sanitized text rootElement.appendChild(childElement);
The following demonstrates a similar vulnerable .NET application:
String nodeText = request.getParameter("node"); XmlWriterSettings settings = new XmlWriterSettings(); writer = XmlWriter.Create(m_Document, settings); writer.WriteElementString("newNode", nodeText); // un-sanitized text writer.WriteEndElement();
In this code example, user input is propagated into the XML file without proper sanitization. This vulnerability could be exploited in various ways, depending on how the application uses the XML file.
Danger due to incorrect execution of user input Character sanitization, The worst case scenario of this attack depends on the page context modified on the client side, which may destroy the application logic
The remedy for several problems lies in sanitizing user input. By validating that user input does not contain dangerous characters, it is possible to prevent malicious users from causing the application to perform unintended tasks, such as launching arbitrary SQL queries, embedding Javascript## that will be executed on the client side # code, run various operating system commands, etc.
[10] \" (backslash escaped quote)
[11] <> (angle bracket)
[12] () (bracket)
[13] + (plus sign)
[14] CR (carriage return, ASCII 0x0d)
[15] LF (line feed, ASCII 0x0a)
[16] , (comma)
[17] \ (backslash)
The following sections describe various issues, suggested fixes for the issues, and dangerous characters that may trigger these issues:
A. Ensure that the values and types (such as Integer, Date, etc.) entered by the user are valid and meet the application expectations
B. Use stored procedures to abstract data access so that users do not directly access tables or views. When using stored procedures, implement them using ADO command objects to enforce variable types.
C. Clean input to. Exclude context-changing symbols, for example:
[1] ' (single quote)
[2] " (quote mark)
[3] \' (backslash (escape single quote)
[4] \" (backslash escaped quote)
[5] ) (end bracket)
[6] ; (semicolon )
A. Sanitize user input and filter out JavaScript code. We recommend that you filter the following characters:
[1] <> (angle brackets)
[2] " (quotation marks)
[3] ' (single quotation mark)
[4] % (percent symbol)
[5] ;(semicolon)
[6] () (brackets)
[7] & (& ampersand)
[8] + (plus sign)
B. If you want to revise <%00script> variant, see MS article 821349
C. For UTF-7 attacks: [-] If possible, it is recommended that you implement a specific character set encoding (using 'Content -Type' header or tag).
Sanitize user input (at least the input that is later embedded in the HTTP response). Please ensure that the input does not contain malicious characters, such as:
[ 1] CR (Carriage Return, ASCII 0x0d)
[2] LF (Line Feed, ASCII 0x0a) Remote command execution: Cleans input to exclude symbols that are meaningful for executing operating system commands, for example:
[1] | (vertical bar symbol)
[2] & (& symbol)
[3]; (semicolon)
A. Never pass unchecked user input to Perl commands such as eval(), open(), sysopen(), system(), etc.
B. Make sure the input does not contain malicious characters, such as:
[1] $ (dollar sign)
[2] % (percent sign)
[3] @(at symbol)
XPath Injection: sanitize input to exclude context-changing symbols, for example:
[1] '(single quote)
[2] " (quotes) etc
A. Use positive authentication. Alphanumeric filtering (A..Z,a..z,0. .9) Suitable for most LDAP queries
B. Special LDAP characters that should be filtered out or escaped:
[1] A space or "#" character at the beginning of the string
[2] A space character at the end of the string
[3], ( comma)
[4] + (plus sign)
[5] " (quote mark)
[6] \ (backslash)
[7] <>(angle brackets)
[8] ;(semicolon)
[9] ()(brackets)
Special MX characters should be filtered out:
[1] CR (carriage return, ASCII 0x0d)
[2] LF (line feed, ASCII 0x0a) record forgery:
Special record characters should be filtered out:
[1] CR (carriage return, ASCII 0x0d)
[2] LF (line feed, ASCII 0x0a)
[3] BS (Backspace, ASCII 0x08)
A. Ensure that the values and types (such as Integer, Date, etc.) entered by the user are valid and meet the application expectations .
B. Use stored procedures to abstract data access so that users do not directly access tables or views. C. Use parameterized queriesAPI
#D. Clean up Enter to exclude context-changing symbols, for example: (*):[1] '(single quote)[2] "(quote)[3] \ ' (backslash-escaped single quote) [4] \" (backslash-escaped quote) [5] ) (end bracket)[6] ; (semicolon)
(*) This works in SQL. Advanced query languages may require different sanitization mechanisms.
[1] We recommend that you Servers are upgraded to .NET Framework 2.0 (or newer), which inherently includes security checks to protect against cross-site scripting attacks.
[2] You can add input validation to a Web Form page using the validation control. Validation controls provide easy-to-use mechanisms for all common types of standard validation (for example, testing to verify that a date is valid, or verifying that a value is within a range). In addition, the validation control also supports custom-written validation, allowing you to completely customize the way error messages are displayed to users. Validation controls can be used with any control processed in the Web Forms page class file, including HTML and Web server controls.
[1] "RangeValidator": Check if the user entry (value) is in the specified between upper and lower boundaries. You can check for matching numbers, alphabetic characters, and ranges within dates.
[2] "RegularExpressionValidator": Check whether the entry matches the pattern defined by regular expression. This type of validation enables you to check for predictable character sequences, such as those found in social security numbers, email addresses, phone numbers, zip codes, and more. Examples of regular expressions that can help prevent cross-site scripting:
- A regular expression that can deny basic cross-site scripting variations might look like this: ^([^<]|\<[^ a-zA-Z])*[<]?$
- A general regular expression to reject all the above characters might be as follows: ^([^\<\>\"\'\%\ ;\)\(\&\+]*)$
- Important note: Validation controls do not prevent user input or change the flow of page processing; they only set an error state and generate an error message for the programmer. The responsibility is to test the state of controls in your code before performing further application-specific operations.
There are two ways to check the validity of user input:
1. Test for general errors. Status:
In your code, test the IsValid property of the page. This property sums up (using a logical AND) the IsValid property values of all validation controls on the page if one of the validation controls is set to invalid. Then the page properties will return false
2. Test the error status of individual controls:
在页面的“验证器”集合中循环,该集合包含对所有验证控件的引用。 然后,您就可以检查每个验证控件的 IsValid 属性。
建议使用 Microsoft Anti-Cross Site Scripting Library(V1.5 更高版本)对不受信任的用户输入进行编码。
Anti-Cross Site Scripting Library 显现下列方法:
[1] HtmlEncode - 将在 HTML 中使用的输入字符串编码
[2] HtmlAttributeEncode - 将在 HTML 属性中使用的输入字符串编码
[3] JavaScriptEncode - 将在 JavaScript 中使用的输入字符串编码
[4] UrlEncode - 将在“统一资源定位器 (URL)”中使用的输入字符串编码
[5] VisualBasicScriptEncode - 将在 Visual Basic 脚本中使用的输入字符串编码
[6] XmlEncode - 将在 XML 中使用的输入字符串编码
[7] XmlAttributeEncode - 将在 XML 属性中使用的输入字符串编码
如果要适当使用 Microsoft Anti-Cross Site Scripting Library 来保护 ASP.NET Web 应用程序,您必须运行下列操作:
第 1 步:复查生成输出的 ASP.NET 代码
第 2 步:判断是否包括不受信任的输入参数
第 3 步:判断不受信任的输入的上下文是否作为输出,判断要使用哪个编码方法
第 4 步:编码输出
第 3 步骤的示例:
注意:如果要使用不受信任的输入来安装 HTML 属性,便应该使用 Microsoft.Security.Application.HtmlAttributeEncode 方法,将不受信任的输入编码。另外,如果要在 JavaScript 的上下文中使用不受信任的输入,便应该使用 Microsoft.Security.Application.JavaScriptEncode 来编码。
// Vulnerable code // Note that untrusted input is being treated as an HTML attribute Literal1.Text = "<hr noshade size=[untrusted input here]>"; // Modified code Literal1.Text = "<hr noshade size="+Microsoft.Security.Application.AntiXss.HtmlAttributeEncode([untrusted input here])+">";
第 4 步骤的示例:将输出编码时,必须记住的一些重要事项:
[1] 输出应该编码一次。
[2] 输出的编码与实际撰写,应该尽可能接近。 例如,如果应用程序读取用户输入、处理输入,再用某种形式将它重新写出,便应该紧接在撰写输出之前进行编码。
// Incorrect sequence protected void Button1_Click(object sender, EventArgs e) { // Read input String Input = TextBox1.Text; // Encode untrusted input Input = Microsoft.Security.Application.AntiXss.HtmlEncode(Input); // Process input ... // Write Output Response.Write("The input you gave was"+Input); } // Correct Sequence protected void Button1_Click(object sender, EventArgs e) { // Read input String Input = TextBox1.Text; // Process input ... // Encode untrusted input and write output Response.Write("The input you gave was"+ Microsoft.Security.Application.AntiXss.HtmlEncode(Input)); }
虽然为了用户的方便,可以提供“客户端”层数据的数据验证,但必须使用 Servlet 在服务器层执行验证。 客户端验证本身就不安全,因为这些验证可轻易绕过,例如,通过禁用 Javascript。一份好的设计通常需要 Web 应用程序框架,以提供服务器端实用程序例程,从而验证以下内容:[1] 必需字段[2] 字段数据类型(缺省情况下,所有 HTTP 请求参数都是“字符串”)[3] 字段长度[4] 字段范围[5] 字段选项[6] 字段模式[7] cookie 值[8] HTTP 响应好的做法是将以上例程作为“验证器”实用程序类中的静态方法实现。以下部分描述验证器类的一个示例。
必需字段“始终”检查字段不为空,并且其长度要大于零,不包括行距和后面的空格。如何验证必需字段的示例:
// 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 }
输入的 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
“始终”确保输入参数(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
} }
始终确保输入参数是在由功能需求定义的范围内。以下示例验证输入 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 } } }
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 }
始终检查用户输入与由功能需求定义的模式是否匹配。例如,如果 userName 字段应仅允许字母数字字符,且不区分大小写,那么请使用以下正则表达式:^[a-zA-Z0-9]*$
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; } }
使用 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-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);
许多 J2EE Web 应用程序体系结构都遵循“模型视图控制器(MVC)”模式。在该模式中,Servlet 扮演“控制器”的角色。Servlet 将应用程序处理委派给 EJB 会话 Bean(模型)之类的 JavaBean。然后,Servlet 再将请求转发给 JSP(视图),以呈现处理结果。Servlet 应检查所有的输入、输出、返回码、错误代码和已知的异常,以确保实际处理按预期进行。
数据验证可保护应用程序免遭恶意数据篡改,而有效的错误处理策略则是防止应用程序意外泄露内部错误消息(如异常堆栈跟踪)所不可或缺的。好的错误处理策略会处理以下项:
[1] 定义错误
[2] 报告错误
[3] 呈现错误
[4] 错误映射
应避免在应用程序层(如 Servlet)中硬编码错误消息。 相反地,应用程序应该使用映射到已知应用程序故障的错误密钥。好的做法是定义错误密钥,且该错误密钥映射到 HTML 表单字段或其他 Bean 属性的验证规则。例如,如果需要“user_name”字段,其内容为字母数字,并且必须在数据库中是唯一的,那么就应定义以下错误密钥:
(a) ERROR_USERNAME_REQUIRED:该错误密钥用于显示消息,以通知用户需要“user_name”字段;
(b) ERROR_USERNAME_ALPHANUMERIC:该错误密钥用于显示消息,以通知用户“user_name”字段应该是字母数字;
(c) ERROR_USERNAME_DUPLICATE:该错误密钥用于显示消息,以通知用户“user_name”值在数据库中重复;
(d) ERROR_USERNAME_INVALID:该错误密钥用于显示一般消息,以通知用户“user_name”值无效;
好的做法是定义用于存储和报告应用程序错误的以下框架 Java 类:
- ErrorKeys:定义所有错误密钥
// Example: ErrorKeys defining the following error keys: // - ERROR_USERNAME_REQUIRED // - ERROR_USERNAME_ALPHANUMERIC // - ERROR_USERNAME_DUPLICATE // - ERROR_USERNAME_INVALID // ... public Class ErrorKeys { public static final String ERROR_USERNAME_REQUIRED = "error.username.required"; public static final String ERROR_USERNAME_ALPHANUMERIC = "error.username.alphanumeric"; public static final String ERROR_USERNAME_DUPLICATE = "error.username.duplicate"; public static final String ERROR_USERNAME_INVALID = "error.username.invalid"; }
- Error:封装个别错误
// Example: Error encapsulates an error key. // Error is serializable to support code executing in multiple JVMs. public Class Error implements Serializable { // Constructor given a specified error key public Error(String key) { this(key, null); } // Constructor given a specified error key and array of placeholder objects public Error(String key, Object[] values) { this.key = key; this.values = values; } // Returns the error key public String getKey() { return this.key; } // Returns the placeholder values public Object[] getValues() { return this.values; } private String key = null; private Object[] values = null; }
- Errors:封装错误的集合
// Example: Errors encapsulates the Error objects being reported to the presentation layer. // Errors are stored in a HashMap where the key is the bean property name and value is an // ArrayList of Error objects. public Class Errors implements Serializable { // Adds an Error object to the Collection of errors for the specified bean property. public void addError(String property, Error error) { ArrayList propertyErrors = (ArrayList)errors.get(property); if (propertyErrors == null) { propertyErrors = new ArrayList(); errors.put(property, propertyErrors); } propertyErrors.put(error); } // Returns true if there are any errors public boolean hasErrors() { return (errors.size > 0); } // Returns the Errors for the specified property public ArrayList getErrors(String property) { return (ArrayList)errors.get(property); } private HashMap errors = new HashMap(); }
以下是使用上述框架类来处理“user_name”字段验证错误的示例:
// Example to process validation errors of the "user_name" field. Errors errors = new Errors(); String userName = request.getParameter("user_name"); // (a) Required validation rule if (!Validator.validateRequired(userName)) { errors.addError("user_name", new Error(ErrorKeys.ERROR_USERNAME_REQUIRED)); } // (b) Alpha-numeric validation rule else if (!Validator.matchPattern(userName, "^[a-zA-Z0-9]*$")) { errors.addError("user_name", new Error(ErrorKeys.ERROR_USERNAME_ALPHANUMERIC)); } else { // (c) Duplicate check validation rule // We assume that there is an existing UserValidationEJB session bean that implements // a checkIfDuplicate() method to verify if the user already exists in the database. try { ... if (UserValidationEJB.checkIfDuplicate(userName)) { errors.addError("user_name", new Error(ErrorKeys.ERROR_USERNAME_DUPLICATE)); } } catch (RemoteException e) { // log the error logger.error("Could not validate user for specified userName: " + userName); errors.addError("user_name", new Error(ErrorKeys.ERROR_USERNAME_DUPLICATE); } } // set the errors object in a request attribute called "errors" request.setAttribute("errors", errors);
有两种方法可报告 web 层应用程序错误:
(a) Servlet 错误机制、(b) JSP 错误机制
[2-a] Servlet 错误机制
Servlet 可通过以下方式报告错误:
- 转发给输入 JSP(已将错误存储在请求属性中),或
- 使用 HTTP 错误代码参数来调用 response.sendError,或
- 抛出异常
好的做法是处理所有已知应用程序错误(如 [1] 部分所述),将这些错误存储在请求属性中,然后转发给输入 JSP。输入 JSP 应显示错误消息,并提示用户重新输入数据。以下示例阐明转发给输入 JSP(userInput.jsp)的方式:
// Example to forward to the userInput.jsp following user validation errors RequestDispatcher rd = getServletContext().getRequestDispatcher("/user/userInput.jsp"); if (rd != null) { rd.forward(request, response); }
如果 Servlet 无法转发给已知的 JSP 页面,那么第二个选项是使用 response.sendError 方法,将 HttpServletResponse.SC_INTERNAL_SERVER_ERROR(状态码 500)作为参数,来报告错误。 请参阅 javax.servlet.http.HttpServletResponse 的 Javadoc,以获取有关各种 HTTP 状态码的更多详细信息。返回 HTTP 错误的示例:
// Example to return a HTTP error code RequestDispatcher rd = getServletContext().getRequestDispatcher("/user/userInput.jsp"); if (rd == null) { // messages is a resource bundle with all message keys and values response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, messages.getMessage(ErrorKeys.ERROR_USERNAME_INVALID)); }
作为最后的手段,Servlet 可以抛出异常,且该异常必须是以下其中一类的子类:
- RuntimeException
- ServletException
- IOException
[2-b] JSP 错误机制
JSP 页面通过定义 errorPage 伪指令来提供机制,以处理运行时异常,如以下示例所示:
<%@ page errorPage="/errors/userValidation.jsp" %>
未捕获的 JSP 异常被转发给指定的 errorPage,并且原始异常设置在名称为 javax.servlet.jsp.jspException 的请求参数中。错误页面必须包括 isErrorPage 伪指令:
<%@ page isErrorPage="true" %>
isErrorPage 伪指令导致“exception”变量初始化为所抛出的异常对象。
J2SE Internationalization API 提供使应用程序资源外部化以及将消息格式化的实用程序类,其中包括:
(a) 资源束、(b) 消息格式化
[3-a] 资源束
资源束通过将本地化数据从使用该数据的源代码中分离来支持国际化。每一资源束都会为特定的语言环境存储键/值对的映射。
java.util.PropertyResourceBundle 将内容存储在外部属性文件中,对其进行使用或扩展都很常见,如以下示例所示:
################################################ # ErrorMessages.properties ################################################ # required user name error message error.username.required=User name field is required # invalid user name format error.username.alphanumeric=User name must be alphanumeric # duplicate user name error message error.username.duplicate=User name {0} already exists, please choose another one
可定义多种资源,以支持不同的语言环境(因此名为资源束)。例如,可定义 ErrorMessages_fr.properties 以支持该束系列的法语成员。如果请求的语言环境的资源成员不存在,那么会使用缺省成员。在以上示例中,缺省资源是 ErrorMessages.properties。应用程序(JSP 或 Servlet)会根据用户的语言环境从适当的资源检索内容。
[3-b] 消息格式化
J2SE 标准类 java.util.MessageFormat 提供使用替换占位符来创建消息的常规方法。MessageFormat 对象包含嵌入了格式说明符的模式字符串,如下所示:
// Example to show how to format a message using placeholder parameters String pattern = "User name {0} already exists, please choose another one"; String userName = request.getParameter("user_name"); Object[] args = new Object[1]; args[0] = userName; String message = MessageFormat.format(pattern, args);
以下是使用 ResourceBundle 和 MessageFormat 来呈现错误消息的更加全面的示例:
// Example to render an error message from a localized ErrorMessages resource (properties file) // Utility class to retrieve locale-specific error messages public Class ErrorMessageResource { // Returns the error message for the specified error key in the environment locale public String getErrorMessage(String errorKey) { return getErrorMessage(errorKey, defaultLocale); } // Returns the error message for the specified error key in the specified locale public String getErrorMessage(String errorKey, Locale locale) { return getErrorMessage(errorKey, null, locale); } // Returns a formatted error message for the specified error key in the specified locale public String getErrorMessage(String errorKey, Object[] args, Locale locale) { // Get localized ErrorMessageResource ResourceBundle errorMessageResource = ResourceBundle.getBundle("ErrorMessages", locale); // Get localized error message String errorMessage = errorMessageResource.getString(errorKey); if (args != null) { // Format the message using the specified placeholders args return MessageFormat.format(errorMessage, args); } else { return errorMessage; } } // default environment locale private Locale defaultLocale = Locale.getDefaultLocale(); } // Get the user's locale Locale userLocale = request.getLocale(); // Check if there were any validation errors Errors errors = (Errors)request.getAttribute("errors"); if (errors != null && errors.hasErrors()) { // iterate through errors and output error messages corresponding to the "user_name" property ArrayList userNameErrors = errors.getErrors("user_name"); ListIterator iterator = userNameErrors.iterator(); while (iterator.hasNext()) { // Get the next error object Error error = (Error)iterator.next(); String errorMessage = ErrorMessageResource.getErrorMessage(error.getKey(), userLocale); output.write(errorMessage + "\r\n"); } }
建议定义定制 JSP 标记(如 displayErrors),以迭代处理并呈现错误消息,如以上示例所示。
通常情况下,“Servlet 容器”会返回与响应状态码或异常相对应的缺省错误页面。可以使用定制错误页面来指定状态码或异常与 Web 资源之间的映射。好的做法是开发不会泄露内部错误状态的静态错误页面(缺省情况下,大部分 Servlet 容器都会报告内部错误消息)。该映射配置在“Web 部署描述符(web.xml)”中,如以下示例所指定:
<!-- Mapping of HTTP error codes and application exceptions to error pages --> <error-page> <exception-type>UserValidationException</exception-type> <location>/errors/validationError.html</error-page> </error-page> <error-page> <error-code>500</exception-type> <location>/errors/internalError.html</error-page> </error-page> <error-page> ... </error-page>
虽然为方便用户而在客户端层上提供数据验证,但仍必须始终在服务器层上执行数据验证。客户端验证本身就不安全,因为这些验证可轻易绕过,例如,通过禁用 Javascript。一份好的设计通常需要 Web 应用程序框架,以提供服务器端实用程序例程,从而验证以下内容:[1] 必需字段[2] 字段数据类型(缺省情况下,所有 HTTP 请求参数都是“字符串”)[3] 字段长度[4] 字段范围[5] 字段选项[6] 字段模式[7] cookie 值[8] HTTP 响应好的做法是实现一个或多个验证每个应用程序参数的函数。以下部分描述一些检查的示例。
“始终”检查字段不为空,并且其长度要大于零,不包括行距和后面的空格。如何验证必需字段的示例:
// 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 }
输入的 Web 应用程序中的字段数据类型和输入参数欠佳。例如,所有 HTTP 请求参数或 cookie 值的类型都是“字符串”。开发者负责验证输入的数据类型是否正确。
“始终”确保输入参数(HTTP 请求参数或 cookie 值)有最小长度和/或最大长度的限制。
始终确保输入参数是在由功能需求定义的范围内。
[5] 字段选项
Web 应用程序通常会为用户显示一组可供选择的选项(例如,使用 SELECT HTML 标记),但不能执行服务器端验证以确保选定的值是其中一个允许的选项。请记住,恶意用户能够轻易修改任何选项值。始终针对由功能需求定义的受允许的选项来验证选定的用户值。
始终检查用户输入与由功能需求定义的模式是否匹配。例如,如果 userName 字段应仅允许字母数字字符,且不区分大小写,那么请使用以下正则表达式:^[a-zA-Z0-9]+$
适用于 cookie 值的相同的验证规则(如上所述)取决于应用程序需求(如验证必需值、验证长度等)。
[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); ?>
The above is the detailed content of Detailed introduction to XML injection and code defense. For more information, please follow other related articles on the PHP Chinese website!