使用CodeQL寻找SSTI漏洞


污点追踪概述

污点追踪是指分析项目代码运行过程中可能存在安全隐患或者受污染的数据的流动情况。

在代码自动化安全审计的理论中,有一个最核心的三元组概念,就是source,sinksanitizer

  • source 是指漏洞污染链条的输入点,比如获取http请求的参数部分
  • sink 是指漏洞污染链条的执行点,比如SQL注入漏洞,最终执行SQL语句的函数就是sink
  • sanitizer 又叫做净化函数,是指在整个漏洞链条当中,如果存在一个方法阻断了整个传递链,那么这个方法就叫sanitizer

回忆我们平时审计代码的过程,是否是sourcesink同时存在,并且source -> sink 的链路是通的,才表示当前漏洞是存在的

CodeQL中进行污点追踪

这里给出一个简易版的flask SSTI漏洞的代码

from flask import Flask, request,render_template, render_template_string

app = Flask(__name__)
app.config['SECRET_KEY'] = '\xca\x0c\x86\x04\x98@\x02b\x1b7\x8c\x88]\x1b\xd7"+\xe6px@\xc3#\\'


@app.route('/ssti')
def ssti():
    if request.values.get('name'):
        name = request.values.get('name')
        template = "

{name}".format(name=name) return render_template_string(template) # template = Template('

%s' %name) # return template.render() # template = "

{{ name }}" # return render_template_string(template, name=name) else: return render_template_string('

输入name值

') if __name__ == '__main__': app.run()

我们直接使用全局污点追踪TaintTracking::Configuration

官方文档中的模板为

import python

class MyTaintTrackingConfiguration extends TaintTracking::Configuration {
  MyTaintTrackingConfiguration() { this = "..." }

  override predicate isSource(DataFlow::Node source) {
    ...
  }

  override predicate isSink(DataFlow::Node sink) {
    ...
  }
}

我们通过isSource(DataFlow::Node source)方法来设置source,在一个典型的flask应用中,思考一下我们的source是什么。显然,作为一个web应用,source理所当然是用户的输入,在flask中有很多种方式获取用户的输入,例如:request.form.get('title')或者request.values.get('name')

CodeQL中封装的RemoteFlowSource表示来自远程网络输入的数据流,也就是用户的网络输入,里面也包括了flask的输入,我们直接调用即可

override predicate isSource(DataFlow::Node source) {
        source instanceof RemoteFlowSource
}

这里我们暂且把instanceof理解为这种类型的意思

上述代码的sink在哪里呢,显然是

return render_template_string(template)

这里使用codeql中的API图进行定义

    override predicate isSink(DataFlow::Node sink) {
        exists(DataFlow::CallCfgNode call |
            call = API::moduleImport("flask").getMember("render_template_string").getACall() and
            sink = call.getArg(0)
          )
    }

exists子查询,是CodeQL谓词语法里非常常见的语法结构,它根据内部的子查询返回true or false,来决定筛选出哪些数据。

将存在render_template_string函数调用的第一个参数作为sink

设置好了sourcesink后,首尾如果能够连通,一个受污染的变量,能够流转到危险函数,才证明漏洞存在。连通工作是由CodeQL引擎本身来完成的,我们使用其中的config.hasFlow(src, sink)方法来判断即可

整体代码如下

import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import semmle.python.dataflow.new.RemoteFlowSources
import semmle.python.Concepts
import semmle.python.ApiGraphs

class SSTIVulConfig extends TaintTracking::Configuration {
    SSTIVulConfig() { this = "RemoteToFileConfiguration Tracking" }

    override predicate isSource(DataFlow::Node source) {
        source instanceof RemoteFlowSource
    }

    override predicate isSink(DataFlow::Node sink) {
        exists(DataFlow::CallCfgNode call |
            call = API::moduleImport("flask").getMember("render_template_string").getACall() and
            sink = call.getArg(0)
          )
    }
}

from SSTIVulConfig config, DataFlow::Node src, DataFlow::Node sink
where config.hasFlow(src, sink)
select src,sink

运行查询最终得到SSTI漏洞

参考链接

  • https://rmb122.com/2020/03/30/Codeql-踩坑记录/
  • https://rmb122.com/2020/03/31/Codeql-踩坑记录-二/
  • https://www.freebuf.com/articles/web/283795.html
  • https://codeql.github.com/docs/codeql-language-guides/using-api-graphs-in-python/
  • https://codeql.github.com/docs/codeql-language-guides/analyzing-data-flow-in-python/

END

建了一个微信的安全交流群,欢迎添加我微信备注进群,一起来聊天吹水哇,以及一个会发布安全相关内容的公众号,欢迎关注 ??

GIF GIF