JDK FFM API 安全风险分析:从 Native 调用边界到 RASP 检测思路
摘要
随着 Java 平台能力不断增强,Java 已不再只是传统意义上的“托管语言”。JDK 引入 Foreign Function & Memory API(以下简称 FFM API)后,Java 应用可以在不编写 JNI 代码、不额外加载本地动态库的情况下,与本地函数和堆外内存进行交互。这一能力提升了 Java 与 Native 生态的集成效率,但也带来了新的安全边界问题。
在传统 Java 命令执行防护体系中,RASP、WAF 和审计工具通常重点关注 Runtime.exec()、ProcessBuilder.start()、ProcessImpl 等调用链。然而,FFM API 提供了另一类 Native 调用路径,使得部分安全产品原有的 Java 层 Hook 策略可能出现覆盖盲区。本文从原理、风险、检测和防御四个角度,对 FFM API 相关安全问题进行分析,重点讨论如何从 JVM 参数控制、RASP Hook 扩展、主机行为监控和 WAF 规则更新等方面完善防护体系。
免责声明:本文仅用于安全研究、风险分析和授权场景下的防御验证,不提供可直接用于未授权攻击的利用代码或完整攻击链。请勿将相关技术用于任何非法用途。
关键词
Java 安全、FFM API、JDK 21、JDK 22、RASP、SpEL 注入、Native 调用、eBPF、JVM 安全
一、背景:Java Native 能力带来的安全边界变化
Java 命令执行相关风险长期存在。无论是反序列化漏洞、表达式注入、模板注入,还是配置错误导致的脚本执行,最终经常会落到几个典型 API 上:
java.lang.Runtime.exec()java.lang.ProcessBuilder.start()- 底层
ProcessImpl相关调用
因此,很多 RASP 产品会围绕这些入口进行 Hook,对命令参数、调用栈、上下文来源和进程行为进行审计或阻断。
这种防护模式在传统场景下有效,因为攻击链通常需要经过 Java 标准进程创建 API。然而,FFM API 的出现改变了这一前提。
FFM API 的设计目标是替代传统 JNI,降低 Java 调用 Native 函数的成本。开发者可以直接通过 Java 代码查找本地符号、构造函数描述符、分配堆外内存,并通过 MethodHandle 调用本地函数。
从安全视角看,这意味着:
- Native 调用不一定经过
Runtime或ProcessBuilder; - 本地函数调用可以在 JVM 进程内完成;
- 部分行为从 Java API 层下沉到 JVM 和操作系统层;
- 传统基于字节码插桩的 RASP 需要更新检测边界。
二、FFM API 的核心组件
FFM API 主要位于 java.lang.foreign 包中,几个核心组件如下。
| 组件 | 作用 | 安全关注点 |
|---|---|---|
Linker |
Java 与本地代码之间的桥梁 | 获取 Native 调用能力的关键入口 |
SymbolLookup |
查找已加载共享库中的函数符号 | 可能用于定位敏感 Native 函数 |
FunctionDescriptor |
描述本地函数参数和返回值 | 决定 Java 与 Native 函数的调用约定 |
MemorySegment |
表示连续内存区域 | 可用于传递参数或操作堆外内存 |
Arena |
管理内存生命周期 | 控制堆外内存分配与释放 |
ValueLayout |
描述基础数据类型内存布局 | 与 Native 函数签名匹配相关 |
一个典型的 FFM Native 调用过程可以抽象为以下几步:
- 获取平台链接器;
- 查找目标 Native 函数符号;
- 构造函数描述符;
- 创建下行调用句柄;
- 分配参数内存;
- 通过
MethodHandle发起调用。
这里最值得安全人员关注的是:整个过程可以不经过传统的 Java 命令执行 API,而是通过 JVM 内部机制完成 Java 到 Native 的调用转换。
三、FFM API 与 JNI 的差异
很多人会将 FFM API 与 JNI 进行类比,但二者在安全审计上的差异非常明显。
| 对比项 | JNI | FFM API |
| 开发方式 | 需要编写 C/C++ 并编译动态库 | 纯 Java 代码即可完成 |
| 文件依赖 | 通常需要 .so、.dll 等文件 |
可直接调用系统已加载库 |
| 审计特征 | System.loadLibrary() 等调用较明显 |
可能不触发传统库加载检测 |
| 部署成本 | 需要跨平台编译 | 与 Java 代码集成更紧密 |
| RASP 可见性 | 可以关注库加载和 JNI 调用 | 需要额外覆盖 FFM 入口 |
从防御角度看,JNI 的风险较容易通过“动态库落盘”“库加载行为”“本地方法注册”等特征发现。而 FFM API 更加轻量,且调用链更贴近 JVM 内部执行机制,因此需要新的检测思路。
四、风险场景一:绕过传统 Java 命令执行 Hook
传统 RASP 对命令执行的检测通常集中在以下路径:
Runtime.exec()
-> ProcessBuilder.start()
-> ProcessImpl
-> fork / execve
在这个路径中,RASP 可以在 Java 层较早介入,例如在 Runtime.exec() 或 ProcessBuilder.start() 触发时进行拦截。
但 FFM API 的调用路径并不相同。其抽象路径更接近:
Linker
-> SymbolLookup
-> downcallHandle
-> JVM adapter
-> Native function
从 Java 层看,它可能只是一次 MethodHandle.invoke() 调用;从操作系统层看,最终行为可能表现为进程创建、内存映射或其他系统调用。
这就带来一个核心问题:
如果 RASP 只关注
Runtime、ProcessBuilder等传统入口,就可能无法覆盖 FFM API 引入的新 Native 调用路径。
因此,防护体系不能只停留在“拦截 Java 命令执行 API”这一层,而应进一步关注 Native 调用入口和系统调用行为。
五、风险场景二:表达式注入中的新型调用链
在 Java Web 应用中,SpEL 表达式注入是一类典型高危漏洞。如果开发者将用户输入直接拼入表达式并执行,攻击者就可能借助表达式能力访问 Java 类型、调用方法或构造对象。
传统 SpEL RCE 风险往往围绕以下敏感关键字展开:
RuntimeexecProcessBuildergetRuntime
因此,WAF 和 RASP 常对这些关键字进行匹配或语义分析。
但如果运行环境允许 FFM API,并且表达式上下文可以访问相关类型,那么风险关键词可能发生变化。例如:
LinkerSymbolLookupFunctionDescriptorMemorySegmentdowncallHandlejava.lang.foreign
这意味着,仅仅检测 Runtime、exec 等传统特征已经不够。对于支持 JDK 21+、JDK 22+ 的 Java 应用,安全规则库需要及时补充 FFM API 相关特征。
六、风险场景三:堆外内存与可执行内存分配
FFM API 不仅可以调用本地函数,还可以操作堆外内存。如果应用通过 Native 能力申请具备执行权限的内存区域,再写入机器码并执行,就可能形成更隐蔽的攻击路径。
从主机层看,此类行为的关键特征不是 Java API 调用,而是系统调用层面的异常行为,例如:
- Java 进程申请可执行内存;
- 出现
mmap搭配PROT_EXEC; - 出现
mprotect将内存页修改为可执行; - JVM 进程内出现异常代码执行行为。
正常情况下,Java 应用很少需要自行申请 RWX 权限内存。JIT 编译器确实会管理代码缓存,但其行为模式与一次性申请可读、可写、可执行内存的攻击行为存在差异。
因此,对这类风险,单纯依赖 Java 层 Hook 效果有限,更适合通过 eBPF、EDR、审计日志等方式从主机侧进行监控。
七、检测思路一:JVM 参数层面的限制
1. JDK 21 场景
在 JDK 21 中,FFM API 仍属于 Preview 能力,通常需要显式启用 Preview 特性。生产环境应避免随意开启相关参数,尤其是对外暴露的 Web 应用、网关服务、管理后台和任务调度平台。
建议:
# 生产环境原则上不启用
--enable-preview
如果业务确实依赖 Preview 特性,应进行专项安全评估,并配合 RASP、主机监控和依赖审计。
2. JDK 22 及以上场景
在更高版本 JDK 中,FFM API 逐步正式化。此时更应关注 Native Access 的授权范围,避免所有模块默认拥有 Native 调用能力。
建议遵循最小权限原则:
--enable-native-access=<明确允许的模块>
不建议在生产环境中使用过宽的授权方式,尤其要避免将 Native Access 开放给不可信模块、插件系统、表达式执行模块或脚本引擎模块。
八、检测思路二:RASP Hook 覆盖范围扩展
传统 RASP 应继续保留对以下入口的检测:
Runtime.exec()ProcessBuilder.start()ProcessImpl- 反射调用命令执行 API
- 脚本引擎调用链
同时,应新增对 FFM API 关键入口的检测。
| Hook 点 | 建议关注内容 |
Linker.nativeLinker() |
是否获取 Native 链接能力 |
SymbolLookup.find() |
是否查找敏感 Native 符号 |
Linker.downcallHandle() |
是否创建 Native 调用句柄 |
SymbolLookup.libraryLookup() |
是否加载额外本地库 |
MemorySegment 写操作 |
是否写入异常二进制内容 |
Arena 分配 |
是否频繁分配堆外内存 |
敏感符号可以重点关注:
systemexecveforkpopenmmapmprotectdlopendlsym
需要注意的是,不能简单地“一刀切”拦截所有 FFM API 调用。部分高性能组件、数据库驱动、向量计算库或系统集成模块可能存在合理使用场景。因此建议结合调用栈、模块名、业务上下文和运行环境进行综合判断。
九、检测思路三:基于 eBPF 的主机行为监控
对于 Native 调用和内存执行类风险,主机层监控非常关键。可以通过 eBPF 监控 Java 进程的敏感系统调用行为。
例如,重点关注以下事件:
- Java 进程调用
execve; - Java 进程调用
mmap且带有PROT_EXEC; - Java 进程调用
mprotect将内存修改为可执行; - Java 进程产生异常子进程;
- Java 服务进程访问异常共享库路径。
一个防御侧监控思路如下:
# 示例:监控进程申请可执行内存的行为
sudo bpftrace -e '
tracepoint:syscalls:sys_enter_mmap
/args->prot & 4/
{
printf("[ALERT] PID=%d COMM=%s mmap(len=%lu, prot=0x%x)\n",
pid, comm, args->len, args->prot);
}'
该类规则适合部署在测试环境、灰度环境或高安全区域中,用于发现异常 Native 行为。
在生产环境中,建议结合白名单策略,避免对 JVM 自身 JIT、性能分析工具或合法 Native 组件产生误报。
十、检测思路四:WAF 与网关规则更新
如果 Web 应用存在表达式执行、模板渲染、脚本执行、动态规则执行等能力,则 WAF 不应只检测传统命令执行关键字,还应增加 FFM API 相关特征。
可关注的关键字包括:
java.lang.foreign
nativeLinker
downcallHandle
SymbolLookup
FunctionDescriptor
MemorySegment
Arena
ValueLayout
同时应结合上下文判断,例如:
- 参数中是否出现 Java 类型引用;
- 是否存在表达式语法;
- 是否出现 Native 函数名称;
- 是否存在异常编码、拼接或分段绕过;
- 是否命中高危接口路径。
示例规则思路:
SecRule ARGS "@rx (?i)(nativeLinker|downcallHandle|SymbolLookup|java\.lang\.foreign|FunctionDescriptor|MemorySegment)" \
"id:100001,\
phase:2,\
deny,\
status:403,\
msg:'Potential FFM API abuse attempt',\
tag:'attack-rce'"
实际部署中,应根据业务误报情况进行灰度验证,并结合日志审计持续优化。
十一、开发侧治理建议
1. 禁止直接执行不可信表达式
对于 SpEL、OGNL、MVEL、Groovy、JavaScript 引擎等能力,应避免直接执行用户输入。
错误做法:
用户输入 -> 拼接表达式 -> 直接求值
推荐做法:
用户输入 -> 白名单字段解析 -> 参数化处理 -> 固定规则执行
2. 对表达式上下文做最小化暴露
如果业务必须使用表达式引擎,应限制表达式可访问的类型、方法和对象。
建议:
- 禁止访问任意 Java 类型;
- 禁止反射能力;
- 禁止访问 ClassLoader;
- 禁止访问系统属性;
- 禁止访问
java.lang.foreign; - 禁止访问进程、文件、网络相关 API。
3. 建立 JDK 升级安全评估机制
JDK 升级不仅是兼容性问题,也是安全边界变化问题。每次从 JDK 8、11、17 升级到 21 或更高版本时,应评估:
- 是否启用 Preview 特性;
- 是否开放 Native Access;
- 是否引入新的外部函数调用能力;
- RASP 是否支持新 JDK 特性;
- WAF 规则是否覆盖新增风险关键字;
- 主机监控是否能识别 Native 行为。
十二、企业落地建议
对于企业级 Java 应用,可以从以下几个层面形成闭环治理。
1. 资产梳理
梳理所有使用 JDK 21+ 的应用,重点关注:
- 对外暴露的 Web 服务;
- 存在表达式执行能力的系统;
- 规则引擎、流程引擎、低代码平台;
- 插件化系统;
- 运维管理平台;
- 自动化执行平台。
2. 参数基线
建立 JVM 启动参数安全基线,重点检查:
- 是否启用
--enable-preview; - 是否使用过宽的
--enable-native-access; - 是否加载未知 Agent;
- 是否存在异常本地库路径;
- 是否开启危险调试参数。
3. RASP 升级
推动 RASP 能力从传统 Java API Hook 扩展到:
- FFM API 关键入口;
- 反射调用链;
- 表达式执行链;
- Native 符号查找;
- 异常内存权限变更;
- Java 进程异常子进程行为。
4. 主机检测
结合 EDR、HIDS、eBPF 等能力,重点监控:
- Java 进程创建 shell;
- Java 进程执行系统工具;
- Java 进程申请可执行内存;
- Java 进程加载异常共享库;
- Java 进程出现异常网络连接。
5. 安全测试
在授权测试环境中构造安全验证用例,验证以下能力是否有效:
- WAF 是否拦截 FFM 关键字;
- RASP 是否识别 Native 调用;
- 主机侧是否捕获异常系统调用;
- 告警是否能关联到应用、接口和调用栈;
- 阻断策略是否影响正常业务。
十三、总结
FFM API 是 Java 平台能力演进的重要方向,它让 Java 与 Native 生态的交互更加高效,也让 Java 在系统级编程场景中具备更强能力。但从安全角度看,任何新的 Native 交互能力都可能改变原有防护边界。
传统 Java 安全防护往往聚焦在 Runtime.exec()、ProcessBuilder、反射和类加载等路径上。但在 FFM API 场景下,Native 调用可能通过新的链路完成,导致仅基于传统 Java API 的 RASP Hook 出现覆盖不足。
因此,防御体系需要从单点拦截转向多层联动:
- JVM 层限制不必要的 Native Access;
- RASP 层扩展 FFM API Hook;
- WAF 层更新关键字和语义规则;
- 主机层基于 eBPF/EDR 监控系统调用;
- 开发侧避免执行不可信表达式;
- 企业侧建立 JDK 升级安全评估机制。
安全的本质不是阻止技术演进,而是在能力演进的同时同步更新边界认知。FFM API 的出现提醒我们:Java 应用安全不能只看 Java 层 API,更要关注 JVM 与操作系统之间的真实行为。
更多推荐



所有评论(0)