一般
有多種減輕威脅的技巧:
[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("<");
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/