从一道CTF题浅谈MyBatis与Ognl的那些事
字数 1535 2025-08-06 20:12:33

MyBatis与OGNL表达式安全风险深度分析

0x01 引言

本文通过分析一道CTF题目"ezsql",深入探讨MyBatis框架中OGNL表达式的使用及其潜在的安全风险。主要解决以下问题:

  1. 为什么SQL注入能解析OGNL表达式达到RCE的效果?
  2. Provider注解、XML配置和@Select配置的安全差异
  3. 预编译(#{})是否能规避此类风险

0x02 MyBatis封装SQL流程

2.1 核心解析过程

MyBatis工作流程分为构建和执行两个阶段:

  1. 构建阶段:解析XML/注解配置,转换为内部对象
  2. 执行阶段:通过配置信息执行SQL,与JDBC交互

关键解析流程:

// Mapper代理入口
org.apache.ibatis.binding.MapperProxy#invoke

// 执行Mapper方法
org.apache.ibatis.binding.MapperMethod#execute

// 获取BoundSql
MappedStatement#getBoundSql

// SQL源处理
SqlSource接口实现类:
- StaticSqlSource:静态SQL
- DynamicSqlSource:处理${}和动态SQL节点
- RawSqlSource:处理不包含${}和动态SQL节点
- ProviderSqlSource:处理@SelectProvider等注解

${}与#{}的区别

  • ${}:直接替换,调用DynamicSqlSource解析

    • 解析流程:TextSqlNode.apply()GenericTokenParser.parse()BindingTokenParser.handleToken()OgnlCache.getValue()
  • #{}:预编译处理,使用ParameterMappingTokenHandler替换为占位符?

0x03 安全风险分析

3.1 XML配置风险

理想情况下,如果XML中直接写入恶意OGNL:

<select id="test" parameterType="String" resultMap="User">
    select * from users where username like ${@java.lang.Runtime@getRuntime().exec("calc")}
</select>

实际场景中,MyBatis设计已规避此风险:

  • 仅解析原始${username}表达式
  • 用户输入值直接赋值,不进行二次OGNL解析

3.2 注解配置风险

3.2.1 普通注解(@Select等)

  • 与XML配置类似,SQL语句固定
  • 无法直接插入用户可控的OGNL表达式

3.2.2 Provider注解风险(@SelectProvider等)

关键风险点:

  1. 允许动态生成SQL字符串
  2. 用户输入可能直接拼接到SQL中
  3. 拼接后的SQL若包含${},会触发OGNL解析

漏洞利用示例:

// Controller
@GetMapping("/mybatis/ognl")
public List<User> mybatisOgnl(@RequestParam("name") String name) {
   return userMapper.getUserByUserName(name);
}

// Mapper
@SelectProvider(type = FindUserByName.class, method = "getUser")
List<User> getUserByUserName(String name);

class FindUserByName {
    public String getUser(String name) {
        String s = new SQL() {
            {
                SELECT("*");
                FROM("users");
                WHERE("username='" + name+"'"); // 直接拼接用户输入
            }
        }.toString();
        return s;
    }
}

POC:

${@java.lang.Runtime@getRuntime().exec("open /System/Applications/Calculator.app")}

0x04 防御措施

4.1 MyBatis 3.5.4+的黑名单机制

org.apache.ibatis.ognl.OgnlRuntime#InvokeMethod中:

  • _useStricterInvocation默认为true
  • 黑名单包含RuntimeProcessBuilder等危险类

4.2 修复建议

  1. 使用预编译:在Provider中采用#{}方式

    WHERE("username = #{name}")
    
  2. 输入过滤:必须拼接时,严格检查用户输入

  3. 升级MyBatis:使用3.5.4+版本利用内置黑名单

0x05 总结

风险场景矩阵:

配置方式 SQL注入风险 OGNL注入风险 备注
XML + ${} 需直接修改XML
@Select + ${} 同XML
@SelectProvider 用户输入可触发OGNL解析
任何方式 + #{} 推荐使用

关键结论:

  • Provider注解是OGNL注入的主要风险点
  • 预编译(#{})可同时防范SQL注入和OGNL注入
  • MyBatis 3.5.4+通过黑名单提供了额外防护
MyBatis与OGNL表达式安全风险深度分析 0x01 引言 本文通过分析一道CTF题目"ezsql",深入探讨MyBatis框架中OGNL表达式的使用及其潜在的安全风险。主要解决以下问题: 为什么SQL注入能解析OGNL表达式达到RCE的效果? Provider注解、XML配置和@Select配置的安全差异 预编译(#{})是否能规避此类风险 0x02 MyBatis封装SQL流程 2.1 核心解析过程 MyBatis工作流程分为构建和执行两个阶段: 构建阶段 :解析XML/注解配置,转换为内部对象 执行阶段 :通过配置信息执行SQL,与JDBC交互 关键解析流程: ${}与#{}的区别 ${} :直接替换,调用DynamicSqlSource解析 解析流程: TextSqlNode.apply() → GenericTokenParser.parse() → BindingTokenParser.handleToken() → OgnlCache.getValue() #{} :预编译处理,使用ParameterMappingTokenHandler替换为占位符? 0x03 安全风险分析 3.1 XML配置风险 理想情况下,如果XML中直接写入恶意OGNL: 实际场景中,MyBatis设计已规避此风险: 仅解析原始 ${username} 表达式 用户输入值直接赋值,不进行二次OGNL解析 3.2 注解配置风险 3.2.1 普通注解(@Select等) 与XML配置类似,SQL语句固定 无法直接插入用户可控的OGNL表达式 3.2.2 Provider注解风险(@SelectProvider等) 关键风险点: 允许动态生成SQL字符串 用户输入可能直接拼接到SQL中 拼接后的SQL若包含${},会触发OGNL解析 漏洞利用示例: POC: 0x04 防御措施 4.1 MyBatis 3.5.4+的黑名单机制 在 org.apache.ibatis.ognl.OgnlRuntime#InvokeMethod 中: _useStricterInvocation 默认为true 黑名单包含 Runtime 和 ProcessBuilder 等危险类 4.2 修复建议 使用预编译 :在Provider中采用#{}方式 输入过滤 :必须拼接时,严格检查用户输入 升级MyBatis :使用3.5.4+版本利用内置黑名单 0x05 总结 风险场景矩阵: | 配置方式 | SQL注入风险 | OGNL注入风险 | 备注 | |----------------|-------------|--------------|--------------------------| | XML + ${} | 高 | 低 | 需直接修改XML | | @Select + ${} | 高 | 低 | 同XML | | @SelectProvider| 高 | 高 | 用户输入可触发OGNL解析 | | 任何方式 + #{} | 低 | 低 | 推荐使用 | 关键结论: Provider注解是OGNL注入的主要风险点 预编译(#{})可同时防范SQL注入和OGNL注入 MyBatis 3.5.4+通过黑名单提供了额外防护