Arthus 动态诊断 结合 OGNL 可能存在的风险

【注意:此文章为博主原创文章!转载需注意,请带原文链接,至少也要是txt格式!】
什么是 PaaS?
PaaS(Platform as a Service)即平台即服务,是一种云计算服务模式,提供开发者用于构建、部署和运行应用程序的开发架构和运行环境。开发者无需管理底层硬件、虚拟化、网络等基础设施,只需关注应用的开发和运维。例如,开发者可以通过 PaaS 平台快速部署应用和服务,管理扩容需求等。
PaaS 提供以下常见功能:
- 应用程序运行时环境。
- 数据库服务、存储服务、队列等中间件服务。
- 自动化部署、弹性扩展。
- 监控、日志和诊断工具。
常见的 PaaS 平台有 Heroku、Google App Engine、AWS Elastic Beanstalk 和国内的阿里云 EDAS 等。
什么是 Arthus?
Arthus 是一个 Java Agent 工具,可以注入运行时的 Java 应用中,用于动态调试和排查系统问题。它类似于工具 Arthas,但更强调插件化和易扩展性,方便开发者在不修改程序代码、不重启应用的前提下执行调试。
Arthus 支持在应用运行时执行动态方法调用、查看 Java 对象、修改系统状态或代码行为,大大提升排查问题的效率。但由于它的能力很强大,如果滥用或被恶意操作,也可能带来安全风险。
什么是 OGNL?
OGNL(Object-Graph Navigation Language)是 Object-Graph Navigation Language 的缩写,是 Java 中的一个功能强大的表达式语言库。OGNL 允许开发者:
- 动态调用 Java 对象的方法。
- 访问对象的属性。
- 执行动态表达式逻辑。
常见的 OGNL 使用场景是框架级别的表达式解析器,比如在 Apache Struts2 中曾广泛使用 OGNL 实现数据绑定和动态方法调用。
OGNL 功能强大、灵活,但也带来了一定的安全隐患。如果用户能够控制 OGNL 表达式内容(比如通过 HTTP 请求参数传递表达式),恶意利用可能出现以下问题:
- 动态执行系统命令(通过调用 Java 的
Runtime.getRuntime().exec()
方法)。 - 未授权访问或修改敏感属性。
- 扩散攻击面,诱发远程命令执行漏洞。
OGNL 的安全问题曾导致著名的 Struts2 框架多个版本被曝出漏洞(如 S2-045)。
例如,以下 OGNL 表达式可以直接注入并执行任意命令:
#rt = @java.lang.Runtime@getRuntime(),
#proc = #rt.exec('ls -la'),
#is = #proc.getInputStream(),
new java.io.InputStreamReader(#is),
new java.io.BufferedReader(#is).readLines()
PaaS 引入 Arthus 和 OGNL 的风险分析
结合 PaaS 平台的使用背景,问题可以拆分为以下几点:
1. Arthus 与 OGNL 的组合问题
Arthus 本身用于运行时动态诊断,功能非常强大,但一旦结合 OGNL,不加限制的表达式解析可能带来以下风险:
- 远程命令执行风险:恶意用户可以通过 OGNL 表达式调用 Runtime 的相关方法,注入任意系统命令以控制容器。
- 读取敏感数据的风险:OGNL 能访问对象属性和方法,可能泄露系统配置、敏感环境变量。
- 调试工具滥用风险:Arthus 本身并不是为生产环境设计的,赋能过强,将动态执行权限暴露在不受控环境中,极容易被滥用。
例如,根据作用:
- 如果暴露了某个 HTTP 接口,通过调用 Arthus 表达动态 OGNL 表达式并注入系统命令,可能控制整个 PaaS 容器。
- 如果开发环境泄露到互联网上,未限制调用者权限,等同于让黑客获取了远程 Shell。
2. PaaS 场景中特别值得注意的地方
PaaS 平台托管的 Java 应用通常运行在虚拟机或容器环境中,以下场景尤为敏感:
- 多租户环境:多个用户共享同一底层资源,如果某用户因 OGNL 注入漏洞被攻破,可能影响其他租户。
- 容器隔离不足:OGNL 注入可能配合容器逃逸漏洞(比如 Docker 的历史问题)攻击宿主机。
- 自动化功能暴露:PaaS 环境通常提供自动化 scaling 和管理接口,OGNL 调用配合 Arthus 可能进一步利用这些接口加速破坏范围。
3. 开发和测试中的常见问题
- 一些开发者在测试时放松权限限制(如开放 debugger 工具、分配管理员权限)。
- 开发中使用调试工具,但未对外部调用权限加以保护,可能误将系统暴露到了外部。
- 即便测试环境没有直接暴露,攻击者可以通过横向渗透或 supply chain 手段获取配置。
测试环境是否应放开 OGNL 限制?
是否可以放开
即便是测试环境,也不应随意放开 OGNL 限制。原因如下:
- 测试环境往往与生产环境相连:测试环境偶尔会复用生产环境中的部分数据或接口,一旦被攻破,可能威胁生产环境安全。
- 恶意行动可通过测试环境传递到更大的范围:攻击者可能通过 OGNL 播种恶意脚本,用于后续阶段攻击。
- 扩散风险:许多企业使用 CI/CD 流程直接将测试环境的镜像部署到生产环境,如果对调试工具和 OGNL 的限制设计不完善,漏洞可能直接扩散到生产。
如何在测试环境安全地使用 OGNL?
如果确实有理由需要使用 OGNL 表达式,需严格控制其开放条件:
- 权限隔离:
- 限制 OGNL 执行的上下文,仅允许特定的模块或方法调用,而非随意访问整个 Java 对象。
- 调试环境需要强认证,避免匿名访问。
- 黑白名单规则:
- 使用 OGNL 的场景需硬编码白名单,限制允许加载、访问的类和方法。
- 禁止访问核心类(如
java.lang.System
或java.lang.Runtime
)。
- 容器隔离:
- 测试环境的 Arthus 和 OGNL 不应与生产环境共享同一容器或主机环境,必须隔离存储路径和网络权限。
- 监控日志:
- 对所有通过 Arthus 运行的 OGNL 表达式进行日志记录,并设置关键安全检测,例如拒绝非预期调用。
总结
- PaaS 是一种支持应用部署、测试、运维的一站式平台,主要解决开发效率问题。
- Arthus 是一种强大的 Java 调试工具,用于运行时动态诊断和调试。
- OGNL 是 Java 的动态表达式语言,可以高效调用对象方法和属性,但因功能强大也被滥用于注入攻击。
- OGNL 与 Arthus 的安全问题:在任何环境下,OGNL 配合 Arthus 都具有极高的风险,可能造成远程命令执行和信息泄露。
- 即便是测试环境,也不建议放开 OGNL 限制,可以通过权限隔离、白名单、容器隔离和日志监控等手段保障安全。
问:基于这种情况,那应该如何使用 OGNL 的场景需硬编码白名单,限制允许加载、访问的类和方法。具体应该怎么限制?
要安全地使用 OGNL(尤其是将其用于动态表达式解析的场景),必须考虑对其功能进行严格的限制,以避免被攻击者滥用。以下是通过硬编码白名单的方式限制 OGNL 允许加载、访问的类和方法的具体实现方案:
OGNL 的安全限制策略与实现
1. 限制加载的类
OGNL 的强大之处在于它可以通过静态方法调用(如 @Class@method()
)或动态实例访问对象。为了避免恶意利用,需要明确限制将哪些类允许被访问。
实现步骤:
- 定义白名单:列出允许通过 OGNL 访问的类集合。
- 拦截不在白名单中的类加载请求。
- 对于任何试图访问非白名单的类,立即抛出异常,不执行表达式。
Java 实现示例:
import ognl.Node;
import java.util.HashSet;
import java.util.Set;
public class OGNLWhiteListEnforcer {
// 定义允许访问的类的白名单
private static final Set<String> ALLOWED_CLASSES = new HashSet<>();
static {
// 添加需要允许访问的类(白名单)
ALLOWED_CLASSES.add("com.example.MyService"); // 自定义合法类
ALLOWED_CLASSES.add("java.util.Date"); // 允许访问 Date
ALLOWED_CLASSES.add("java.lang.String"); // 允许 String
}
// 验证类是否在白名单中
public static boolean isClassAllowed(String className) {
return ALLOWED_CLASSES.contains(className);
}
// 在表达式解析前,检查是否访问了未授权的类并抛出异常
public static void enforceWhiteList(Object target) {
if (target != null) {
String className = target.getClass().getName();
if (!isClassAllowed(className)) {
throw new SecurityException("Access to class " + className + " is not allowed by OGNL.");
}
}
}
// 核心:在 OGNL 解析点注入安全校验
public static Object secureOgnlParse(String expression, Object root) {
try {
enforceWhiteList(root);
return ognl.Ognl.getValue(expression, root);
} catch (Exception e) {
throw new RuntimeException("OGNL parsing failed: " + e.getMessage(), e);
}
}
}
在以上代码中,通过 ALLOWED_CLASSES
定义了只能被访问的类。如果表达式试图访问非白名单类,则会抛出 SecurityException
。
2. 限制加载的静态方法
OGNL 表达式支持通过 @ClassName@method()
调用静态方法。这种机制非常危险,比如:
@java.lang.Runtime@getRuntime().exec("ls -la")
@java.lang.System@getProperty("user.home")
为了防止滥用静态方法调用,可以限制静态方法所属的类及方法名称。
实现步骤:
- 定义静态方法白名单,允许的类以及方法组合。
- 在解析静态方法之前,检查调用是否符合白名单规则。
- 拦截未授权的调用并抛出异常。
示例代码:
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class OGNLStaticMethodWhiteList {
// 定义允许访问的静态方法(基于类名和方法名)
private static final Map<String, Set<String>> ALLOWED_STATIC_METHODS = new HashMap<>();
static {
// 例如:只允许 java.lang.Math 的 abs 和 max 方法
Set<String> mathMethods = new HashSet<>();
mathMethods.add("abs");
mathMethods.add("max");
ALLOWED_STATIC_METHODS.put("java.lang.Math", mathMethods);
}
// 检查静态方法是否允许
public static boolean isStaticMethodAllowed(String className, String methodName) {
Set<String> methods = ALLOWED_STATIC_METHODS.get(className);
return methods != null && methods.contains(methodName);
}
// 核心:静态方法校验逻辑
public static void enforceStaticMethodWhiteList(String className, String methodName) {
if (!isStaticMethodAllowed(className, methodName)) {
throw new SecurityException("Static method " + className + "." + methodName + " is not allowed.");
}
}
// 示例调用:在表达式解析前验证静态方法
public static Object secureStaticMethodParse(String className, String methodName, Object... args) {
enforceStaticMethodWhiteList(className, methodName); // 验证
try {
Class<?> clazz = Class.forName(className);
return clazz.getMethod(methodName, Object.class).invoke(null, args);
} catch (Exception e) {
throw new RuntimeException("Static method invocation failed: " + e.getMessage(), e);
}
}
}
- 在
ALLOWED_STATIC_METHODS
中定义了允许的静态方法(例如java.lang.Math.abs
和java.lang.Math.max
)。 - 如果表达式试图调用未作授权的静态方法,则会直接拒绝。
3. 限制实例方法调用
OGNL 支持动态访问实例对象的属性和方法。例如,对象 user
有 getName()
方法,OGNL 表达式可调用 user.name
或 user.getName()
。
此机制可能被攻击者利用。如果对象实例暴露某些敏感属性或提供执行系统指令的实例方法,则会成为漏洞入口。
实现步骤:
- 定义允许访问的实例方法白名单。
- 拦截未授权的方法调用。
示例代码:
import java.util.HashSet;
import java.util.Set;
public class OGNLInstanceMethodWhiteList {
// 定义允许调用的实例方法
private static final Set<String> ALLOWED_INSTANCE_METHODS = new HashSet<>();
static {
ALLOWED_INSTANCE_METHODS.add("toString");
ALLOWED_INSTANCE_METHODS.add("getName");
}
// 判断实例方法是否被允许
public static boolean isInstanceMethodAllowed(String methodName) {
return ALLOWED_INSTANCE_METHODS.contains(methodName);
}
// 强制校验实例方法的白名单
public static void enforceInstanceMethodWhiteList(String methodName) {
if (!isInstanceMethodAllowed(methodName)) {
throw new SecurityException("Instance method " + methodName + " is not allowed.");
}
}
// 在 OGNL 表达式解析时拦截方法调用
public static Object secureInstanceMethodInvoke(Object target, String methodName, Object... args) {
enforceInstanceMethodWhiteList(methodName); // 检查是否在白名单中
try {
return target.getClass().getMethod(methodName).invoke(target, args);
} catch (Exception e) {
throw new RuntimeException("Instance method invocation failed: " + e.getMessage(), e);
}
}
}
以上代码的策略是明确列出允许调用的对象实例方法(例如 toString
、getName
),拒绝调用其他任何方法。
4. 综合实现 OGNL 安全解析器
基于上述三个限制(类、静态方法、实例方法)的实现,可以创建一个安全的 OGNL 解析器,接管整体解析过程。
import ognl.Ognl;
import ognl.OgnlContext;
public class SecureOGNLParser {
public static Object parseExpression(String expression, Object root) {
try {
Object parsedExpression = Ognl.parseExpression(expression);
// 检查类白名单
OGNLWhiteListEnforcer.enforceWhiteList(root);
// 执行 OGNL
OgnlContext context = new OgnlContext();
return Ognl.getValue(parsedExpression, context, root);
} catch (Exception e) {
throw new RuntimeException("OGNL execution blocked due to security violation: " + e.getMessage());
}
}
}
安全解析器会在解析任意 OGNL 表达式时,检查底层的类、静态方法和实例方法是否合规。
总结
通过对 类、静态方法、实例方法 的访问设置白名单,结合扩展的 OGNL 解析器,可以大幅度减少 OGNL 的安全风险。重点注意:
- 只允许必要的类、方法被解析。
- 强制性在所有 OGNL 解析调用之前验证访问约束。
- 出现违规则立即拒绝,抛出异常。
上述方案可应用于生产、测试环境,确保动态表达式功能协作的安全性。
布施恩德可便相知重
微信扫一扫打赏
支付宝扫一扫打赏