前言
我们已经学习了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代码的基本查询
学习使用LGTM编写和运行简单的CodeQL查询
(为什么这里是用LGTM在线环境而不是VSCode本地环境,估计是官方人员觉得在线环境方便讲解并且有demo吧)
关于查询
我们编写查询找到项目中的只包含pass语句的if语句——实际上,这些代码都是多余的,例如
if error:pass
在LGTM平台上运行查询
LGTM平台链接:https://lgtm.com/
图形化界面,我们选择python作为查询语言,选择项目demo为:
将以下查询复制到查询控制台的文本框中
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
,可以看到查询操作的进度
查询需要一些时间才能返回结果。查询完成后,结果会显示在项目名称下方,查询结果展示在两列中,对应select
查询语句中的两个表达式。第一列对应表达式ifstmt
,并能够点击跳转到项目源代码中ifstmt
出现的位置,第二列是警报信息
我们可以点击匹配查询语句的代码进行跳转,在代码查看器中查看语句。
可以看到已经被高亮出来了
关于我们刚才的查询语句
在初始的import
语句之后,这个简单的查询包含了三个部分,在我们之前的QL
语法部分我们已经学习过了,即from
,where
,select
接下来详细解释各部分的含义
import python
每个查询都以一个或多个import语句开始,这里是导入python的标准codeql
库,用来解析python项目代码,生成相应的语法树,进而被我们的codeql
查询语句查询,找到相应的结果
from If ifstmt Stmt pass
这里定义了ifstmt
和pass
两个变量,其类型分别为If
(python中的if语句)和 Stmt
(statement
python中的语句)
当然这个时候我们不认识If
和Stmt
类型,在python.qll
里面我们可以看到python语言的codeql
标准库,后面我们会在提到,现在只需要对这两个类型有个模糊的认识即可。
-
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()
在这种情况下,带有pass
的if
语句并不是冗余的。对于这种情况我们可以修改原有的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
分支的语句
END
建了一个微信的安全交流群,欢迎添加我微信备注进群
,一起来聊天吹水哇,以及一个会发布安全相关内容的公众号,欢迎关注