0x01 介紹
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);
在上述示例中,惡意使用者可以注入兩點(“..”)來隔開單獨的環境。
0x02 主要修複思路
若幹問題的補救方法在于對使用者輸入進行清理。通過驗證使用者輸入未包含危險字元,便可能防止惡意的使用者讓您的應用程式運作計劃外的操作,例如:啟用任意 SQL 查詢、嵌入運作于用戶端的 Javascript 代碼、運作各種作業系統指令,等等。
1 建議過濾出以下所有字元
[1] |(豎線符号)
[2] & (& 符号)
[3];(分号)
[4] $(美元符号)
[5] %(百分比符号)
[6] @(at 符号)
[7] '(單引号)
[8] "(引号)
[9] \'(反斜杠轉義單引号)
[10] \"(反斜杠轉義引号)
[11] <>(尖括号)
[12] ()(括号)
[13] +(加号)
[14] CR(回車符,ASCII 0x0d)
[15] LF(換行,ASCII 0x0a)
[16] ,(逗号)
[17] \(反斜杠)
替代補救方案驗證未清理的輸入是無害的。
2 好的使用者輸入驗證機制
[1] 正面驗證 -比對使用者輸入與已知可接受的值集(白名稱清單)、範圍或正規表達式。 如果找不到比對項,就以适當方式拒絕輸入。
[2] 間接選擇 -不直接使用使用者輸入。使用者輸入應該當作可接受值的散清單中的一個鍵來處理。
[3] 消息認證代碼(MAC)-如果需要允許的值的動态執行個體化,可以使用 HMAC 來保護它們。
這類 MAC 在連接配接了密鑰的值上運作加密散列函數而計算出來。将 HMAC 附加到請求之後,當接收請求時,再加以驗證,便可以使用 HMAC。這樣做可以確定值的完整性及真實性有效,不是惡意的使用者所僞造。在給定密鑰和資料之後,這個函數(用 Java 撰寫)會生成一個 HMAC.
3 驗證使用者輸入的下列屬性
[1] 參數類型
在 Web 應用程式中,輸入參數的類型欠佳。 例如,所有 HTTP 請求參數或 cookie 值的類型都是“字元串”。開發者負責驗證輸入的資料類型是否正确
[2] 參數長度
請確定輸入參數(HTTP 請求參數或 cookie 值)有最小長度和/或最大長度的限制。另外,除去會觸發問題的敏感 API 呼叫,也可以補救許多問題,例如,下列問題示例及其修訂建議:遠端執行碼:
[1] 清理輸入以排除對執行作業系統指令有意義的符号,例如:
[a] |(豎線符号)
[b] &(& 符号)
[c] ;(分号)
[d] .(點)
[2] 可能的話,請在更改根目錄的環境中運作 Web 應用程式
[3] 不讓使用者指定要直接裝入的本機圖像。 如果需要多重選擇,請使用白名稱清單、“間接選擇”或 HMAC。
4 代碼注入
[1] 清理使用者輸入的危險字元。 要清理的字元集,會随着求值的語言及所求值的表達式其中之輸入的上下文而不同。
[2] 避免利用使用者控制的資料來呼叫腳本評估程式
[3] 利用正面驗證機制(模式比對)。
5 SOAP 操作
[1] 避免将使用者控制的輸入傳到敏感的 SOAP 相關呼叫中
[2] 應用正面驗證機制(模式比對)。
[3] 清理使用者輸入。 建議您過濾下列字元,或将它們轉換成轉義的 XML 相等項:
[a] <>(尖括号)
[b] "(引号)
[c] '(單引号)
[d] &(& 符号)
[4] 可以的話,将使用者輸入放在 CDATA 部分中(也就是說,使用者輸入是字元)。 CDATA 部分會訓示 XML 引擎避免解析它包含的資料。 不過,請别忘了過濾出或轉義 CDATA 終止文本字元串 ("]]>")。
0x03 J2EE- 清理使用者輸入
以下是清理使用者輸入的 Java 特定機制,以及上述一般機制的 Java 實施:
[1] 不需要全部重新處理
在大部分情況下,必要的清理能力都已實施。Apache StringEscapeUtils(Apache Commons Lang 的一部分)已實施各種目标的清理方法(其中包括 HTML、XML、SQL,等等)。不過,如果未曾實施必要的清理方法,您可以撰寫自己的清理函數。以下是避免遭受“跨站點腳本編制”的清理方法:
<span style="font-size:14px;"> 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;
}
</span>
[2]架構自動清理
各種“Java Web 應用程式”架構都會自動清理傳播到“HTTP 響應”中的危險字元。其中包括 Apache 的 Struts,在預設情況下,它支援在使用 Struts 'bean:write' 标記撰寫的所有資料上,過濾 HTTP 響應中輸出的危險字元。
[3] Servlet 過濾器
Java Servlet API 2.3 引進了過濾器,它支援截取及變換 HTTP 請求或響應。以下為使用“Servlet 過濾器”來清理使用 StringEscapeUtils.escapeHtml 響應的示例:
<span style="font-size:14px;"> // 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);
}
}
}
</span>
引用A. Apache Commons Lang(包括 StringUtils)-
http://commons.apache.org/lang/
B. Apache Struts -
http://struts.apache.org/
0x04 使用者輸入驗證
以下是驗證使用者輸入的 Java 特定機制,以及上述一般機制的 Java 實施:
[1] 正面驗證
[a] 白名稱清單
<span style="font-size:14px;"> 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);
</span><p><span style="font-size:14px;"> out.println("connected!"); </span></p>
[b] 模式比對
對比輸入與預期的模式。 例如,如果 userName 字段應僅允許字母數字字元,且不區分大小寫,那麼請使用以下正規表達式:^[a-zA-Z0-9]*$,Java 1.3 或更早的版本不包含任何正規表達式包。建議将“Apache 正規表達式包”(請參閱以下“資源”)與 Java 1.3 一起使用,以解決該缺乏支援的問題。執行正規表達式驗證的示例:
<span style="font-size:14px;"> // 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
...
}
</span>
Java 1.4 引進了一種新的正規表達式包(java.util.regex)。以下是 Validator.matchPattern 的修訂版,使用新的 Java 1.4 正規表達式包:
<span style="font-size:14px;"> // 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);
}
</span>
[c] 範圍驗證
以下示例驗證輸入 numberOfChoices 是否在 10 至 20 之間:
<span style="font-size:14px;"> // 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
...
}
</span>
[2] 間接選擇
下列代碼片段在 J2EE 下,實施上述使用者輸入驗證“間接選擇”機制:
<span style="font-size:14px;"> 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);
</span>
[3] 消息認證代碼(MAC)
下列函數在 J2EE 之下,實施上述使用者輸入驗證 HMAC 機制:在給定密鑰和資料之後,這個函數會生成一個 HMAC:
<span style="font-size:14px;"> 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;
}
}
</span>
以下代碼利用上述函數來防禦“連接配接操縱”:
<span style="font-size:14px;"> 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!");
</span>
[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 的示例:
<span style="font-size:14px;"> grant codeBase "file:c:\\code\\-" {
permission java.lang.RuntimePermission "exitVM";
};
</span>
在預設情況下,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
0x05 使用者輸入屬性的驗證的 Java 特定機制
[1] 類型檢查
接着便是 Java 的數字(int)類型驗證功能實施:
<span style="font-size:14px;"> public static boolean validateInt(String value) {
boolean isFieldValid = false;
try {
Integer.parseInt(value);
isFieldValid = true;
} catch (Exception e) {
isFieldValid = false;
}
return isFieldValid;
}
</span>
好的做法是将所有 HTTP 請求參數轉換為其各自的資料類型。例如,開發者應将請求參數的“integerValue”存儲在請求屬性中,并按以下示例所示來使用:
<span style="font-size:14px;"> // 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");
</span>
應用程式應處理的主要 Java 資料類型:
- Byte
- Short
- Integer
- Long
- Float
- Double
- Date
[2] 長度驗證
Java String 長度驗證器實施:
<span style="font-size:14px;"> public static boolean validateLength(String value, int minLength, int maxLength) {
String validatedValue = value;
if (!validateRequired(value)) {
validatedValue = "";
}
return (validatedValue.length() >= minLength &&
validatedValue.length() <= maxLength);
}
... </span>
0x06 各類問題類型的建議
1 遠端執行碼
[1] 利用 Java 的 SecurityManager 來确定允許目标的白名稱清單。
如下所示,精心制作一個 FilePermission 規則條目,便可以控制 exec 呼叫:
<span style="font-size:14px;"> grant codeBase "file:<codepath>" {
permission java.io.FilePermission "<path>","execute";
};
</span>
如下所示,精心制作一個 RuntimePermission 規則條目,便可以控制 loadLibrary 呼叫:
<span style="font-size:14px;"> grant codeBase "file:<codepath>" {
permission java.lang.RuntimePermission "loadLibrary.<library name (e.g.: kernel32)>"
};</span>
2 資源注入
[1] 利用 Java 的 SecurityManager。
例如,指定下列“grant”規則,可以限制應用程式綁定所能綁定的端口:
<span style="font-size:14px;"> grant codeBase "file:<codepath>" {
permission java.net.SocketPermission "<host|0.0.0.0 for all interfaces>:<port>","listen";
};</span>
3 代碼注入
[1] 實施正面驗證機制(模式比對)
下列 J2EE 代碼證明如何送出求值的表達式隻包含簡單計算所需要的特定字元集:
<span style="font-size:14px;"> 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);
}
</span>
4 SOAP 操作
[1] 實施正面驗證機制(模式比對)
下列 J2EE 代碼證明如何確定使用者輸入隻包含字母數字字元:
<span style="font-size:14px;"> SOAPMessage m = mf.createMessage();
SOAPBody b = m.getSOAPBody();
if (! body.matches("\\w+"))
{
out.println("invalid input");
return;
}
b.addChildElement(sf.createName(body));
</span>
[2] 下列代碼片段證明如何利用 Apache 的 StringUtils,在 Java 中轉義 XML 資料:
<span style="font-size:14px;"> String body = request.getParameter("val");
SOAPMessage m = mf.createMessage();
SOAPBody b = m.getSOAPBody();
String encodedBody = StringEscapeUtils.escapeXml(body);
b.addChildElement(sf.createName(encodedBody));
</span>
<span style="font-size:14px;">
</span>
<span style="font-size:14px;"><span style="font-family: "Microsoft YaHei"; font-size: 32px; line-height: 26px;">歡迎大家分享更好的思路,熱切期待^^_^^</span>
</span>
<span style="font-size:14px;">
</span>