首页
社区
课程
招聘
[推荐]CodeQL污点分析-JAVA序列化漏洞
2022-2-23 16:56 9156

[推荐]CodeQL污点分析-JAVA序列化漏洞

2022-2-23 16:56
9156

CodeQL workshop for Java: Apache Struts 中的不安全反序列化

(-->原贴)

问题陈述

序列化是将内存对象转换为文本或二进制输出格式的过程,通常用于共享或保存程序状态。然后可以通过反序列化过程在未来将这些序列化数据加载回内存。

 

在 Java、Python 和 Ruby 等语言中,反序列化不仅可以恢复原始数据,还可以恢复复杂类型,例如库和用户定义的类。这提供了强大的功能和灵活性,但如果反序列化不受限制地发生在不受信任的用户数据上,则会引入致命的攻击维度。

 

Apache Struts 是一个流行的开源 MVC 框架,用于在 Java 中创建 Web 应用程序。2017 年,来自 GitHub 安全实验室前身的研究人员发现了 CVE-2017-9805,这是 Apache Struts 中的一个 XML 反序列化漏洞,允许远程执行代码。

 

出现问题是因为 Apache Struts 框架的一部分包括接受多种不同格式或内容类型的请求的功能。它通过ContentTypeHandler接口提供了一个支持这些内容类型的可插拔系统,该接口提供了如下接口方法:

1
2
3
4
5
6
7
/**
 * Populates an object using data from the input stream
 * @param in The input stream, usually the body of the request
 * @param target The target, usually the action class
 * @throws IOException If unable to write to the output stream
 */
void toObject(Reader in, Object target) throws IOException;

新的内容类型处理程序是通过实现接口并定义一个 toObject 方法来定义的,该方法以指定的内容类型(以 Reader 的形式)获取数据并使用它来填充 Java 对象目标,通常需要反序列化。但是,in 参数通常是从请求的正文中填充的,没有经过消毒或安全检查。这意味着它应该被视为“不受信任”的用户数据,并且仅在某些安全条件下进行反序列化。

 

在本次workshop中,我们将编写一个查询以在由已知易受攻击的 Apache Struts 版本构建的数据库中查找 CVE-2017-9805

环境搭建及CodeQL语法

省略不表

Workshop

workshop分为几个步骤。你可以在每个步骤中编写一个查询,或者使用你在每个步骤中优化的单个查询。每个步骤都有一个hint,用于描述 Java 的 CodeQL 标准库中有用的类和谓词。

Section 1 查找 XML 反序列化
 

XStream 是一个 Java 框架,用于将 Java 对象序列化为 Apache Struts 使用的 XML。它提供了一种方法 XStream.fromXML 用于将 XML 反序列化为 Java 对象。默认情况下,输入不会以任何方式进行验证,并且容易受到远程代码执行漏洞的攻击。在本节中,我们将识别代码库中对 fromXML 的调用。

  1. 查找程序中的所有方法调用。
    Hints
  • 方法调用由 CodeQL Java 库中的 MethodAccess 类型表示。
    Solution
    ```SQL
    import java

from MethodAccess call
select call

1
2
3
4
5
6
7
8
9
10
11
12
13
2. 报告每个方法调用所调用的方法。
Hints
- 添加一个名为 Method 的 CodeQL 变量,其类型为 Method。
- `MethodAccess` 有一个谓词 `getMethod()` 用于返回方法。
- 添加 where 子句。
 
Solution
```SQL
import java
 
from MethodAccess call, Method method
where call.getMethod() = method
select call, method
  1. 查找程序中对调用 fromXML 的方法的所有调用。
    Hints
    Method.getName() 返回一个表示方法名称的字符串。

Solution

1
2
3
4
5
import java
 
from MethodAccess fromXML
where fromXML.getMethod().getName() = "fromXML"
select fromXML
  1. XStream.fromXML 方法反序列化第一个参数(即索引 0 处的参数)。报告反序列化的参数:
    hints
  • MethodCall.getArgument(int i) 返回第 i 个索引处的参数。
  • 参数是程序中的表达式,由 CodeQL 类Expr表示。引入一个新变量来保存参数表达式。

Solution

1
2
3
4
5
6
7
import java
 
from MethodAccess fromXML, Expr arg
where
  fromXML.getMethod().getName() = "fromXML" and
  arg = fromXML.getArgument(0)
select fromXML, arg
  1. 封装。
    Hints
    复制上一个查询的 where 子句。
    Solution
    ```SQL
    import java

predicate isXMLDeserialized(Expr arg) {
exists(MethodAccess fromXML |
fromXML.getMethod().getName() = "fromXML" and
arg = fromXML.getArgument(0)
)
}

 

from Expr arg
where isXMLDeserialized(arg)
select arg

1
2
3
4
5
6
7
8
9
###### Section 2: 从 ContentTypeHandler 中查找 toObject 方法的实现
与谓词一样,CodeQL 中的类可用于封装逻辑的可重用部分。类表示单组值,它们还可以包含特定于该组值的操作(称为成员谓词)。有许多 CodeQL 类(`MethodAccess、Method `等)和相关成员谓词(`MethodAccess.getMethod()、Method.getName() `等)的实例。
1. 创建一个名为 ContentTypeHandler 的 CodeQL 类,以找到接口 org.apache.struts2.rest.handler.ContentTypeHandler。
```C++
class ContentTypeHandler extends RefType {
  ContentTypeHandler() {
      // TODO Fill me in
  }
}

Hint
使用 RefType.hasQualifiedName(string packageName, string className)来识别具有给定包名和类名的类。

1
2
3
from RefType r
where r.hasQualifiedName("org.apache.struts2.rest.handler", "ContentTypeHandler")
select r

Solution

1
2
3
4
5
6
7
8
import java
 
/** The interface `org.apache.struts2.rest.handler.ContentTypeHandler`. */
class ContentTypeHandler extends RefType {
  ContentTypeHandler() {
    this.hasQualifiedName("org.apache.struts2.rest.handler", "ContentTypeHandler")
  }
}
  1. 创建一个名为 ContentTypeHandlerToObject 的 CodeQL 类,用于在其直接超类型包括 ContentTypeHandler 的类上识别名为 toObject 的方法。
    Hint
  • 使用 Method.getName() 来识别方法的名称。
  • 要确定该方法是否在直接父类包含 ContentTypeHandler 的类上声明,需要:
    • 使用Method.getDeclaringType()识别方法的声明类型。
    • 使用RefType.getASuperType() 识别该类型的父类型
    • 使用 instanceof 断言父类型之一是 ContentTypeHandler
      Solution
      1
      2
      3
      4
      5
      6
      7
      /** A `toObject` method on a subtype of `org.apache.struts2.rest.handler.ContentTypeHandler`. */
      class ContentTypeHandlerToObject extends Method {
      ContentTypeHandlerToObject() {
      this.getDeclaringType().getASupertype() instanceof ContentTypeHandler and
      this.hasName("toObject")
      }
      }
    1. toObject 方法应将第一个参数视为不受信任的用户输入。编写查询以查找 toObject 方法的第一个(即索引 0)参数。
      Hint
  • 使用 Method.getParameter(int index) 获取第 i 个索引参数。
  • 使用 ContentTypeHandlerToObject 类型的单个 CodeQL 变量创建查询。
    Solution

    1
    2
    from ContentTypeHandlerToObject toObjectMethod
    select toObjectMethod.getParameter(0)
    1. 不安全的 XML 反序列化
      我们现在已经确定了 (a) 程序中接收不可信数据的位置和 (b) 程序中可能执行不安全 XML 反序列化的位置。我们现在想将这两者联系在一起来问:不受信任的数据是否曾经流向可能不安全的 XML 反序列化调用?

    在程序分析中,我们称之为数据流问题。数据流可以帮助我们回答以下问题:这个表达式是否曾经包含一个源自程序中特定其他位置的值?

    我们可以将数据流问题可视化为通过有向图寻找路径之一,其中图的节点是程序中的元素,边表示这些元素之间的数据流。如果存在路径,则数据在这两个节点之间流动。

template

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
 * @name Unsafe XML deserialization
 * @kind problem
 * @id java/unsafe-deserialization
 */
import java
import semmle.code.java.dataflow.DataFlow
 
// TODO add previous class and predicate definitions here
 
class StrutsUnsafeDeserializationConfig extends DataFlow::Configuration {
  StrutsUnsafeDeserializationConfig() { this = "StrutsUnsafeDeserializationConfig" }
  override predicate isSource(DataFlow::Node source) {
    exists(/** TODO fill me in **/ |
      source.asParameter() = /** TODO fill me in **/
    )
  }
  override predicate isSink(DataFlow::Node sink) {
    exists(/** TODO fill me in **/ |
      /** TODO fill me in **/
      sink.asExpr() = /** TODO fill me in **/
    )
  }
}
 
from StrutsUnsafeDeserializationConfig config, DataFlow::Node source, DataFlow::Node sink
where config.hasFlow(source, sink)
select sink, "Unsafe XML deserialization"
  1. 使用第 2 节编写的查询完成 isSource 谓词。
    Hint
    可以通过以下方式将查询子句转换为谓词:
  • 将 from 部分中的变量声明转换为存在的变量声明
  • 将 where 子句条件(如果有)放在存在的主体中
  • 添加一个将选择等同于谓词参数之一的条件。
    请记住包含您之前定义的 ContentTypeHandlerToObject 类。
    Solution
    1
    2
    3
    4
    5
    override predicate isSource(Node source) {
      exists(ContentTypeHandlerToObject toObjectMethod |
        source.asParameter() = toObjectMethod.getParameter(0)
      )
    }
  1. 使用第 1 节编写的最终查询完成 isSink 谓词。记住使用 isXMLDeserialized 谓词!
    1
    2
    3
    4
    5
    6
    override predicate isSink(Node sink) {
     exists(Expr arg |
       isXMLDeserialized(arg) and
       sink.asExpr() = arg
     )
    }

更新查询,使其不仅报告sink,还报告source和到该source的路径。可以通过进行以下更改来做到这一点:

  • @kindproblem转换为path-problem。这告诉 CodeQL 工具链将此查询的结果解释为路径结果。
  • 添加一个新的导入 DataFlow::PathGraph,它将在查询结果旁边报告路径数据。
  • sourcesinkDataFlow::Node 更改为 DataFlow::PathNode,以确保节点保留路径信息。
  • 使用 hasFlowPath 而不是 hasFlow。
  • 更改选择以将source和sink报告为第二列和第三列。工具链将此数据与来自 PathGraph 的路径信息相结合以构建路径。

QL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* @name Unsafe XML deserialization
* @kind path-problem
* @id java/unsafe-deserialization
*/
import java
import semmle.code.java.dataflow.DataFlow
import DataFlow::PathGraph
 
predicate isXMLDeserialized(Expr arg) {
  exists(MethodAccess fromXML |
    fromXML.getMethod().getName() = "fromXML" and
    arg = fromXML.getArgument(0)
  )
}
 
/** The interface `org.apache.struts2.rest.handler.ContentTypeHandler`. */
class ContentTypeHandler extends RefType {
  ContentTypeHandler() {
    this.hasQualifiedName("org.apache.struts2.rest.handler", "ContentTypeHandler")
  }
}
 
/** A `toObject` method on a subtype of `org.apache.struts2.rest.handler.ContentTypeHandler`. */
class ContentTypeHandlerToObject extends Method {
  ContentTypeHandlerToObject() {
    this.getDeclaringType().getASupertype() instanceof ContentTypeHandler and
    this.hasName("toObject")
  }
}
 
class StrutsUnsafeDeserializationConfig extends DataFlow::Configuration {
  StrutsUnsafeDeserializationConfig() { this = "StrutsUnsafeDeserializationConfig" }
  override predicate isSource(DataFlow::Node source) {
    exists(ContentTypeHandlerToObject toObjectMethod |
      source.asParameter() = toObjectMethod.getParameter(0)
    )
  }
  override predicate isSink(DataFlow::Node sink) {
    exists(Expr arg |
      isXMLDeserialized(arg) and
      sink.asExpr() = arg
    )
  }
}
 
from StrutsUnsafeDeserializationConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink, source, sink, "Unsafe XML deserialization"

[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2022-2-24 10:33 被Potatoes编辑 ,原因:
收藏
点赞1
打赏
分享
最新回复 (2)
雪    币: 12776
活跃值: (16307)
能力值: (RANK:730 )
在线值:
发帖
回帖
粉丝
有毒 10 2022-2-24 09:19
2
0
师傅好,咱们有web板块,建议将帖子转移到web板块哈。还有个小瑕疵是标题是不是拼错了
雪    币: 219
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
Potatoes 2022-2-24 10:06
3
0
有毒 师傅好,咱们有web板块,建议将帖子转移到web板块哈。还有个小瑕疵是标题是不是拼错了[em_78]
确实,感谢相告!
游客
登录 | 注册 方可回帖
返回