Java代码审计手书(三) - 安全漏洞与防御措施
1. 代码注入类漏洞
1.1 脚本引擎注入(SCRIPT_ENGINE_INJECTION)
漏洞特征:动态执行未经验证的用户输入脚本代码。
风险:恶意代码执行可导致数据泄露或任意系统指令执行。
漏洞代码:
public void runCustomTrigger(String script) {
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("JavaScript");
engine.eval(script); // 危险点
}
解决方案:使用"Cloudbees Rhino Sandbox"库安全评估JS代码
public void runCustomTrigger(String script) {
SandboxContextFactory contextFactory = new SandboxContextFactory();
Context context = contextFactory.makeContext();
contextFactory.enterContext(context);
try {
ScriptableObject prototype = context.initStandardObjects();
prototype.setParentScope(null);
Scriptable scope = context.newObject(prototype);
scope.setPrototype(prototype);
context.evaluateString(scope,script, null, -1, null);
} finally {
context.exit();
}
}
1.2 SpEL表达式注入(SPEL_INJECTION)
漏洞特征:未验证用户输入直接用于Spring表达式解析。
漏洞代码:
public void parseExpressionInterface(Person personObj,String property) {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(property+" == 'Albert'"); // 危险点
StandardEvaluationContext testContext = new StandardEvaluationContext(personObj);
boolean result = exp.getValue(testContext, Boolean.class);
}
防御措施:
- 严格验证用户输入
- 避免用户控制表达式内容
1.3 EL表达式注入(EL_INJECTION)
漏洞特征:未验证用户输入直接用于表达式语言解析。
漏洞代码:
public void evaluateExpression(String expression) {
FacesContext context = FacesContext.getCurrentInstance();
ExpressionFactory expressionFactory = context.getApplication().getExpressionFactory();
ELContext elContext = context.getELContext();
ValueExpression vex = expressionFactory.createValueExpression(elContext, expression, String.class);
return (String) vex.getValue(elContext);
}
1.4 Seam日志注入(SEAM_LOG_INJECTION)
漏洞特征:日志API支持EL表达式解析,可能执行未预期代码。
漏洞代码:
public void logUser(User user) {
log.info("Current logged in user : " + user.getUsername()); // 危险点
}
解决方案:
public void logUser(User user) {
log.info("Current logged in user : #0", user.getUsername());
}
1.5 OGNL注入(OGNL_INJECTION)
漏洞特征:未验证用户输入直接用于OGNL表达式解析。
漏洞代码:
public void getUserProperty(String property) {
return ognlUtil.getValue("user."+property, ctx, root, String.class); // 危险点
}
2. 加密与安全传输类漏洞
2.1 不安全的加密算法
DES不安全:
Cipher c = Cipher.getInstance("DES/ECB/PKCS5Padding"); // 不安全
3DES不安全:
Cipher c = Cipher.getInstance("DESede/ECB/PKCS5Padding"); // 不安全
解决方案:使用AES/GCM
Cipher c = Cipher.getInstance("AES/GCM/NoPadding"); // 安全
2.2 RSA密钥过短
漏洞特征:使用小于2048位的RSA密钥。
漏洞代码:
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(512); // 不安全
解决方案:
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048); // 安全
2.3 未加密的Socket通信
漏洞代码:
Socket soc = new Socket("www.google.com",80); // 明文传输
ServerSocket soc = new ServerSocket(1234); // 明文传输
解决方案:
Socket soc = SSLSocketFactory.getDefault().createSocket("www.google.com", 443); // SSL加密
ServerSocket soc = SSLServerSocketFactory.getDefault().createServerSocket(1234); // SSL加密
2.4 加密完整性缺失
漏洞特征:使用不提供完整性的加密模式(CBC, OFB, CTR, ECB)。
漏洞代码:
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); // 无完整性保护
解决方案:
Cipher c = Cipher.getInstance("AES/GCM/NoPadding"); // 提供完整性
2.5 Padding Oracle攻击
漏洞特征:使用CBC模式与PKCS5Padding易受攻击。
漏洞代码:
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); // 易受攻击
解决方案:
Cipher c = Cipher.getInstance("AES/GCM/NoPadding"); // 安全
3. Web安全类漏洞
3.1 HTTP响应拆分(HTTP_RESPONSE_SPLITTING)
漏洞特征:未过滤CR/LF字符可能导致响应被分割。
漏洞代码:
String author = request.getParameter(AUTHOR_PARAMETER);
Cookie cookie = new Cookie("author", author);
response.addCookie(cookie); // 可能注入CR/LF
3.2 未验证的重定向
漏洞特征:直接使用用户提供的URL进行重定向。
漏洞代码:
// Servlet中
resp.sendRedirect(req.getParameter("redirectUrl"));
// Play Framework中
def login(redirectUrl:String) = Action {
Redirect(url)
}
// Spring中
@RequestMapping("/redirect")
public String redirect(@RequestParam("url") String url) {
return "redirect:" + url;
}
解决方案:
- 不接受用户输入的重定向URL
- 使用目的地址key查询合法地址
- 仅接受相对路径
- URLs白名单
3.3 JSP动态包含(JSP_INCLUDE)
漏洞特征:用户可控的JSP文件包含。
漏洞代码:
<jsp:include page="${param.secret_param}" />
解决方案:
<c:if test="${param.secret_param == 'page1'}">
<jsp:include page="page1.jsp" />
</c:if>
3.4 XSS漏洞
JSP中XSS:
<%= taintedInput %> <!-- 不安全 -->
Servlet中XSS:
resp.getWriter().write(input1); // 不安全
解决方案:使用编码输出
<%= Encode.forHtml(taintedInput) %>
resp.getWriter().write(Encode.forHtml(input1));
JSTL输出禁用XML转义:
<c:out value="${param.test_param}" escapeXml="false"/> <!-- 不安全 -->
解决方案:
<c:out value="${param.test_param}"/>
4. 其他安全漏洞
4.1 硬编码凭据
硬编码密码:
private String SECRET_PASSWORD = "letMeIn!";
Properties props = new Properties();
props.put(Context.SECURITY_CREDENTIALS, "p@ssw0rd");
硬编码密钥:
byte[] key = {1, 2, 3, 4, 5, 6, 7, 8};
SecretKeySpec spec = new SecretKeySpec(key, "AES");
4.2 不安全的哈希比较
漏洞特征:使用equals()比较哈希值可能泄露信息。
漏洞代码:
if(userInput.equals(actualHash)) { ... } // 不安全
解决方案:
if(MessageDigest.isEqual(userInput.getBytes(),actualHash.getBytes())) { ... }
4.3 坏的十六进制转换
漏洞特征:使用Integer.toHexString()可能导致转换错误。
漏洞代码:
stringBuilder.append(Integer.toHexString(b & 0xFF)); // 可能出错
解决方案:
stringBuilder.append(String.format("%02X", b));
4.4 XMLDecoder反序列化
漏洞特征:XMLDecoder可执行任意方法调用。
漏洞代码:
XMLDecoder d = new XMLDecoder(in);
Object result = d.readObject(); // 危险
解决方案:避免使用XMLDecoder解析不受信任数据。
4.5 固定IV
漏洞特征:使用静态初始化向量(IV)。
漏洞代码:
private static byte[] IV = new byte[16] {(byte)0,(byte)1,(byte)2,...};
IvParameterSpec ivSpec = new IvParameterSpec(IV);
解决方案:每次生成随机IV
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
5. 最佳实践总结
- 输入验证:对所有用户输入进行严格验证
- 安全编码:
- 避免动态执行用户提供的代码
- 使用安全的加密算法和足够长的密钥
- 避免硬编码凭据
- 输出编码:对所有动态输出进行适当的编码
- 安全传输:始终使用加密通信(SSL/TLS)
- 日志安全:过滤日志中的CRLF字符和敏感信息
- 配置安全:确保安全配置(如ESAPI加密配置正确)
- 依赖管理:保持安全库和框架的最新版本
通过遵循这些安全编码实践,可以显著降低Java应用程序中的安全风险。