>  기사  >  백엔드 개발  >  XML 주입 및 코드 방어에 대한 자세한 소개

XML 주입 및 코드 방어에 대한 자세한 소개

黄舟
黄舟원래의
2017-03-18 17:28:592912검색


0x01 소개

일부 웹 애플리케이션은 XML을 사용합니다. 파일은 구성부터 전체 데이터베이스 기능까지 다양한 목적으로 사용됩니다. 사용자 입력은 일반적으로

이러한 파일에 전파되어 구성을 사용자 정의하거나 애플리케이션 데이터베이스를 업데이트합니다. 사용자 입력을 사용하기 전에 삭제하거나 검증하지 않으면 보안 위험이 발생할 수 있습니다. 예방 조치를 취하지 않으면 악의적인 사용자가 구성 지침을 변경하고 새 사용자를 추가할 수 있습니다(사용자 목록이 통과하는 경우). 유지 관리용 XML 파일), 더 높은 권한 등을 얻을 수 있습니다. 다음은 취약한 J2EE 애플리케이션을 보여줍니다.

  		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);

다음은 유사한 취약한 .NET 애플리케이션을 보여줍니다.

  		String nodeText = request.getParameter("node");
  		XmlWriterSettings settings = new XmlWriterSettings();
  		writer = XmlWriter.Create(m_Document, settings);
  		writer.WriteElementString("newNode", nodeText); // un-sanitized text
  		 writer.WriteEndElement();

이 코드 예제에서 사용자는 입력은 적절한 삭제 없이 XML 파일로 전파됩니다. 이 취약점은 응용 프로그램이 XML 파일을 사용하는 방식에 따라 다양한 방식으로 악용될 수 있습니다.

0x02 Threat Impact

사용자 입력의 잘못된 실행으로 인한 위험 문자 삭제, 이 공격의 최악의 시나리오는 클라이언트 측에서 수정된 페이지 컨텍스트에 따라 달라지며, 이로 인해 애플리케이션 로직이 파괴될 수 있습니다

0x03 아이디어 수정

여러 문제에 대한 해결책은 사용자 입력을 삭제하는 데 있습니다. 사용자 입력에 위험한 문자가 포함되어 있지 않은지 확인함으로써 악의적인 사용자가 애플리케이션에서 임의의 SQL 쿼리 실행, Javascript 코드, 다양한 운영 체제 명령 실행 등

1 추천 필터 문자

[1] |(세로 막대 기호)

[2] &(& 앰퍼샌드)

[3];(세미콜론)

[4] $(달러 기호)

[5] %(퍼센트 기호)

[6] @(기호 )

[7] '(작은따옴표)

[8] "(따옴표)

[9] '(백슬래시로 이스케이프된 작은따옴표)

[10] "(백슬래시로 이스케이프 처리된 따옴표)

[11] a8093152e673feb7aba1828c43532094(꺾쇠 괄호)

[12] ()(괄호)

[13] +(더하기 기호)

[14] CR(캐리지 리턴, ASCII 0x0d)

[15] LF(라인 피드, ASCII 0x0a)

[16 ] , (쉼표)

[17] (백슬래시)

다음 섹션에서는 다양한 문제, 해당 문제에 대해 제안된 개정 및 이를 유발할 수 있는 위험한 문자에 대해 설명합니다.

2 SQL 인젝션과 블라인드 SQL 인젝션

A. 사용자가 입력한 값과 유형(예: 정수, 날짜 등)이 유효한지, 애플리케이션 기대치를 충족하는지 확인

B. 스토리지를 활용하여 사용자가 테이블이나 뷰에 직접 액세스하지 않도록 데이터 액세스를 추상화합니다. 저장 프로시저를 사용하는 경우 변수 유형을 적용하기 위해 이를 구현합니다.

C. 예를 들어 다음과 같이 기호를 변경합니다.

[1] '(작은따옴표)

[2] "(따옴표)

[3] '(백슬래시로 이스케이프된 작은따옴표) )

[4] "(백슬래시 이스케이프 인용)

[5] )(끝 대괄호)

[6] ;(세미콜론)

3 크로스 사이트 스크립팅

A. 사용자 입력을 삭제하고 JavaScript 코드를 필터링하는 것이 좋습니다.

[1] a8093152e673feb7aba1828c43532094

[2] "(따옴표)

[3] '(작은따옴표)

[4] %(퍼센트 기호)

[5] ; (세미콜론)

[6] ()(괄호)

[7] &(& 앰퍼샌드)

[8] +(더하기 기호)

B. 53970efcfe04ee69fddbddf90d241207 변형을 수정하려면 MS 문서 821349

를 참조하세요. C. UTF-7 공격의 경우: [-] 가능하면 특정 문자 집합 인코딩을 구현하는 것이 좋습니다( 'Content -Type' 헤더 또는 e8e496c15ba93d81f6ea4fe5f55a2244 태그 사용)

4 HTTP 응답 분할

사용자 입력(최소한 나중에 HTTP 응답에 포함된 입력)을 삭제하세요. 입력에 다음과 같은 악성 문자가 포함되어 있지 않은지 확인하세요.

[ 1] CR(캐리지 리턴, ASCII 0x0d)

[2] LF(라인 피드, ASCII 0x0a) 원격 명령 실행: 운영 체제 명령 실행에 의미 있는 기호를 제외하도록 입력을 정리합니다. 예:

[1] |(세로 막대 기호)

[2] &(& 기호)

[3](세미콜론)

5 쉘 명령 실행

A. 확인되지 않은 사용자 입력을 eval(), open(), sysopen(), system() 등과 같은 Perl 명령에 전달하지 마십시오.

B. 입력에 다음과 같은 악성 문자가 포함되어 있지 않은지 확인하세요.

[1] $(달러 기호)

[2] %(퍼센트 기호)

[3] @(at 기호)

XPath 주입: 입력을 삭제하여 컨텍스트 변경 기호를 제외합니다. 예:

[1] ' (작은따옴표)

[2] " (따옴표) 등

6 LDAP 삽입

A. 긍정적인 인증을 사용합니다. 영숫자 필터링(A..Z,a.. z,0.9) 대부분의 LDAP 쿼리에 적합합니다.

B. 필터링하거나 이스케이프해야 하는 특수 LDAP 문자:

[1] 문자열 시작 부분의 공백 또는 "#" 문자

[2] 문자열

끝 부분의 공백 문자[3] , ( 쉼표)

[4] +(더하기 기호)

[5] "(따옴표)

[6](백슬래시)

[ 7 ] a8093152e673feb7aba1828c43532094 (꺾쇠괄호)

[8] ; (세미콜론)

[9] () (괄호)

7 MX 주입

특수 MX 문자는 필터링되어야 합니다:

[1] CR(캐리지 리턴, ASCII 0x0d)

[2] LF(라인 피드, ASCII 0x0a) 레코드 위조:

특수 레코드 문자는 필터링되어야 합니다.

[1] CR(캐리지 리턴, ASCII 0x0d)

[2] LF(라인 피드, ASCII 0x0a)

[3] BS(Backspace, ASCII 0x08)

8 ORM 주입

A. 사용자가 입력한 값과 유형(Integer, Date 등)이 맞는지 확인하세요.

B. 사용자가 테이블이나 뷰에 직접 액세스하지 않도록 저장 프로시저를 사용하여 데이터 액세스를 추상화합니다. C. 매개변수화된 쿼리를 사용합니다. API

D. 문맥 변경 기호를 제외하는 정리 입력입니다. 예: (*):

[1] '(작은따옴표)

[2] "(따옴표)

[3] '( 백슬래시로 이스케이프된 작은따옴표)

[4] "(백슬래시로 이스케이프된 따옴표)

[5] )(끝 대괄호)

[6] ;(세미콜론)

(*) 이는 SQL에서 작동합니다. 고급 쿼리 언어에는 다른 삭제 메커니즘이 필요할 수 있습니다.

0x04 Asp.Net

1 일반 권장 사항

[1] 서버는 크로스 사이트 스크립팅 공격으로부터 보호하기 위한 보안 검사가 기본적으로 포함된 .NET Framework 2.0(또는 그 이상)으로 업그레이드됩니다.

[2] 유효성 검사 컨트롤 을 사용하여 Web Forms 페이지에 입력 유효성 검사를 추가할 수 있습니다. 유효성 검사 컨트롤은 모든 일반적인 유형의 표준 유효성 검사(예: 날짜가 유효한지 확인하기 위한 테스트 또는 값이 범위 내에 있는지 확인)에 대해 사용하기 쉬운 메커니즘을 제공합니다. 또한 유효성 검사 컨트롤은 사용자 정의 작성된 유효성 검사도 지원하므로 오류 메시지가 사용자에게 표시되는 방식을 완전히 사용자 정의할 수 있습니다. 유효성 검사 컨트롤은 HTML 및 웹 서버 컨트롤을 포함하여 Web Forms 페이지 클래스 파일에서 처리되는 모든 컨트롤과 함께 사용할 수 있습니다.

2 사용자 입력에 유효한 값만 포함되어 있는지 확인 ​​

[1] "RangeValidator": 사용자 입력(값)이 유효한지 확인합니다. 상한과 하한 경계 사이에 지정되어 있습니다. 날짜 내에서 일치하는 숫자, 알파벳 문자 및 범위를 확인할 수 있습니다.

[2] "RegularExpressionValidator": 항목이 정규식에 정의된 패턴과 일치하는지 확인합니다. 이러한 유형의 검증을 통해 주민등록번호, 이메일 주소, 전화번호, 우편번호 등에서 발견되는 예측 가능한 문자 시퀀스를 확인할 수 있습니다. 교차 사이트 스크립팅을 방지하는 데 도움이 될 수 있는 정규식의 예:

- 기본적인 교차 사이트 스크립팅 변형을 거부할 수 있는 정규식은 다음과 같습니다. ^([^<]|<[^a -zA-Z])*[<]?$

- 위의 모든 문자를 거부하는 일반적인 정규 표현식은 다음과 같습니다: ^([^a8093152e673feb7aba1828c43532094"'%;)(& +]* )$

중요 참고 사항: 유효성 검사 컨트롤은 사용자 입력을 방지하거나 페이지 처리 흐름을 변경하지 않으며 오류 상태를 설정하고 오류 메시지를 생성할 뿐입니다. 작업을 실행하기 전에 코드의 컨트롤 상태를 테스트하세요.

사용자 입력의 유효성을 확인하는 방법에는 두 가지가 있습니다.

1. 일반 오류 상태를 테스트하세요.

코드에서 테스트 페이지의 IsValid 속성은 페이지에 있는 모든 유효성 검사 컨트롤의 IsValid 속성 값을 집계합니다(논리 AND 사용). 유효성 검사 컨트롤 중 하나가 유효하지 않은 것으로 설정되면 페이지 속성은 false를 반환합니다.

2. 개별 컨트롤의 오류 상태를 테스트합니다.

在页面的“验证器”集合中循环,该集合包含对所有验证控件的引用。 然后,您就可以检查每个验证控件的 IsValid 属性。

3 编码输入

        建议使用 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));
  }

0x05 J2EE

1 输入数据验证

     虽然为了用户的方便,可以提供“客户端”层数据的数据验证,但必须使用 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.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 敏感字符: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 &#39;<&#39;:
                  result.append("&lt;");
                  break;
              case &#39;>&#39;: 
                  result.append("&gt;");
                  break;
              case &#39;"&#39;: 
                  result.append("&quot;");
                  break;
              case &#39;\&#39;&#39;: 
                  result.append("&#39;");
                  break;
              case &#39;%&#39;: 
                  result.append("&#37;");
                  break;
              case &#39;;&#39;: 
                  result.append("&#59;");
                  break;
              case &#39;(&#39;: 
                  result.append("&#40;");
                  break;
              case &#39;)&#39;: 
                  result.append("&#41;");
                  break;
              case &#39;&&#39;: 
                  result.append("&amp;");
                  break;
              case &#39;+&#39;:
                  result.append("&#43;");
                  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);

2 错误处理

      许多 J2EE Web 应用程序体系结构都遵循“模型视图控制器MVC)”模式。在该模式中,Servlet 扮演“控制器”的角色。Servlet 将应用程序处理委派给 EJB 会话 Bean(模型)之类的 JavaBean。然后,Servlet 再将请求转发给 JSP(视图),以呈现处理结果。Servlet 应检查所有的输入、输出、返回码、错误代码和已知的异常,以确保实际处理按预期进行。

数据验证可保护应用程序免遭恶意数据篡改,而有效的错误处理策略则是防止应用程序意外泄露内部错误消息(如异常堆栈跟踪)所不可或缺的。好的错误处理策略会处理以下项:

[1] 定义错误

[2] 报告错误

[3] 呈现错误

[4] 错误映射

[1] 定义错误

         应避免在应用程序层(如 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);

[2] 报告错误

有两种方法可报告 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”变量初始化为所抛出的异常对象。

[3] 呈现错误

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&#39;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),以迭代处理并呈现错误消息,如以上示例所示。

[4] 错误映射

通常情况下,“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>


0x06 PHP

1  输入数据验证

      虽然为方便用户而在客户端层上提供数据验证,但仍必须始终在服务器层上执行数据验证。客户端验证本身就不安全,因为这些验证可轻易绕过,例如,通过禁用 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 敏感字符:6d267e5fab17ea8bc578f9e7e5e1570b " ' % ; ) ( & +PHP 包含一些自动化清理实用程序函数,如 htmlentities():

  $input = htmlentities($input, ENT_QUOTES, &#39;UTF-8&#39;);

此外,为了避免“跨站点脚本编制”的 UTF-7 变体,您应该显式定义响应的 Content-Type 头,例如:

  <?php
  header(&#39;Content-Type: text/html; charset=UTF-8&#39;);
  ?>

[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);
  ?>


위 내용은 XML 주입 및 코드 방어에 대한 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.