Java-Sec代码审计漏洞篇(一)
字数 1185 2025-08-22 12:22:24
Java代码审计漏洞详解与防御指南
一、CRLF注入漏洞
漏洞原理
CRLF是"回车+换行"(\r\n,编码后是%0D%0A)的简称。在HTTP协议中,HTTP Header和HTTP Body是用两个CRLF来分割的。攻击者通过控制HTTP消息头中的字符,注入恶意换行,可以注入会话Cookie或HTML代码。
漏洞代码示例
@Controller
@RequestMapping("/crlf")
public class CRLFInjection {
@RequestMapping("/safecode")
@ResponseBody
public void crlf(HttpServletRequest request, HttpServletResponse response) {
response.addHeader("test1", request.getParameter("test1"));
response.setHeader("test2", request.getParameter("test2"));
String author = request.getParameter("test3");
Cookie cookie = new Cookie("test3", author);
response.addCookie(cookie);
}
}
攻击示例
访问URL:http://localhost:8080/crlf/safecode?test1=111%0d%0ax&test2=111%0d%0a111
修复方案
现代Java EE应用服务器已修复此问题。对于自定义代码,应对输入进行严格过滤。
二、命令注入漏洞
1. 参数注入
漏洞代码
@GetMapping("/codeinject")
public String codeInject(String filepath) throws IOException {
String[] cmdList = new String[]{"sh", "-c", "ls -la " + filepath};
ProcessBuilder builder = new ProcessBuilder(cmdList);
builder.redirectErrorStream(true);
Process process = builder.start();
return WebUtils.convertStreamToString(process.getInputStream());
}
攻击示例
codeinject?filepath=/tmp;cat /etc/passwd
2. Host头注入
漏洞代码
@GetMapping("/codeinject/host")
public String codeInjectHost(HttpServletRequest request) throws IOException {
String host = request.getHeader("host");
String[] cmdList = new String[]{"sh", "-c", "curl " + host};
ProcessBuilder builder = new ProcessBuilder(cmdList);
builder.redirectErrorStream(true);
Process process = builder.start();
return WebUtils.convertStreamToString(process.getInputStream());
}
攻击示例
127.0.0.1;id
修复方案
@GetMapping("/codeinject/sec")
public String codeInjectSec(String filepath) throws IOException {
String filterFilePath = SecurityUtil.cmdFilter(filepath);
if (null == filterFilePath) {
return "Bad boy. I got u.";
}
String[] cmdList = new String[]{"sh", "-c", "ls -la " + filterFilePath};
ProcessBuilder builder = new ProcessBuilder(cmdList);
builder.redirectErrorStream(true);
Process process = builder.start();
return WebUtils.convertStreamToString(process.getInputStream());
}
安全检查类
public class SecurityUtil {
private static final Pattern FILTER_PATTERN = Pattern.compile("^[a-zA-Z0-9_/.-]+$");
public static String cmdFilter(String input) {
if (!FILTER_PATTERN.matcher(input).matches()) {
return null;
}
return input;
}
}
三、越权漏洞
漏洞代码示例
@RestController
@RequestMapping("/cookie")
public class Cookies {
private static String NICK = "nick";
@GetMapping(value = "/vuln01")
public String vuln01(HttpServletRequest req) {
String nick = WebUtils.getCookieValueByName(req, NICK);
return "Cookie nick: " + nick;
}
// 其他几种获取cookie的方式也存在同样问题
// ...
}
修复方案
- 对Cookie进行加密处理
- 使用安全的Session管理机制
- 实现严格的权限验证
四、CORS跨域漏洞
漏洞原理
CORS(跨源资源共享)允许浏览器向跨域服务器发出XMLHttpRequest请求。配置不当会导致跨域请求伪造,结合XSS、CSRF进行攻击。
漏洞代码
@GetMapping("/vuln/origin")
public String vuls1(HttpServletRequest request, HttpServletResponse response) {
String origin = request.getHeader("origin");
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Credentials", "true");
return info;
}
@GetMapping("/vuln/setHeader")
public String vuls2(HttpServletResponse response) {
response.setHeader("Access-Control-Allow-Origin", "*");
return info;
}
攻击POC
<!DOCTYPE html>
<html>
<head>
<title>CORS TEST</title>
</head>
<body>
<div id='output'></div>
<script type="text/javascript">
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get', 'http://vuln.com/xxxx', true);
req.withCredentials = true;
req.send();
function reqListener() {
var output = document.getElementById('output');
output.innerHTML = "URL: http://vuln.com/xxxx Response: <textarea style='width: 659px; height: 193px;'>" + req.responseText + "</textarea>";
};
</script>
</body>
</html>
修复方案
@GetMapping("/sec/checkOrigin")
public String seccode(HttpServletRequest request, HttpServletResponse response) {
String origin = request.getHeader("Origin");
if (origin != null && SecurityUtil.checkURL(origin) == null) {
return "Origin is not safe.";
}
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Credentials", "true");
return LoginUtils.getUserInfo2JsonStr(request);
}
五、反序列化漏洞
漏洞原理
当输入的反序列化数据可被用户控制时,攻击者可构造恶意输入,在反序列化过程中执行任意代码。
漏洞代码
@RequestMapping("/rememberMe/vuln")
public String rememberMeVul(HttpServletRequest request) throws IOException, ClassNotFoundException {
Cookie cookie = getCookie(request, Constants.REMEMBER_ME_COOKIE);
if (null == cookie) {
return "No rememberMe cookie. Right?";
}
String rememberMe = cookie.getValue();
byte[] decoded = Base64.getDecoder().decode(rememberMe);
ByteArrayInputStream bytes = new ByteArrayInputStream(decoded);
ObjectInputStream in = new ObjectInputStream(bytes);
in.readObject();
in.close();
return "Are u ok?";
}
修复方案
@RequestMapping("/rememberMe/security")
public String rememberMeBlackClassCheck(HttpServletRequest request) throws IOException, ClassNotFoundException {
Cookie cookie = getCookie(request, Constants.REMEMBER_ME_COOKIE);
if (null == cookie) {
return "No rememberMe cookie. Right?";
}
String rememberMe = cookie.getValue();
byte[] decoded = Base64.getDecoder().decode(rememberMe);
ByteArrayInputStream bytes = new ByteArrayInputStream(decoded);
try {
AntObjectInputStream in = new AntObjectInputStream(bytes);
in.readObject();
in.close();
} catch (InvalidClassException e) {
logger.info(e.toString());
return e.toString();
}
return "I'm very OK.";
}
自定义安全检查类
@Override
protected Class<?> resolveClass(final ObjectStreamClass desc) throws IOException, ClassNotFoundException {
String className = desc.getName();
String[] denyClasses = {"java.net.InetAddress", "org.apache.commons.collections.Transformer",
"org.apache.commons.collections.functors"};
for (String denyClass : denyClasses) {
if (className.startsWith(denyClass)) {
throw new InvalidClassException("Unauthorized deserialization attempt", className);
}
}
return super.resolveClass(desc);
}
六、Fastjson反序列化漏洞
漏洞原理
- 允许通过"@type"键指定任意反序列化类名
- 自定义反序列化机制使用反射生成指定类实例,自动调用setter和部分getter方法
漏洞代码
@RequestMapping(value = "/deserialize", method = {RequestMethod.POST})
@ResponseBody
public String Deserialize(@RequestBody String params) {
try {
JSONObject ob = JSON.parseObject(params);
return ob.get("name").toString();
} catch (Exception e) {
return e.toString();
}
}
攻击示例
{
"name":{
"@type": "java.net.Inet4Address",
"val": "w2b6lu.dnslog.cn"
}
}
恶意类POC
package exp;
import java.lang.Runtime;
import java.lang.Process;
public class poc {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"touch", "/tmp/success"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}
修复方案
- 升级到最新安全版本
- 使用白名单机制限制反序列化类
- 禁用autoType功能
七、文件上传漏洞
漏洞代码
@PostMapping("/upload")
public String singleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) {
if (file.isEmpty()) {
redirectAttributes.addFlashAttribute("message", "Please select a file to upload");
return "redirect:/file/status";
}
try {
byte[] bytes = file.getBytes();
Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename());
Files.write(path, bytes);
redirectAttributes.addFlashAttribute("message",
"You successfully uploaded '" + UPLOADED_FOLDER + file.getOriginalFilename() + "'");
} catch (IOException e) {
redirectAttributes.addFlashAttribute("message", "upload failed");
logger.error(e.toString());
}
return "redirect:/file/status";
}
攻击示例
上传文件名包含路径穿越:../../Users/oldthree/Documents/0.OL4THREE/0.Base/apache-tomcat-8.5.70/webapps/java_sec_code_war/1.png
修复方案
@PostMapping("/upload/picture")
@ResponseBody
public String uploadPicture(@RequestParam("file") MultipartFile multifile) throws Exception {
if (multifile.isEmpty()) {
return "Please select a file to upload";
}
String fileName = multifile.getOriginalFilename();
String Suffix = fileName.substring(fileName.lastIndexOf("."));
String mimeType = multifile.getContentType();
String filePath = UPLOADED_FOLDER + fileName;
File excelFile = convert(multifile);
// 校验1:文件后缀名白名单
String[] picSuffixList = {".jpg", ".png", ".jpeg", ".gif", ".bmp", ".ico"};
boolean suffixFlag = false;
for (String white_suffix : picSuffixList) {
if (Suffix.toLowerCase().equals(white_suffix)) {
suffixFlag = true;
break;
}
}
if (!suffixFlag) {
logger.error("[-] Suffix error: " + Suffix);
deleteFile(filePath);
return "Upload failed. Illeagl picture.";
}
// 校验2:MIME类型黑名单
String[] mimeTypeBlackList = {"text/html", "text/javascript", "application/javascript",
"application/ecmascript", "text/xml", "application/xml"};
for (String blackMimeType : mimeTypeBlackList) {
if (SecurityUtil.replaceSpecialStr(mimeType).toLowerCase().contains(blackMimeType)) {
logger.error("[-] Mime type error: " + mimeType);
deleteFile(filePath);
return "Upload failed. Illeagl picture.";
}
}
// 校验3:文件内容是否为图片
boolean isImageFlag = isImage(excelFile);
deleteFile(randomFilePath);
if (!isImageFlag) {
logger.error("[-] File is not Image");
deleteFile(filePath);
return "Upload failed. Illeagl picture.";
}
try {
byte[] bytes = multifile.getBytes();
Path path = Paths.get(UPLOADED_FOLDER + multifile.getOriginalFilename());
Files.write(path, bytes);
} catch (IOException e) {
logger.error(e.toString());
deleteFile(filePath);
return "Upload failed";
}
logger.info("[+] Safe file. Suffix: {}, MIME: {}", Suffix, mimeType);
logger.info("[+] Successfully uploaded {}", filePath);
return String.format("You successfully uploaded '%s'", filePath);
}
八、SQL注入漏洞
漏洞代码
@RequestMapping("/jdbc/vuln")
public String jdbc_sqli_vul(@RequestParam("username") String username) {
StringBuilder result = new StringBuilder();
try {
Class.forName(driver);
Connection con = DriverManager.getConnection(url, user, password);
if (!con.isClosed()) System.out.println("Connect to database successfully.");
// sqli vuln code
Statement statement = con.createStatement();
String sql = "select * from users where username = '" + username + "'";
logger.info(sql);
ResultSet rs = statement.executeQuery(sql);
while (rs.next()) {
String res_name = rs.getString("username");
String res_pwd = rs.getString("password");
String info = String.format("%s: %s\n", res_name, res_pwd);
result.append(info);
logger.info(info);
}
rs.close();
con.close();
} catch (ClassNotFoundException e) {
logger.error("Sorry, can't find the Driver!");
} catch (SQLException e) {
logger.error(e.toString());
}
return result.toString();
}
攻击示例
aaa' or '1'='1
修复方案
@RequestMapping("/jdbc/sec")
public String jdbc_sqli_sec(@RequestParam("username") String username) {
StringBuilder result = new StringBuilder();
try {
Class.forName(driver);
Connection con = DriverManager.getConnection(url, user, password);
if (!con.isClosed()) System.out.println("Connecting to Database successfully.");
// fix code
String sql = "select * from users where username = ?";
PreparedStatement st = con.prepareStatement(sql);
st.setString(1, username);
logger.info(st.toString()); // sql after prepare statement
ResultSet rs = st.executeQuery();
while (rs.next()) {
String res_name = rs.getString("username");
String res_pwd = rs.getString("password");
String info = String.format("%s: %s\n", res_name, res_pwd);
result.append(info);
logger.info(info);
}
rs.close();
con.close();
} catch (ClassNotFoundException e) {
logger.error("Sorry, can`t find the Driver!");
e.printStackTrace();
} catch (SQLException e) {
logger.error(e.toString());
}
return result.toString();
}
总结
- 输入验证:对所有用户输入进行严格验证和过滤
- 输出编码:对输出到页面的内容进行编码处理
- 最小权限原则:数据库连接使用最小权限账户
- 安全配置:框架和服务器保持安全配置
- 安全编码:使用预编译、参数化查询等安全编码实践
- 持续更新:及时更新框架和组件到最新安全版本
- 防御深度:实施多层防御策略,不依赖单一安全措施
通过理解和应用这些安全编码实践,可以显著提高Java应用程序的安全性,减少漏洞风险。