A-A+

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

2024年12月30日 15:10 学习笔记 暂无评论 共7283字 (阅读44 views次)

【注意:此文章为博主原创文章!转载需注意,请带原文链接,至少也要是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 请求参数传递表达式),恶意利用可能出现以下问题:

  1. 动态执行系统命令(通过调用 Java 的 Runtime.getRuntime().exec() 方法)。
  2. 未授权访问或修改敏感属性。
  3. 扩散攻击面,诱发远程命令执行漏洞。

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 限制。原因如下:

  1. 测试环境往往与生产环境相连:测试环境偶尔会复用生产环境中的部分数据或接口,一旦被攻破,可能威胁生产环境安全。
  2. 恶意行动可通过测试环境传递到更大的范围:攻击者可能通过 OGNL 播种恶意脚本,用于后续阶段攻击。
  3. 扩散风险:许多企业使用 CI/CD 流程直接将测试环境的镜像部署到生产环境,如果对调试工具和 OGNL 的限制设计不完善,漏洞可能直接扩散到生产。

如何在测试环境安全地使用 OGNL?

如果确实有理由需要使用 OGNL 表达式,需严格控制其开放条件:

  1. 权限隔离
    • 限制 OGNL 执行的上下文,仅允许特定的模块或方法调用,而非随意访问整个 Java 对象。
    • 调试环境需要强认证,避免匿名访问。
  2. 黑白名单规则
    • 使用 OGNL 的场景需硬编码白名单,限制允许加载、访问的类和方法。
    • 禁止访问核心类(如 java.lang.System 或 java.lang.Runtime)。
  3. 容器隔离
    • 测试环境的 Arthus 和 OGNL 不应与生产环境共享同一容器或主机环境,必须隔离存储路径和网络权限。
  4. 监控日志
    • 对所有通过 Arthus 运行的 OGNL 表达式进行日志记录,并设置关键安全检测,例如拒绝非预期调用。

总结

  1. PaaS 是一种支持应用部署、测试、运维的一站式平台,主要解决开发效率问题。
  2. Arthus 是一种强大的 Java 调试工具,用于运行时动态诊断和调试。
  3. OGNL 是 Java 的动态表达式语言,可以高效调用对象方法和属性,但因功能强大也被滥用于注入攻击。
  4. OGNL 与 Arthus 的安全问题:在任何环境下,OGNL 配合 Arthus 都具有极高的风险,可能造成远程命令执行和信息泄露。
  5. 即便是测试环境,也不建议放开 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 支持动态访问实例对象的属性和方法。例如,对象 usergetName() 方法,OGNL 表达式可调用 user.nameuser.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);
        }
    }
}

以上代码的策略是明确列出允许调用的对象实例方法(例如 toStringgetName),拒绝调用其他任何方法。


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 的安全风险。重点注意:

  1. 只允许必要的类、方法被解析。
  2. 强制性在所有 OGNL 解析调用之前验证访问约束。
  3. 出现违规则立即拒绝,抛出异常。

上述方案可应用于生产、测试环境,确保动态表达式功能协作的安全性。

布施恩德可便相知重

微信扫一扫打赏

支付宝扫一扫打赏

×

给我留言