前言

我们已经学习了​​QL​​的基础语法,已经可以对问题进行简单的查询了。但对于某一种特定的语言,以我们现在的基础还是不能对其项目代码进行清晰描述。

比如,我们想要获取​​python​​​编写的​​flask​​web应用中可能存在SSTI漏洞的点

from flask import Flask
from flask import request
from flask import config
from flask import render_template_string
app = Flask(__name__)

app.config['SECRET_KEY'] = "flag{SSTI_123456}"
@app.route('/')
def hello_world():
return 'Hello World!'

@app.errorhandler(404)
def page_not_found(e):
template = '''
{%% block body %%}
<div class="center-content error">
<h1>Oops! That page doesn't exist.</h1>
<h3>%s</h3>
</div>
{%% endblock %%}
''' % (request.args.get('404_url'))
return render_template_string(template), 404

if __name__ == '__main__':
app.run(host='0.0.0.0',debug=True)

可以看到这里我们需要检测代码中是否存在​​request.args.get()​​​获取的参数,并追踪该方式获得的参数​​404_url​​​在后续的过程中是否经过了过滤,又或者会不会有一个等式​​405_test=404_url+"test code"​​​,导致​​405_test​​​参数实际上也被污染了。最后看这些参数是否会回显​​render_template_string()​​到页面上。

整个过程需要考虑到参数在代码中的运行流程,所以传统的正则表达式匹配敏感字符在这种情况下就捉襟见肘了。

所以我们还需要学习​​codeql​​对python代码进行查询的相关基础知识,比如python的表达式,参数,函数等,这样才能在自己独立审计的时候举一反三。

官方教程链接:​​https://codeql.github.com/docs/codeql-language-guides/codeql-for-python/​

当然​​codeql​​​也支持其他语言的查询,链接为:
​​​https://codeql.github.com/docs/codeql-language-guides/​

python代码的控制流

我们可以编写CodeQL查询来探索python程序的控制流图,例如发现无法访问的代码或者互斥的代码块

关于分析控制流

在分析Scope类的控制流图时,我们可以借助CodeQL提供的两个类​​ControlFlowNode​​​和​​BasicBlock​​​类。在进行变种分析的时候,我们经常遇到​​我们能从B点到达A点吗?​​​或者​​能否在不经过A点的前提下到达B点?​

为了回答这些我呢提,我们需要借助​​AstNode​​类,这个类可以表示一个语法元素并对应其源代码,使查询的结果变得更加易于理解

ControlFlowNode类

​ControlFlowNode​​​表示的是控制流图中的节点。抽象语法树AST节点与控制流节点之间存在一对多的关系,因为每个语法元素,即​​AstNode​​​类,可以映射到0个,1个或者多个​​ControlFlowNode​​​类,但是每个​​ControlFlowNode​​​类仅映射到一个​​AstNode​

为什么我们要学习看上去这么复杂的关系?参考下面这个例子:

try:
might_raise()
if cond:
break
finally:
close_resource()

在上面的代码中存在许多的路径。比如调用​​close_resource()​​的路径就有三条,并且各不相同:

  • 常规路径,即​​cond=false​
  • break路径,即​​cond=true​
  • 异常路径,即​​might_raise()​​引发异常所导致的路径

具体可以参考下面带注释的流程图:

CodeQL分析python代码5-python中的控制流_python

​ControlFlowNode​​​和​​AstNode​​​类最简单的用法是查找不可达的代码,每条通过​​AstNode​​​的路径都有一个​​ControlFlowNode​​​。因此所有没有对应​​ControlFlowNode​​​的​​AstNode​​都是不可达的

查找无法访问的AstNode示例

import python

from AstNode node
where not exists(node.getAFlowNode())
select node

在LGTM上的运行结果如图

CodeQL分析python代码5-python中的控制流_控制流_02

可以看到这里返回了大量的结果,其中一些是我们想要找到的不可达的代码,它们没有控制流节点。然而因为Module类也是AstNode类的一个子类,因此,上面的查询结果中也包含有用C语言实现模块,以及不含有源代码的模块。这些不是我们想要的,因此我们最好还是查找所有不可达的语句

查找不可达语句的示例

import python

from Stmt s
where not exists(s.getAFlowNode())
select s

CodeQL分析python代码5-python中的控制流_flask_03


这时候返回的结果就明显减少了。

BasicBlock类

​BasicBlock​​​类表示控制流节点的基本构造块,该类对直接编写查询不是很有用,但对于构建复杂的分析非常有用,例如数据流。为什么呢?因为​​BasicBlock​​类共享控制流节点的许多有用的属性,例如从哪里到哪里,以及什么支配着什么等等。但是由于基本构造块比控制流节点少,所以查询起来性能会更好(更快,更节约内存)

查找互斥的基本构造块

假如我们有以下python代码

if condition():
return 0
pass

我们能否断定在单次执行该代码时不可能同时到达​​return 0​​​和​​pass​​语句吗?要想让两个基本构造块互斥,就必须使其彼此不可达。我们可以这样写查询语句:

import python

from BasicBlock b1, BasicBlock b2
where b1 != b2 and not b1.strictlyReaches(b2) and not b2.strictlyReaches(b1)
select b1, b2

但是根据我们对于互斥的理解,如果两个基本构造块位于不同的作用域中,那么它们就是互斥的。为了使结果的准确率更高,我们可以要求检查从同一个函数入口点访问的两个基本块是否彼此不可达

exists(Function shared, BasicBlock entry |
entry.contains(shared.getEntryNode()) and
entry.strictlyReaches(b1) and entry.strictlyReaches(b2)
)

结合上面的讨论,我们可以得到:

在同一函数中查找互斥块的示例

import python

from BasicBlock b1, BasicBlock b2
where b1 != b2 and not b1.strictlyReaches(b2) and not b2.strictlyReaches(b1) and
exists(Function shared, BasicBlock entry |
entry.contains(shared.getEntryNode()) and
entry.strictlyReaches(b1) and entry.strictlyReaches(b2)
)
select b1, b2

显然,这个查询通常会返回大量的结果。不过因为其经典性我们将其作为控制流分析的示例。诸如此类的控制流分析对于数据流分析来说使非常有帮助的。

END

公众号,欢迎关注 😃


CodeQL分析python代码5-python中的控制流_flask_04