ESAPI是owasp提供的一套API級別的web應用解決方案,本人通過對ESAPI和其提供的demo源碼學習發現,關鍵的不是對其所提供的API的使用,而是其web應用安全防御體系的構建的思想。比如,您不一定要使用ESAPI去實現日志系統,而是應該明白,一套好的日志系統應該是怎么樣子的,應具備什么樣的特性等。
此外,在引入使用時可能會遇到不少麻煩,所以讀者應根據自身的業務特性和需求進行整合,而不是一味的去粘貼和拼接。
1、Authentication
測試用於登錄認證的ESAPI接口的步驟:
1.1、創建用戶(方法的實現請參考FileBasedAuthenticator .java)
1.1.1、Authenticator instance = ESAPI.authenticator(); //實例化
1.1.2、User user = instance.createUser(accountName, password, password); //在users.txt 中創建一個新用戶,【遺留問題:在createUser()中並未發現是寫到users.txt文件】
1.1.3、instance.getUser(accountName).enable(); //使能賬戶
1.1.4、instance.getUser(accountName).unlock(); //解鎖賬戶
1.2、用戶登錄(方法的實現請參考AbstractAuthenticator .java)
/*
1.2.1、必須預先在ESAPI.properties文件中設置UsernameParameterName和PasswordParameterName參數,因為login()會使用這兩個參數的值去http請求中獲取用戶輸入的用戶名和密碼。比如說,如果UsernameParameterName的值為username,那么login()會在http請求中去找username字段並獲取其值(用戶名)。
1.2.2、該函數用來檢查發送該請求的用戶是否已登錄,如果沒有就重定向讓其登錄,內部實現的具體步驟如下:
1) 檢查用戶是否已登錄並存在會話中
a. 如果是,確認會話最長時間和閑置操作時間是否到達
b. 如果步驟1滿足條件則步驟可跳過
2) 如果不滿足步驟1,則需要對用戶的憑證(用戶名和密碼)進行有效驗證
a.建議使用ESAPI接口
loginWithUsernameAndPassword(HttpServletRequest, HttpServletResponse)
接口來驗證用戶憑證
3) 記錄用戶本次登錄的主機IP
4) 驗證本次請求的方式是否是安全的(比如POST+SSL)
5) 驗證用戶賬戶是否允許登錄
a. 驗證用戶沒有被禁用、過期和鎖定等
6) 將用戶分配到會話變量中
*/
【遺留問題:同一個用戶重復登錄時,是否只創建一個user對象再綁定多個session?】
User user = ESAPI.authenticator().login(HTTPServletRequest, HTTPServletResponse);
DefaultUser user = (DefaultUser) getUserFromSession();//檢查用戶是否已登錄
HttpSession session = ESAPI.httpUtilities().getCurrentRequest().getSession(false);
if (session == null) return null;
return ESAPI.httpUtilities().getSessionAttribute(USER);
user = getUserFromRememberToken();//【遺留問題:選”記住我”才會用到,未理解】
user = (DefaultUser) loginWithUsernameAndPassword(request);//如果沒登錄則要求用戶進行認證(hash[username+id]值和users.txt對比),並返回user對象
user.setLastHostAddress(request.getRemoteHost());//記錄用戶最后一次成功登錄的主機IP
ESAPI.httpUtilities().assertSecureRequest(ESAPI.currentRequest());//驗證請求是否使用POST+SSL方式傳輸
if (user.isAnonymous()||!user.isEnabled()||user.isLocked()||user.isExpired()||user.isSessionTimeout()||user.isSessionAbsoluteTimeout())//各種用戶狀態條件判斷
user.logout();//強制用戶注銷
user.setLocale(request.getLocale());//【遺留問題:未理解】
/* 為用戶創建會話 */
HttpSession session = request.getSession();//如果當前請求不存在有效session則創建一個,如果存在就返回當前的
user.addSession(session);//將會話綁定到user對象中,一個用戶可能有多個session?
session.setAttribute(USER, user);//user對象存儲到USER
setCurrentUser(user);//將用戶設置為當前登錄用戶
1.3、用戶注銷
User user = ESAPI.authenticator().logout;
1.4、關於ESAPI.authenticator().login(HTTPServletRequest, HTTPServletResponse)對每個請求的處理邏輯。
![]() |
2、Session Management
2.1、用戶登錄后更新session id的接口(防止會話定置攻擊):
javax.servlet.http.HttpSession ESAPI.httpUtilities().changeSessionIdentifier(javax.servlet.http.HttpServletRequest request);
接口內部實現說明:將當前session數據拷貝到新session,並使當前session失效,並將新session綁定到當前user對象中,並返回新的session。
2.2、為URL增加CSRF token
2.2.1、為指定URL增加CSRF TOKEN
String transferFundsHref = "/SwingSet/main?function=TransferFunds&lab";
<a href='<%=ESAPI.httpUtilities().addCSRFToken(transferFundsHref)%>' >Transfer Funds</a>
說明:在url末尾添加的csrftoken值等於user.getCSRFToken()
2.2.2、在transferFundsHref的頁面里驗證request攜帶的和user的CSRF token是否一致。
ESAPI.httpUtilities().verifyCSRFToken();
if ( !user.getCSRFToken().equals( token ) ) { //驗證兩者是否一致
throw new IntrusionException("Authentication failed",
3、AccessControl
3.1、角色權限驗證:
3.1.1、接口:ESAPI.accessController().assertAuthorizedForURL(java.lang.String url)
3.1.2、說明:檢查當前用戶對應的角色是否有權限訪問url,權限的定義在esapi\fbac-policies\URLAccessRules.txt文件中。
3.1.3、接口源碼分析:
ESAPI.accessController().assertAuthorizedForURL(string url)
this.assertAuthorized("AC 1.0 URL", new Object[] {url});
/*
在esapi\ESAPI-AccessControlPolicy.xml文件中獲取name為“AC 1.0 URL”的訪問控制信息:
*/
AccessControlRule rule = (AccessControlRule)ruleMap.get(key);
/*
將key對應的(此例key等於"AC 1.0 URL")對應的訪問控制信息(如上圖)作為 AccessControlRule接口的構造函數的入參(參考 AccessControlRule.java), 即順序調用了以下3個函數:
void setPolicyParameters(P policyParameter);//P為訪問控制信息
P getPolicyParameters();//該函數未重寫,默認為空,故不執行
boolean isAuthorized(R runtimeParameter);//此處R為空,故在下面調用
*/
isAuthorized = rule.isAuthorized(runtimeParameter);
/*
此處runtimeParameter等於string url,經過setPolicyParameters()的設置,此 處調用isAuthorized()實際上等於調用FileBaseACRs.java中定義的 isAuthorizedForURL(),即實際效果等於:isAuthorizedForURL(string url),請參 考第4、5點的分析。
*/
3.1.4、void setPolicyParameters()的分析(源碼在DelegatingACR.java):
void setPolicyParameters(DynaBeanACRParameter policyParameter)
delegateClassName =
policyParameter.getString("delegateClass", "").trim();
methodName =
policyParameter.getString("delegateMethod", "").trim();
parameterClassNames =
policyParameter.getStringArray("parameterClasses");
/*
由以上截圖可知,此例的delegateClassName為:
org.owasp.esapi.reference.accesscontrol.FileBasedACRs;
methodName為:isAuthorizedForURL;
parameterClassNames為:java.lang.String
*/
Class delegateClass = getClass(delegateClassName, "delegate");
Class parameterClasses[] = getParameters(parameterClassNames);
/*
通過getClass獲取FileBasedACRs類,【疑問:parameterClasses[]是什么呢?】
*/
this.delegateMethod = delegateClass.getMethod(methodName, parameterClasses);
/*
根據方法名來從FileBasedACRs類中獲取方法,此例實際為isAuthorizedForURL()
*/
3.1.5、boolean isAuthorized ()的源碼分析
boolean isAuthorized(Object[] runtimeParameters)
return ((Boolean)delegateMethod.invoke(delegateInstance,
runtimeParameters)).booleanValue();
/*
調用FileBasedACRs類中定義的回調函數,此例實際為isAuthorizedForURL(), runtimeParameters為要檢查的內容,比如要訪問的URL、service、function和file等, 此列為URL。
*/
3.1.6、boolean isAuthorizedForURL()的源碼分析
urlMap = loadRules("URLAccessRules.txt");
/*
故實際定義URL訪問控制權限的文件是URLAccessRules.txt,其它類似的還有 FunctionAccessRules.txt和DataAccessRules.txt等等,由以上的分析可知,實
際上最終會對哪個權限控制文件做檢查是由void assertAuthorized(Object key,
Object runtimeParameter)的key決定的,key的值都在ESAPI-AccessControlPolicy.xml 文件中定義了,目前可能的值為"AC 1.0 Data"、"AC 1.0 File"、"AC 1.0 Function"、"AC 1.0 Service"、"AC 1.0 URL"。
補充說明: ESAPI提供的頂層接口(比如assertAuthorizedForURL()等)已經幫我們定義 好了要調用的key值了(如本例),因此實際上只需要修改權限控制文件 (URLAccessRules.txt、FunctionAccessRules.txt等)就可以了。
*/
return matchRule(urlMap, url);
/*
在URLAccessRules.txt檢查當前用戶的角色以及該角色是否有訪問url的權限,
URLAccessRules.txt內容如下圖所示:
*/
【遺留問題】:
是不是在過濾器中設置ESAPI的ESAPI.authenticator().login()和ESAPI.accessController().assertAuthorizedForURL()就可以解決每一個請求的認證和權限驗證問題了?
3.2、直接對象引用:
3.2.1、使用隨機映射表對象的樣例代碼:
<%@ page import="org.owasp.esapi.reference.RandomAccessReferenceMap"%>
<%
Set fileSet = new HashSet();
ArrayList list = new ArrayList();
list.add(msgID0); //將需要映射的資源的直接引用名(此處為message id)添加到list
list.add(msgID1);
……
fileSet.addAll(list);
RandomAccessReferenceMap MsgIDMap = new RandomAccessReferenceMap(fileSet);
/*調用ESAPI接口生成隨機映射表對象*/
HttpSession sess = ESAPI.httpUtilities().getCurrentRequest().getSession(false);
sess.setAttribute("MsgIDMap ", MsgIDMap);
/*
此時應將映射表對象存放到安全的地方,比如:session
*/
String indirect = instance.getIndirectReference(msgID0));
/*
獲取要被訪問的資源的間接引用值,該值由ESAPI接口隨機生成,並且每次進行映 射時值都會動態產生變化,因此,如果用戶每次登錄時進行重映射,那么用戶在瀏 覽器端看到的間接引用值也會每次都不一樣,也大大增加了防御CSRF攻擊的能力。
*/
String href = "http://www.test.com?messageid=";
%>
<a href="<%=href + indirect %>">點擊讀取消息;
/*
將提供給用戶讀取消息的鏈接返回給用戶,用戶最終看到的鏈接將帶上間接引用值。
例如:http://www.test.com?messageid=DfDsQK
*/
【說明】:當要映射的資源較多時,使用資源映射表可能會消耗較多的內存。
【遺留問題】:同一個用戶重復登錄都要重新映射?不同類型的資源(比如消息id、用戶id和設備id等等)應當映射到不同的表中還是可以全部映射到一個表中?
3.2.2、處理來自用戶請求的樣例代碼:
<%
HttpSession sess = ESAPI.httpUtilities().getCurrentRequest().getSession(false);
RandomAccessReferenceMap MsgIDMap =
(RandomAccessReferenceMap) sess.getAttribute("MsgIDMap ");
/*
從當前登錄用戶的session中還原出資源的隨機映射表對象。
*/
String indMsgID = request.getParameter( " messageid " );
String directMsgID = (String) MsgIDMap.getDirectReference(indMsgID);
/*
獲取客戶端傳過來的間接索引值,再通過間接索引值得到直接引用對象的值,如果 該值為空,表示用戶傳過來了一個非法的間接索引值,此時getDirectReference 會 拋出一個AccessControlException異常,應用系統應在異常中做適當處理,比如將 該異常行為記錄到日志中。
*/
%>
3.2.3、服務器處理攜帶用戶資源標簽請求的流程圖
4、Input Validation
4.1、使用自定義的正則表達式規則驗證用戶的輸入數據。
4.1.1、接口說明:
接口1:
java.lang.String getValidInput(java.lang.String context, java.lang.String input, java.lang.String type, int maxLength, boolean allowNull)
功能說明:使用esapi/validation.properties文件中定義的正則表達式去驗證用戶的輸入數據。
參數說明:
context:任意定義的字符串,主要用於日志打印信息。
Input:需要驗證的輸入字符。
Type:選擇需要使用的一種正則表達式(見4.1.2)。
MaxLenth:允許輸入字符的最大長度。
AllowNull:是否允許輸入空字符(true為允許,false為不允許)。
返回:經過規范化和驗證過濾后的字符串【遺留問題:實際驗證時好像未返回字符串】
接口2:
isValidInput(java.lang.String context, java.lang.String input, java.lang.String type, int maxLength, boolean allowNull)
功能與getValidInput()一樣,但不返回字符串,僅驗證通過時返回true,否則返回false。
4.1.2、validation.properties文件的正則表達式定義如下:
4.1.3、樣例代碼:
String input = request.getParameter("input");
type = "SafeString"; //使用validation.properties文件定義的SafeString正則表達式規則
try {
ESAPI.validator().getValidInput(
"Swingset Validation Secure Exercise", input, type,
200, false);
} catch (ValidationException e) {
/*當檢測到輸入字符串不匹配正則表達式時產生該異常,應進行適當處理*/
input = "Validation attack detected";
request.setAttribute("userMessage", e.getUserMessage());
request.setAttribute("logMessage", e.getLogMessage());
} catch (Exception e) {
input = "exception thrown";
request.setAttribute("logMessage", e.getMessage());
}
接口3:void assertValidFileUpload(java.lang.String context,
java.lang.String filepath, java.lang.String filename, java.io.File parent, byte[] content, int maxBytes, java.util.List<java.lang.String> allowedExtensions, boolean allowNull)
功能說明:對上傳文件的路徑、文件名、擴展名和文件大小進行檢查。
參數說明:略。
函數分析:
void assertValidFileUpload(String context, String directorypath, String filename, File parent, byte[] content, int maxBytes, List<String> allowedExtensions, boolean allowNull)
getValidFileName( context, filename, allowedExtensions, allowNull );
getValidInput( context, input, "FileName", 255, true );
getValidDirectoryPath( context, directorypath, parent, allowNull );
fileValidator.getValidInput( context, canonicalPath, "DirectoryName", 255, false);
getValidFileContent( context, content, maxBytes, allowNull );
esapiMaxBytes = ESAPI.securityConfiguration().getAllowedFileUploadSize();
/* FileName和DirectoryName 正則表達式都在ESAPI.properties全局文件中定義*/
注意事項:如果沒有canonicalize參數,表示該API默認對輸入數據進行canonicalize,否則需要使用canonicalize參數去指定是否需要對輸入數據進行canonicalize,一般情況下,我們都默認使用不帶canonicalize參數的API,表示默認對輸入數據進行canonicalize。
更多owasp數據驗證API請參考在線官方文檔:
URL:http://owasp-esapi-java.googlecode.com/svn/trunk_doc/latest/index.html
接口路徑:org.owasp.esapi->Validator->Method Summary
4.2、在富文本應用系統中防御XSS攻擊。
應用系統允許用戶輸入自定義的html代碼,我們稱之為“富文本”,此類系統容許類似<,>之類的危險標簽的存在(即輸出時原樣攜帶<,>這樣的標簽),此場景下我們需要以下API:
java.lang.String getValidSafeHTML(java.lang.String context, java.lang.String input, int maxLength, boolean allowNull)
功能說明:對用戶輸入的富文本數據進行有效過濾,防止XSS。
參數說明:
context:任意定義的字符串,主要用於日志打印信息。
Input:需要驗證的富文本輸入數據。
maxLenth:允許輸入字符的最大長度。
AllowNull:是否允許輸入空字符(true為允許,false為不允許)。
返回:經過規范化和驗證的安全的html內容,該內容中的body、attributes、CSS、URLs等都不會包含惡意的腳步代碼。
樣例:
輸入:<p>test <b>this</b> <script>alert(document.cookie)</script><i>right</i> now</p>
輸出:<p>test <b>this</b> alert(document.cookie)<i>right</i> now</p>
如果此時對<,>標簽進行html編碼再輸出就會改變原意,達不到想要的顯示效果:<p>test <b>this</b> <i>right</i> now</p>
5、Output Encoding/Escaping
5.1、Canonicalize
術語解釋:canonicalize是指將數據轉換或解碼成一個常見字符集的過程。這里的數據可以是經過多重混合編碼的。比如:%3cscript%3ealert(%22xss%22)%3c%2fscript%3e還原成<script>alert("xss")</script>。在對輸入數據進行過濾之前應進行規范化!
接口1:java.lang.String
canonicalize(java.lang.String input, boolean strict)
功能說明:將編碼過的數據還原為其最簡化的形式。
參數說明:
Input:輸入數據。
strict:指定是否對輸入數據進行多重和混合編碼的檢測,true時將觸發IntrusionException,但不返回數據,而false將直接返回canonicalize后的數據。
返回:canonicalize后的數據。
5.2、對輸出到瀏覽器的數據進行編碼/轉義
以下內容摘自《web安全開發規范v1.1》à反射型、存儲型XSS安全規則:
將用戶數據輸出到html body某處時,必須經過html轉義,比如: <body>...【用戶數據】...</body> <div>...【用戶數據】...</div> 以及其它普通的html標簽,比如p, b, td等等。 ESAPI sample: String safe = ESAPI.encoder().encodeForHTML( request.getParameter( "input" ) ); |
將用戶數據輸出到html 標簽的屬性時,必須經過標簽屬性的轉義。 注意:不包含href, src, style和事件處理函數屬性(比如onmouseover)。 <div attr=...【用戶數據】...>content</div> //數據不在引號內 <div attr='... 【用戶數據】...'>content</div> //數據在單引號內 <div attr="...【用戶數據】...">content</div> //數據在雙引號內 ESAPI sample: String safe = ESAPI.encoder().encodeForHTMLAttribute( request.getParameter( "input" ) ); |
將用戶數據輸出到JavaScript數據域時,必須經過JavaScript轉義。 注意:用戶數據必須在引號內,否則轉義后數據仍然是不安全的。 <script>alert('... 【用戶數據】...')</script> //數據在帶引號的字符串內 <script>x='... 【用戶數據】...'</script> //數據在帶引號的表達式內 <div onmouseover="x='... 【用戶數據】...'"</div> ESAPI sample: String safe = ESAPI.encoder().encodeForJavaScript( request.getParameter( "input" ) ); |
將用戶數據輸出到URL的參數時,必須經過URL轉義。 <a href="http://www.somesite.com/x/y/z?test=...【用戶數據】...">link</a > ESAPI sample: String safe = ESAPI.encoder().encodeForURL( request.getParameter( "input" ) ); 注意:所有基於URL的標簽屬性(比如href,src等),如果其整個或相對的URL被用戶數據控制,比如: <a href=” ...【用戶數據】...”>link</a> 則應先驗證URL的有效性,再進行html標簽屬性的轉義: String userURL = request.getParameter( "userURL" ) boolean isValidURL = ESAPI.validator().isValidInput("URLContext", userURL, "URL", 255, false); if (isValidURL) { <a href="<%=encoder.encodeForHTMLAttribute(userURL)%>">link</a> } |
除了以上4條規則定義的安全上下文以外,其它上下文都是無法安全地轉義的,應避免出現,比如: <script>...【用戶數據】...</script> //直接輸出到js標簽內 <!--...【用戶數據】...--> //直接輸出到注釋內 <div ... 【用戶數據】...=test /> //直接輸出到標簽屬性名 <..【用戶數據】... href="/test" /> //作為標簽名使用 <style>...【用戶數據】...</style> //直接輸出到CSS …… |
5.3、對輸出到解析器的數據進行編碼/轉義:
接口1:java.lang.String encodeForSQL(Codec codec, java.lang.String input)
功能說明:對SQL查詢語句中用戶控制的輸入數據進行編碼/轉義。
參數說明:
codec:編碼器,編碼器的種類請參考在線接口文檔àorg.owasp.esapi.codecs,針對數據查詢的編碼器有MySQLCodec和OracleCodec,故目前只支持mysql和oracle數據庫。
input:SQL查詢輸入數據。
返回:編碼/轉義后的數據。
樣例代碼:
以下內容摘自《web安全開發規范v1.1》
Codec MYSQL_CODEC = new MySQLCodec ();//實例化編碼器()
String query = "SELECT user_id FROM user_data WHERE user_name = '" +
ESAPI.encoder().encodeForSQL(MYSQL_CODEC,req.getParameter("userID"))+"'and user_password = '"+ ESAPI.encoder().encodeForSQL(MYSQL_CODEC, req.getParameter("pwd")) +"'";
編碼結果樣例:
輸入:foo" and 1 = 2
輸出:foo\" and 1 \= 2
接口2:java.lang.String encodeForOS(Codec codec, java.lang.String input)
功能說明:對傳遞給OS shell的輸入數據進行編碼/轉義。
參數說明:
codec:編碼器,編碼器的種類請參考在線接口文檔àorg.owasp.esapi.codecs,針對OS command shell的編碼器有WindowsCodec和UnixCodec,故目前只支持mysql和oracle數據庫。
input:要傳遞給OS shell的輸入數據。
返回:編碼/轉義后的數據。
樣例代碼:需要開發人員提供。
接口3:java.lang.String encodeForLDAP(java.lang.String input)
功能說明:對LDAP查詢數據的編碼/轉義。
參數說明:LDAP查詢輸入數據
更多API請參考在線接口文檔:org.owasp.esapiàEncoder。
6、Cryptography
6.1、加、解密
接口1:CipherText encrypt(PlainText plaintext)
功能說明:對plaintext數據進行加密,加密的算法/模式/填充方式和加密密鑰在ESAPI.properties文件中定義(默認AES128/CBC/PKCS5Padding)。
參數說明:
plaintext:需要加密的明文數據。
返回:加密后的數據。
樣例代碼:
String decryptedStr = request.getParameter("decrypted");
encrypted = ESAPI.encryptor().encrypt(decryptedStr);
注意事項:該接口在對需要加密的明文添加當前時間信息,因此每次加密出來的密文都不相同,在解密時將時間信息刪除即可得到原始明文。【遺留問題:添加時間信息能增強安全性?】
接口2:PlainText decrypt(CipherText ciphertext)
功能說明:對ciphertext數據進行解密。
參數說明:
ciphertext:需要解密的密文。
返回:解密后的明文數據。
樣例代碼:
String encryptedStr = request.getParameter("encrypted");
decrypted = ESAPI.encryptor().decrypt(encryptedStr);
接口3:java.lang.String hash(java.lang.String plaintext,java.lang.String salt,int iterations)
功能說明:對數據進行哈希。
參數說明:
plaintext:需要hash的數據。
Salt:用於hash的salt。
Iterations:要迭代的次數【遺留問題:迭代是指重復哈希?如果是,每次都需要加salt嗎?】。
返回:hash值。
樣例代碼:
String hashStr = request.getParameter("hash");
hashVal = ESAPI.encryptor().hash(hashStr,”ABCDEFG”,3);
更多加密解密API請參考在線官方文檔:org.owasp.esapiàEncryptor
6.2、隨機數
接口1:獲取隨機字符串:
以下參考《web安全開發指南v1.1》
接口:java.lang.String ESAPI.randomizer().getRandomString(int length,char[] characterSet)
說明:從固定字符數組characterset中獲取長度為length的隨機字符串,本接口可用於生成各種自定義長度和復雜度的隨機token,比如CSRF同步token等。
樣例代碼:
char[] CHAR_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8'};
char[] CHAR_LETTERS = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'};
char[] CHAR_ALPHANUMERICS = StringUtilities.union(CHAR_LETTERS, CHAR_DIGITS);
ESAPI.randomizer().getRandomString(6,CHAR_ALPHANUMERICS);
執行結果樣例:0edec6,cjefic等6位隨機字符串。
接口2:獲取隨機GUID:
接口:java.lang.String ESAPI.randomizer().getRandomGUID()
說明:產生32位的GUID格式的隨機字符串。
樣例代碼:略
執行結果樣例:53110e13-804a-4041-a547-09399c2791ea等
接口3:獲取隨機整數:
接口:int ESAPI.randomizer().getRandomInteger(int min,int max)
說明:獲取min到max范圍的隨機整數。
樣例代碼:ESAPI.randomizer().getRandomInteger(-100,100)
執行結果樣例:-11、55等。
接口4:獲取隨機文件名:
接口:java.lang.String getRandomFilename(java.lang.String extension)
說明:生成隨機字符串作為文件名,並添加extension作為文件擴展名。
樣例代碼:ESAPI.randomizer().getRandomFilename("exe")
執行結果樣例:X4f0cafF0Mkj.exe、CzkmUsLKFx7c.exe等。
接口5:獲取隨機長整數:
接口:long ESAPI.randomizer().getRandomLong()
說明:獲取隨機長整數。
樣例代碼:略
執行結果樣例:8712910770684726225、6703356794875845559等。
更多隨機函數接口請參考:
http://owasp-esapi-java.googlecode.com/svn/trunk_doc/latest/index.html
org.owasp.esapiàRandomizer
7、Error Handling and Logging
7.1、日志記錄
7.1.1、日志接口說明:
ESAPI日志接口定義了6個級別的日志等級:
- fatal (highest value)
- error
- warning
- info
- debug
- trace (lowest value)
同時也預定義了4類日志事件:SECURITY_SUCCESS, SECURITY_FAILURE, EVENT_SUCCESS, EVENT_FAILURE,用於區分安全相關的事件和普通事件。
和日志相關的配置參數在ESAPI.properties設置,可配置參數:
Logger.ApplicationName:應用程序名,表示日志是哪個應用程序產生的。
Logger.LogEncodingRequired:true表示當日志包含特殊字符時自動進行轉義后再記錄。
Logger.LogApplicationName:true表示在日志中記錄應用程序嗎。
Logger.LogServerIP:true表示在日志中記錄服務器的IP。
Logger.LogFileName:日志的名稱(必須預先在存在)。
Logger.MaxLogFileSize:單個日志文件的最大容量,超過時自動切到另外一個日志文件。
7.1.2、接口:
原型:
Logger getLogger(java.lang.String moduleName)
參數說明:
moduleName:logger使用來標記該日志是應用程序的哪個模塊產生的。
原型:
Void fatal(Logger.EventType type, java.lang.String message)
參數說明:
Type:事件類型。
Message:要記錄的日志信息。
與logger相關的方法和參數請參考ESAPI在線幫忙文檔:
org.owasp.esapiàlogger
7.1.2、樣例代碼:
WriterAppender appender = new WriterAppender(new SimpleLayout(), out);
org.apache.log4j.Logger.getRootLogger().addAppender(appender);
Logger logger = ESAPI.getLogger("LoggingTutorialModule");
logger.fatal(Logger.SECURITY_FAILURE, " <script>alert('123')</script>");
logger.debug(Logger.EVENT_SUCCESS, " <scr<script>ipt>alert('abc')</script> ");
org.apache.log4j.Logger.getRootLogger().removeAppender(appender);
7.1.3、輸出結果:
181681687 [http-8443-2] FATAL SwingSetInteractive:LoggingTutorialModule - [SECURITY FAILURE Anonymous:308755@unknown -> /SwingSetInteractive/LoggingTutorialModule] <script>alert('123')</script> (Encoded)
181681687 [http-8443-2] DEBUG SwingSetInteractive:LoggingTutorialModule - [EVENT SUCCESS Anonymous:308755@unknown -> /SwingSetInteractive/LoggingTutorialModule] <scr<script>ipt>alert('abc')</script> (Encoded)
在與具體業務相結合時,應提供足夠的信息作為回溯的依據,應提供的信息如下:
導致事件發生的源對象(比如用戶id,用戶名等);
對事件的描述;
事件的結果(成功或失敗);
事件的嚴重等級(在具體的業務事件中定義,然后調用ESAPI接口執行);
事件的類型(在具體的業務事件中定義,然后調用ESAPI接口執行);
發生事件的源IP和目標主機IP;
發生事件的時間。
7.2、錯誤處理
8、Data Protection
8.1、數據緩存
接口:
void setNoCacheHeaders(javax.servlet.http.HttpServletResponse response)
功能說明:為指定的response設置Cache-Control和Expires header。
參數說明:
response:指定的http response,當該參數為空時表示當前request的對應的response。
返回:無
樣例代碼:
<%
ESAPI.httpUtilities().setNoCacheHeaders();
%>
返回的http response中包含的緩存控制header如下:
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Expires: Wed, 31 Dec 1969 23:59:59 GMT
更多和數據安全相關的接口請參考:
org.owasp.esapià HTTPUtilities
9、Http Security
9.1、URL重定向
9.1.1、接口:
void sendRedirect(javax.servlet.http.HttpServletResponse response,java.lang.String location)
功能說明:對需要重定向的參數進行判斷,如果重定向的URL不符合"Redirect"正則表達式的要求,則觸發異常,並重定向回主頁。
參數說明:
response:指定的http response,當該參數為空時表示當前request的對應的response。
location:重定向的URL。
樣例代碼:
<%
String redirect = request.getParameter("redirect");
if (redirect != null){
//response.sendRedirect(redirect);
ESAPI.httpUtilities().sendRedirect(redirect);
}
%>
9.1.2、對sendRedirect的源代碼分析:
void sendRedirect(javax.servlet.http.HttpServletResponse response,java.lang.String location)
ESAPI.validator().isValidRedirectLocation("Redirect", location, false)
ESAPI.validator().isValidInput( context, input, "Redirect", 512, allowNull);
/*通過ESAPI.properties文件中的Redirect正則表達式進行過濾判斷:
Validator.Redirect=^/SwingSet/.+,此處使用的是相對路徑,表示ESAPI只允許 重定向到當前站點的URL*/
response.sendRedirect(location);
10、Interface HTTPUtilities
接口1:
void addCookie(javax.servlet.http.Cookie cookie)
功能描述:添加新的cookie,並為新cookie設置http only和secure屬性。
接口2:void addHeader(java.lang.String name,java.lang.String value)
功能描述:為當前response添加新的header。
接口3:void assertSecureRequest() throws AccessControlException
功能描述:檢查當前request是否是安全的(ssl+post),如果不是則產生異常,服務器應調用該接口對所有包含敏感數據的request做檢查,保證敏感數據不會被嗅探、記錄到日志。
接口4:void assertSecureChannel() throws AccessControlException
功能描述:檢查當前request是否是通過SSL傳輸,如果不是則產生異常,服務器應調用該接口對所有包含敏感數據的request做檢查,保證敏感數據不會被嗅探。
接口5:java.lang.String getCookie(java.lang.String name)
功能描述:根據cookie name來獲取cookie的值。
接口6:java.lang.String getHeader(java.lang.String name)
功能描述:根據header name獲取當前request的header值