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>