通常
有多種減輕威脅的技巧:php
[1] 策略:庫或框架html
使用不容許此弱點出現的通過審覈的庫或框架,或提供更容易避免此弱點的構造。java
[2] 策略:參數化mysql
若是可用,使用自動實施數據和代碼之間的分離的結構化機制。這些機制也許可以自動提供相關引用、編碼和驗證,而不是依賴於開發者在生成輸出的每一處提供此能力。程序員
[3] 策略:環境固化web
使用完成必要任務所需的最低特權來運行代碼。正則表達式
[4] 策略:輸出編碼sql
若是在有風險的狀況下仍須要使用動態生成的查詢字符串或命令,請對參數正確地加引號並將這些參數中的任何特殊字符轉義。數據庫
[5] 策略:輸入驗證假定全部輸入都是惡意的。使用「接受已知善意」輸入驗證策略:嚴格遵照規範的可接受輸入的白名單。拒絕任何沒有嚴格遵照規範的輸入,或者將其轉換爲遵照規範的內容。不要徹底依賴於經過黑名單檢測惡意或格式錯誤的輸入。可是,黑名單可幫助檢測潛在攻擊,或者肯定哪些輸入格式不正確,以至應當將其完全拒絕。express
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("<");
break;
case '>':
result.append(">");
break;
case '"':
result.append(""");
break;
case '\'':
result.append("'");
break;
case '%':
result.append("%");
break;
case ';':
result.append(";");
break;
case '(':
result.append("(");
break;
case ')':
result.append(")");
break;
case '&':
result.append("&");
break;
case '+':
result.append("+");
break;
default:
result.append(value.charAt(i));
break;
}
return result;
}
...
}
...
// Filter the HTTP response using Validator.filter
PrintWriter out = response.getWriter();
// set output response
out.write(Validator.filter(response));
out.close();
Java Servlet API 2.3 引進了「過濾器」,它支持攔截和轉換 HTTP 請求或響應。
如下示例使用 Validator.filter 來用「Servlet 過濾器」清理響應:
// Example to filter all sensitive characters in the HTTP response using a Java Filter.
// This example is for illustration purposes since it will filter all content in the response, including HTML tags!
public class SensitiveCharsFilter implements Filter {
...
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
PrintWriter out = response.getWriter();
ResponseWrapper wrapper = new ResponseWrapper((HttpServletResponse)response);
chain.doFilter(request, wrapper);
CharArrayWriter caw = new CharArrayWriter();
caw.write(Validator.filter(wrapper.toString()));
response.setContentType("text/html");
response.setContentLength(caw.toString().length());
out.write(caw.toString());
out.close();
}
...
public class CharResponseWrapper extends HttpServletResponseWrapper {
private CharArrayWriter output;
public String toString() {
return output.toString();
}
public CharResponseWrapper(HttpServletResponse response){
super(response);
output = new CharArrayWriter();
}
public PrintWriter getWriter(){
return new PrintWriter(output);
}
}
}
}
[8-2] 保護 cookie
在 cookie 中存儲敏感數據時,確保使用 Cookie.setSecure(布爾標誌)在 HTTP 響應中設置 cookie 的安全標誌,以指導瀏覽器使用安全協議(如 HTTPS 或 SSL)發送 cookie。
保護「用戶」cookie 的示例:
// Example to secure a cookie, i.e. instruct the browser to
// send the cookie using a secure protocol
Cookie cookie = new Cookie("user", "sensitive");
cookie.setSecure(true);
response.addCookie(cookie);
推薦使用的 JAVA 工具用於服務器端驗證的兩個主要 Java 框架是:
[1] Jakarta Commons Validator(與 Struts 1.1 集成)Jakarta Commons Validator 實施全部以上數據驗證需求,是強大的框架。這些規則配置在定義表單字段的輸入驗證規則的 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/