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的方式也存在同样问题
    // ...
}

修复方案

  1. 对Cookie进行加密处理
  2. 使用安全的Session管理机制
  3. 实现严格的权限验证

四、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反序列化漏洞

漏洞原理

  1. 允许通过"@type"键指定任意反序列化类名
  2. 自定义反序列化机制使用反射生成指定类实例,自动调用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
        }
    }
}

修复方案

  1. 升级到最新安全版本
  2. 使用白名单机制限制反序列化类
  3. 禁用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();
}

总结

  1. 输入验证:对所有用户输入进行严格验证和过滤
  2. 输出编码:对输出到页面的内容进行编码处理
  3. 最小权限原则:数据库连接使用最小权限账户
  4. 安全配置:框架和服务器保持安全配置
  5. 安全编码:使用预编译、参数化查询等安全编码实践
  6. 持续更新:及时更新框架和组件到最新安全版本
  7. 防御深度:实施多层防御策略,不依赖单一安全措施

通过理解和应用这些安全编码实践,可以显著提高Java应用程序的安全性,减少漏洞风险。

Java代码审计漏洞详解与防御指南 一、CRLF注入漏洞 漏洞原理 CRLF是"回车+换行"( \r\n ,编码后是 %0D%0A )的简称。在HTTP协议中,HTTP Header和HTTP Body是用两个CRLF来分割的。攻击者通过控制HTTP消息头中的字符,注入恶意换行,可以注入会话Cookie或HTML代码。 漏洞代码示例 攻击示例 访问URL: http://localhost:8080/crlf/safecode?test1=111%0d%0ax&test2=111%0d%0a111 修复方案 现代Java EE应用服务器已修复此问题。对于自定义代码,应对输入进行严格过滤。 二、命令注入漏洞 1. 参数注入 漏洞代码 攻击示例 codeinject?filepath=/tmp;cat /etc/passwd 2. Host头注入 漏洞代码 攻击示例 127.0.0.1;id 修复方案 安全检查类 三、越权漏洞 漏洞代码示例 修复方案 对Cookie进行加密处理 使用安全的Session管理机制 实现严格的权限验证 四、CORS跨域漏洞 漏洞原理 CORS(跨源资源共享)允许浏览器向跨域服务器发出XMLHttpRequest请求。配置不当会导致跨域请求伪造,结合XSS、CSRF进行攻击。 漏洞代码 攻击POC 修复方案 五、反序列化漏洞 漏洞原理 当输入的反序列化数据可被用户控制时,攻击者可构造恶意输入,在反序列化过程中执行任意代码。 漏洞代码 修复方案 自定义安全检查类 六、Fastjson反序列化漏洞 漏洞原理 允许通过"@type"键指定任意反序列化类名 自定义反序列化机制使用反射生成指定类实例,自动调用setter和部分getter方法 漏洞代码 攻击示例 恶意类POC 修复方案 升级到最新安全版本 使用白名单机制限制反序列化类 禁用autoType功能 七、文件上传漏洞 漏洞代码 攻击示例 上传文件名包含路径穿越: ../../Users/oldthree/Documents/0.OL4THREE/0.Base/apache-tomcat-8.5.70/webapps/java_sec_code_war/1.png 修复方案 八、SQL注入漏洞 漏洞代码 攻击示例 aaa' or '1'='1 修复方案 总结 输入验证 :对所有用户输入进行严格验证和过滤 输出编码 :对输出到页面的内容进行编码处理 最小权限原则 :数据库连接使用最小权限账户 安全配置 :框架和服务器保持安全配置 安全编码 :使用预编译、参数化查询等安全编码实践 持续更新 :及时更新框架和组件到最新安全版本 防御深度 :实施多层防御策略,不依赖单一安全措施 通过理解和应用这些安全编码实践,可以显著提高Java应用程序的安全性,减少漏洞风险。