Codeql 目录穿越实战
字数 1453 2025-08-22 12:23:30

CodeQL目录穿越漏洞检测实战教学文档

1. 前言

CodeQL是一种强大的语义代码分析引擎,可以自动化检测代码中的安全漏洞。本教程将详细介绍如何使用CodeQL检测目录穿越(Directory Traversal)漏洞,以ofcms 1.1.3版本为例进行实战演示。

2. 环境搭建

2.1 下载和部署ofcms

  1. 下载ofcms 1.1.3版本源码
  2. 配置Tomcat容器部署应用
    • 可能遇到文件权限问题,需设置文件安全权限
    • 右键文件 → 属性 → 安全 → 设置所有权限

2.2 数据库配置

  1. 创建名为ofcms的数据库
  2. 导入SQL文件初始化数据库结构
  3. 修改数据库配置文件:
    • 文件路径:ofcms-V1.1.3/ofcms-admin/src/main/resources/dev/conf/db-config.properties
    • 修改为正确的数据库用户名和密码
    • 重命名为db.properties
  4. 重启Web容器

2.3 验证安装

访问应用,确认安装成功。后台管理界面位于特定路径。

3. 目录穿越漏洞复现

  1. 漏洞位置:模板文件操作功能
  2. 复现步骤:
    • 点击保存模板文件
    • 拦截请求包
    • 修改filename参数,添加../路径遍历序列
    • 观察结果,确认文件被写入非预期目录

4. 漏洞代码分析

4.1 漏洞代码位置

save方法中,存在以下可控参数:

  • 文件内容(content)
  • 文件名(filename)

4.2 漏洞产生原因

未对用户提供的文件名参数进行充分验证和过滤,导致可以包含路径遍历序列(../),从而将文件写入任意目录。

5. CodeQL实践

5.1 初始尝试

直接使用官方提供的CodeQL查询可能无法检测到此漏洞,因为:

  1. 默认的source点定义不包含特定框架(JFinal)的参数入口
  2. 需要自定义source点以匹配实际应用架构

5.2 Sink点分析

  1. 关键sink点是文件写入操作:
    FileUtils.writeString(new File(path), content);
    
  2. 在CodeQL中可表示为:
    class FileWriteSink extends MethodAccess {
      FileWriteSink() {
        this.getMethod().hasName("writeString") and
        this.getMethod().getDeclaringType().hasQualifiedName("org.apache.commons.io", "FileUtils")
      }
    }
    

5.3 Source点分析

  1. 在JFinal框架中,参数通常通过getter方法获取

  2. 需要识别框架特定的参数入口点:

    • JFinal Controller的getter方法
    • Servlet API参数
    • 自定义路由参数
  3. 自定义source点示例:

    class JFinalSource extends RemoteFlowSource {
      JFinalSource() {
        exists(Method m | 
          m.getDeclaringType().getASupertype*().hasQualifiedName("com.jfinal.core", "Controller") and
          (m.hasName("getPara") or m.hasName("getModel") or m.hasName("getBean"))
        )
      }
    }
    

5.4 完整查询示例

import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking

class FileWriteSink extends MethodAccess {
  FileWriteSink() {
    this.getMethod().hasName("writeString") and
    this.getMethod().getDeclaringType().hasQualifiedName("org.apache.commons.io", "FileUtils")
  }
}

class JFinalSource extends RemoteFlowSource {
  JFinalSource() {
    exists(Method m | 
      m.getDeclaringType().getASupertype*().hasQualifiedName("com.jfinal.core", "Controller") and
      (m.hasName("getPara") or m.hasName("getModel") or m.hasName("getBean"))
    )
  }
}

class DirectoryTraversalConfiguration extends TaintTracking::Configuration {
  DirectoryTraversalConfiguration() { this = "DirectoryTraversalConfiguration" }
  
  override predicate isSource(DataFlow::Node source) {
    source instanceof RemoteFlowSource
  }
  
  override predicate isSink(DataFlow::Node sink) {
    exists(FileWriteSink fws | 
      sink.asExpr() = fws.getArgument(0) or
      sink.asExpr() = fws.getArgument(1)
    )
  }
}

from DirectoryTraversalConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink, source, sink, "Potential directory traversal vulnerability"

6. 查询思路分析

6.1 Sink点扩展

除了文件写入操作,还可以考虑:

  1. 文件读取操作
  2. 文件删除操作
  3. 文件重命名操作

6.2 Source点扩展

  1. 框架特定source点:
    • JFinal Controller
    • Spring MVC Controller
    • Struts Action
  2. Servlet API source点:
    • HttpServletRequest参数
    • 请求头
    • Cookie值
  3. 自定义路由source点

6.3 完整框架支持

对于完整的企业级应用,需要构建更全面的source点模型:

  1. 识别应用使用的框架
  2. 为每个框架定义特定的参数入口点
  3. 考虑框架间的交互和参数传递

7. 优化建议

  1. 添加路径净化检查:

    predicate isSanitizer(DataFlow::Node node) {
      exists(MethodAccess ma |
        ma.getMethod().hasName("normalize") and
        ma.getMethod().getDeclaringType().hasQualifiedName("org.apache.commons.io", "FilenameUtils") and
        node.asExpr() = ma
      )
    }
    
  2. 添加对特定危险字符的检查:

    override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
      exists(StringLiteral sl | 
        sl.getStringValue().regexpMatch(".*\\.\\./.*") and
        pred.asExpr() = sl and
        succ.asExpr().getEnclosingCallable() = pred.asExpr().getEnclosingCallable()
      )
    }
    

8. 总结

通过本教程,我们学习了:

  1. 如何搭建和复现目录穿越漏洞
  2. CodeQL检测目录穿越的基本原理
  3. 如何自定义source和sink点以适应特定框架
  4. 如何构建完整的污点传播分析

关键要点:

  • 理解应用的框架结构对定义source点至关重要
  • 需要全面考虑所有可能的文件操作sink点
  • 路径净化检查是减少误报的重要手段
  • 特定框架需要特定的查询策略
CodeQL目录穿越漏洞检测实战教学文档 1. 前言 CodeQL是一种强大的语义代码分析引擎,可以自动化检测代码中的安全漏洞。本教程将详细介绍如何使用CodeQL检测目录穿越(Directory Traversal)漏洞,以ofcms 1.1.3版本为例进行实战演示。 2. 环境搭建 2.1 下载和部署ofcms 下载ofcms 1.1.3版本源码 配置Tomcat容器部署应用 可能遇到文件权限问题,需设置文件安全权限 右键文件 → 属性 → 安全 → 设置所有权限 2.2 数据库配置 创建名为 ofcms 的数据库 导入SQL文件初始化数据库结构 修改数据库配置文件: 文件路径: ofcms-V1.1.3/ofcms-admin/src/main/resources/dev/conf/db-config.properties 修改为正确的数据库用户名和密码 重命名为 db.properties 重启Web容器 2.3 验证安装 访问应用,确认安装成功。后台管理界面位于特定路径。 3. 目录穿越漏洞复现 漏洞位置:模板文件操作功能 复现步骤: 点击保存模板文件 拦截请求包 修改 filename 参数,添加 ../ 路径遍历序列 观察结果,确认文件被写入非预期目录 4. 漏洞代码分析 4.1 漏洞代码位置 在 save 方法中,存在以下可控参数: 文件内容(content) 文件名(filename) 4.2 漏洞产生原因 未对用户提供的文件名参数进行充分验证和过滤,导致可以包含路径遍历序列( ../ ),从而将文件写入任意目录。 5. CodeQL实践 5.1 初始尝试 直接使用官方提供的CodeQL查询可能无法检测到此漏洞,因为: 默认的source点定义不包含特定框架(JFinal)的参数入口 需要自定义source点以匹配实际应用架构 5.2 Sink点分析 关键sink点是文件写入操作: 在CodeQL中可表示为: 5.3 Source点分析 在JFinal框架中,参数通常通过getter方法获取 需要识别框架特定的参数入口点: JFinal Controller的getter方法 Servlet API参数 自定义路由参数 自定义source点示例: 5.4 完整查询示例 6. 查询思路分析 6.1 Sink点扩展 除了文件写入操作,还可以考虑: 文件读取操作 文件删除操作 文件重命名操作 6.2 Source点扩展 框架特定source点: JFinal Controller Spring MVC Controller Struts Action Servlet API source点: HttpServletRequest参数 请求头 Cookie值 自定义路由source点 6.3 完整框架支持 对于完整的企业级应用,需要构建更全面的source点模型: 识别应用使用的框架 为每个框架定义特定的参数入口点 考虑框架间的交互和参数传递 7. 优化建议 添加路径净化检查: 添加对特定危险字符的检查: 8. 总结 通过本教程,我们学习了: 如何搭建和复现目录穿越漏洞 CodeQL检测目录穿越的基本原理 如何自定义source和sink点以适应特定框架 如何构建完整的污点传播分析 关键要点: 理解应用的框架结构对定义source点至关重要 需要全面考虑所有可能的文件操作sink点 路径净化检查是减少误报的重要手段 特定框架需要特定的查询策略