CodeQL分析python代码1-python代码的基本查询

前言

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

比如,我们想要获取python编写的flaskweb应用中可能存在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代码的基本查询

学习使用LGTM编写和运行简单的CodeQL查询
(为什么这里是用LGTM在线环境而不是VSCode本地环境,估计是官方人员觉得在线环境方便讲解并且有demo吧)

关于查询

我们编写查询找到项目中的只包含pass语句的if语句——实际上,这些代码都是多余的,例如

if error:pass

在LGTM平台上运行查询

LGTM平台链接:https://lgtm.com/
图形化界面,我们选择python作为查询语言,选择项目demo为:
CodeQL分析python代码1-python代码的基本查询
将以下查询复制到查询控制台的文本框中

import python

from If ifstmt, Stmt pass
where pass = ifstmt.getStmt(0) and
  pass instanceof Pass
select ifstmt, "This 'if' statement is redundant."

如果一切正常,Run按钮会变为绿色,表示我们可以运行查询

点击Run,可以看到查询操作的进度
CodeQL分析python代码1-python代码的基本查询
查询需要一些时间才能返回结果。查询完成后,结果会显示在项目名称下方,查询结果展示在两列中,对应select查询语句中的两个表达式。第一列对应表达式ifstmt,并能够点击跳转到项目源代码中ifstmt出现的位置,第二列是警报信息

CodeQL分析python代码1-python代码的基本查询

我们可以点击匹配查询语句的代码进行跳转,在代码查看器中查看语句。
CodeQL分析python代码1-python代码的基本查询
可以看到已经被高亮出来了

关于我们刚才的查询语句

在初始的import语句之后,这个简单的查询包含了三个部分,在我们之前的QL语法部分我们已经学习过了,即from,where,select

接下来详细解释各部分的含义

  • import python

每个查询都以一个或多个import语句开始,这里是导入python的标准codeql库,用来解析python项目代码,生成相应的语法树,进而被我们的codeql查询语句查询,找到相应的结果

  • from If ifstmt Stmt pass

这里定义了ifstmtpass两个变量,其类型分别为If (python中的if语句)和 Stmtstatement python中的语句)

当然这个时候我们不认识IfStmt类型,在python.qll里面我们可以看到python语言的codeql标准库,后面我们会在提到,现在只需要对这两个类型有个模糊的认识即可。
CodeQL分析python代码1-python代码的基本查询

  • where pass=ifstmt.getStmt(0) and pass instanceof Pass
    这里用到了getStmt()方法,我们可以直接查看源代码是如何实现的
/** INTERNAL: See the class `If` for further information. */
library class If_ extends @py_If, Stmt {
  /** 省略 */

  /** Gets the nth if-true statement of this if statement. */
  Stmt getStmt(int index) { result = this.getBody().getItem(index) }

可以看出来getStmt(int index)是为了获取 if 语句主体结构中的第一个元素

所以这句话的意思是获取if语句中的第一个元素,并且这个元素必须是Pass类型的

接下来的这句话select ifstmt, "This 'if' statement is redundant.",显示查询的结果

不断改进你的查询

编写查询实质上是一个迭代的过程,就像我们编写poc脚本一样,需要根据返回的结果不断改进使其臻至完美。我们编写一个简单的QL查询,然后在运行它的时候,根据获得的结果会发现很多以前没有考虑过的示例或者改进的机会

去除误报

浏览我们的基础查询的结果可以发现还有很大的进步空间。在结果中,我们能够找到很多if带有else分支的语句,这种情况下的pass就不是冗余的,例如:

if cond():
  pass
else:
  do_something()

在这种情况下,带有passif语句并不是冗余的。对于这种情况我们可以修改原有的QL查询让它在具有else分支时忽略pass语句

要排除if具有else分支的语句,我们需要修改原来的where子句,添加条件
and not exists(ifstmt.getOrelse()),现在where语句变成了

where pass = ifstmt.getStmt(0) and
  pass instanceof Pass and
  not exists(ifstmt.getOrelse())

点击运行之后可以看到过滤之后的查询结果变少了,因为不再包含if带有else分支的语句
CodeQL分析python代码1-python代码的基本查询

END

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

上一篇:每日一题(2020年C/C++ B组第二场省赛第六题编程题)


下一篇:图解python | pass语句