Heim > Artikel > Backend-Entwicklung > Detaillierte Einführung in XML-Injection und Code-Verteidigung
Einige Webanwendungen verwenden XML Dateien werden für verschiedene Zwecke verwendet, von der Konfiguration bis zur vollständigen Datenbankfunktionalität. Benutzereingaben werden normalerweise
in diese Dateien weitergegeben, um die Konfiguration anzupassen oder die Anwendungsdatenbank zu aktualisieren. Dies kann zu einem Sicherheitsrisiko werden, wenn falsche Zeichen nicht bereinigt oder validiert werden, bevor Benutzereingaben verwendet werden. Wenn keine Vorsichtsmaßnahmen getroffen werden, können böswillige Benutzer Konfigurationsanweisungen ändern und neue Benutzer hinzufügen (sofern die Benutzerliste erfolgreich ist).
XML-Datei zur Wartung), höhere Berechtigungen erhalten und mehr. Das Folgende zeigt eine anfällige J2EE-Anwendung:
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);
Das Folgende zeigt eine ähnlich anfällige .NET-Anwendung:
String nodeText = request.getParameter("node"); XmlWriterSettings settings = new XmlWriterSettings(); writer = XmlWriter.Create(m_Document, settings); writer.WriteElementString("newNode", nodeText); // un-sanitized text writer.WriteEndElement();
Mit diesem Beispielcode: Benutzereingaben werden ohne ordnungsgemäße Bereinigung in die XML-Datei übernommen. Diese Schwachstelle kann auf unterschiedliche Weise ausgenutzt werden, je nachdem, wie die Anwendung die XML-Datei verwendet.
Gefahr durch fehlerhafte Ausführung von Benutzereingaben Zeichenbereinigung, Das schlimmste Szenario dieses Angriffs hängt davon ab, dass der Seitenkontext auf der Clientseite geändert wird, was die Anwendungslogik zerstören kann
Die Lösung für mehrere Probleme liegt in der Bereinigung von Benutzereingaben. Durch die Überprüfung, dass Benutzereingaben keine gefährlichen Zeichen enthalten, kann verhindert werden, dass böswillige Benutzer die Anwendung dazu veranlassen, unbeabsichtigte Aufgaben auszuführen, wie etwa das Starten beliebiger SQL-Abfragen oder das Einbetten von Javascript Code, verschiedene Betriebssystembefehle ausführen usw.
[10] " (Anführungszeichen mit Backslash-Escape)
[11] a8093152e673feb7aba1828c43532094 (spitze Klammern)
[12] () (Klammern)
[13] + (Pluszeichen)
[14] CR (Wagenrücklauf, ASCII 0x0d)
[15] LF (Zeilenvorschub, ASCII 0x0a)
[16 ] , (Komma)
[17] (Backslash)
In den folgenden Abschnitten werden verschiedene Probleme, empfohlene Überarbeitungen für die Probleme und gefährliche Zeichen beschrieben, die sie auslösen können:
A. Stellen Sie sicher, dass die vom Benutzer eingegebenen Werte und Typen (z. B. Ganzzahl, Datum usw.) gültig sind und den Anwendungserwartungen entsprechen
B. Nutzen Sie den Datenzugriff durch Prozeduren, damit Benutzer nicht direkt auf Tabellen oder Ansichten zugreifen. Implementieren Sie sie mithilfe von ADO-Befehlsobjekten, um Variablentypen zu erzwingen. Ändern Sie die Symbole, zum Beispiel:
[1] ' (einfaches Anführungszeichen)
[2] " (Anführungszeichen)
[3] ' (einfaches Anführungszeichen mit Backslash-Escapezeichen). )
[4] " (Backslash-Escape-Anführungszeichen)
[5] ) (Endklammer)
[6] ; (Semikolon)
3 Cross-Site-Scripting
[1] a8093152e673feb7aba1828c43532094
[2] " (Anführungszeichen) [3] ' (einfaches Anführungszeichen) [4] % (Prozentzeichen) [5] ; (Semikolon)[6] () (Klammern)
[7] & (& kaufmännisches Und)
[8] + (Pluszeichen)
B Informationen zur Überarbeitung der 53970efcfe04ee69fddbddf90d241207-Variante finden Sie im MS-Artikel 821349. mit dem Header „Content -Type“ oder dem Tag e8e496c15ba93d81f6ea4fe5f55a2244
4 HTTP-Antwortaufteilung Bereinigen Sie Benutzereingaben (zumindest die Eingaben, die später in die HTTP-Antwort eingebettet werden). Bitte stellen Sie sicher, dass die Eingabe keine schädlichen Zeichen enthält, wie zum Beispiel:
[2] LF (Line Feed, ASCII 0x0a) Remote-Befehlsausführung: Bereinigen Sie die Eingabe, um Symbole auszuschließen, die für die Ausführung von Betriebssystembefehlen von Bedeutung sind. zum Beispiel:
[1] | (vertikales Balkensymbol)
[2] & (&-Symbol)
[3] (Semikolon)
5 Führen Sie den Shell-Befehl
A aus. Übergeben Sie niemals ungeprüfte Benutzereingaben an
PerlB. Stellen Sie sicher, dass die Eingabe keine schädlichen Zeichen enthält, wie zum Beispiel: [1] $ (Dollarzeichen)
[2] % (Prozentzeichen)
[3] @(at symbol)
XPathInjektion: Eingabe bereinigen, um kontextverändernde Symbole auszuschließen, zum Beispiel:
[1] ' (einfaches Anführungszeichen)[2] " (Anführungszeichen) usw.
6 LDAP-Injektion
A. Positive Authentifizierung verwenden. Alphanumerische Filterung (A..Z,a.. z,0. .9) Geeignet für die meisten LDAP-Abfragen
[1] Leerzeichen oder „#“-Zeichen am Anfang der Zeichenfolge
[2] Leerzeichen am Ende der Zeichenfolge
[3] , ( Komma)
[4] + (Pluszeichen)
[5] "(Anführungszeichen)
[6] (Backslash)
[ 7 ] a8093152e673feb7aba1828c43532094 (Spitze Klammern)
[8] ; (Semikolon)
[9] () (Klammern)
Spezielle MX-Zeichen sollten herausgefiltert werden:
[1] CR (Wagenrücklauf, ASCII 0x0d)
[2] LF (Zeilenvorschub, ASCII 0x0a) Datensatzfälschung:
Sonderzeichen im Datensatz sollten herausgefiltert werden:
[1] CR (Wagenrücklauf, ASCII 0x0d)
[2] LF (Zeilenvorschub, ASCII 0x0a)
[3] BS (Backspace, ASCII 0x08)
A. Stellen Sie sicher, dass die vom Benutzer eingegebenen Werte und Typen (wie Ganzzahl, Datum usw.) sind gültig und erfüllen die Anwendungserwartungen.
B. Verwenden Sie gespeicherte Prozeduren, um den Datenzugriff zu abstrahieren, damit Benutzer nicht direkt auf Tabellen oder Ansichten zugreifen. C. Verwenden Sie parametrisierte Abfragen >D. Bereinigen Sie die Eingabe, um kontextverändernde Symbole auszuschließen, zum Beispiel: (*): [1] '(einfaches Anführungszeichen)
[2] "(Anführungszeichen)
[3] '( Einfaches Anführungszeichen mit Backslash-Escape)
[4] " (Anführungszeichen mit Backslash-Escape)
[5] ) (Endklammer)
[6] ; (Semikolon)
(*) Dies funktioniert in SQL. Erweiterte Abfragesprachen erfordern möglicherweise unterschiedliche Bereinigungsmechanismen.
[1] Wir empfehlen Ihnen Server werden auf .NET Framework 2.0 (oder neuer) aktualisiert, das standardmäßig Sicherheitsüberprüfungen zum Schutz vor Cross-Site-Scripting-Angriffen umfasst.
[2] Sie können Ihrer Web Forms-Seite eine Eingabevalidierung hinzufügen, indem Sie das Validierungs--Steuerelement verwenden. Validierungskontrollen bieten benutzerfreundliche Mechanismen für alle gängigen Arten der Standardvalidierung (z. B. Tests zur Überprüfung, ob ein Datum gültig ist, oder Überprüfung, ob ein Wert innerhalb eines Bereichs liegt). Darüber hinaus unterstützt die Validierungssteuerung auch eine benutzerdefinierte Validierung, sodass Sie die Art und Weise, wie Fehlermeldungen Benutzern angezeigt werden, vollständig anpassen können. Validierungssteuerelemente können mit jedem Steuerelement verwendet werden, das in der Web Forms-Seitenklassendatei verarbeitet wird, einschließlich HTML- und Webserversteuerelementen.
[1] „BereichValidator“: Prüft, ob die Benutzereingabe (Wert) liegt im angegebenen Bereich zwischen oberer und unterer Grenze. Sie können innerhalb von Datumsangaben nach übereinstimmenden Zahlen, alphabetischen Zeichen und Bereichen suchen.
[2] „RegularExpressionValidator“: Prüft, ob der Eintrag mit dem durch den regulären Ausdruck definierten Muster übereinstimmt. Mit dieser Art der Validierung können Sie nach vorhersehbaren Zeichenfolgen suchen, wie sie beispielsweise in Sozialversicherungsnummern, E-Mail-Adressen, Telefonnummern, Postleitzahlen und mehr vorkommen. Beispiele für reguläre Ausdrücke, die dabei helfen können, Cross-Site-Scripting zu verhindern:
– Ein regulärer Ausdruck, der grundlegende Cross-Site-Scripting-Varianten verweigern kann, könnte wie folgt aussehen: ^([^<]|<[^a -zA-Z])*[a8093152e673feb7aba1828c43532094"'%;)(& +]* )$
WICHTIGER HINWEIS: Validierungskontrollen verhindern keine Benutzereingaben und ändern nicht den Seitenverarbeitungsablauf; sie legen lediglich einen Fehlerstatus fest und generieren Fehlermeldungen. Es liegt in der Verantwortung des Programmierers, weitere anwendungsspezifische Maßnahmen durchzuführen Aktionen. Testen Sie vor dem Betrieb den Status der Steuerelemente in Ihrem Code:
Es gibt zwei Möglichkeiten, die Gültigkeit der Benutzereingaben zu überprüfen:
In Ihrem Code wird die IsValid-Eigenschaft der Testseite (mittels logischem UND) der IsValid-Eigenschaftswerte aller Validierungssteuerelemente auf der Seite aggregiert Die Seiteneigenschaft gibt „false“ zurück.
2. Testen Sie den Fehlerstatus einzelner Steuerelemente:在页面的“验证器”集合中循环,该集合包含对所有验证控件的引用。 然后,您就可以检查每个验证控件的 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 敏感字符:6d267e5fab17ea8bc578f9e7e5e1570b " ' % ; ) ( & +
以下示例通过将敏感字符转换为其对应的字符实体来过滤指定字符串:
// 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 敏感字符:6d267e5fab17ea8bc578f9e7e5e1570b " ' % ; ) ( & +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); ?>
Das obige ist der detaillierte Inhalt vonDetaillierte Einführung in XML-Injection und Code-Verteidigung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!