CodeQL从零到精通第五部分:调试查询
当你刚开始使用CodeQL时,可能会遇到查询没有返回预期结果的情况。调试这些查询可能很棘手,因为CodeQL是一种类似Prolog的语言,其评估模型与Python等主流语言有很大不同。这意味着你无法"单步执行"代码,附加gdb或添加print语句等技术也不适用。幸运的是,CodeQL提供了各种内置功能来帮助你诊断和解决查询中的问题。
下面,我们将深入探讨这些功能——从抽象语法树(AST)到部分路径图——使用CodeQL用户的问题作为示例。如果你有自己的问题,可以访问GitHub Security Lab的公共Slack实例并提问,CodeQL工程师会进行监控。
最小代码示例
我们将使用用户NgocKhanhC31提出的问题,后来zhou noel也提出了类似的问题。两人都在编写CodeQL查询以检测使用Gradio框架的项目中的漏洞时遇到了困难。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
import pickle
import gradio as gr
def load_config_from_file(config_file):
"""Load settings from a UUID.pkl file."""
try:
with open(config_file.name, 'rb') as f:
settings = pickle.load(f)
return settings
except Exception as e:
return f"Error loading configuration: {str(e)}"
with gr.Blocks(title="Configuration Loader") as demo:
config_file_input = gr.File(label="Load Config File")
load_config_button = gr.Button("Load Existing Config From File", variant="primary")
config_status = gr.Textbox(label="Status")
load_config_button.click(
fn=load_config_from_file,
inputs=[config_file_input],
outputs=[config_status]
)
demo.launch()
|
这里的漏洞更像是一个"二阶"漏洞。首先,攻击者上传恶意文件,然后应用程序使用pickle加载它。
用户编写了一个CodeQL污点跟踪查询,乍一看应该能找到这个漏洞:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
/**
* @name Gradio unsafe deserialization
* @description This query tracks data flow from inputs passed to a Gradio's Button component to any sink.
* @kind path-problem
* @problem.severity warning
* @id 5/1
*/
import python
import semmle.python.ApiGraphs
import semmle.python.Concepts
import semmle.python.dataflow.new.RemoteFlowSources
import semmle.python.dataflow.new.TaintTracking
import MyFlow::PathGraph
class GradioButton extends RemoteFlowSource::Range {
GradioButton() {
exists(API::CallNode n |
n = API::moduleImport("gradio").getMember("Button").getReturn()
.getMember("click").getACall() |
this = n.getParameter(0, "fn").getParameter(_).asSource())
}
override string getSourceType() { result = "Gradio untrusted input" }
}
private module MyConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof GradioButton }
predicate isSink(DataFlow::Node sink) { exists(Decoding d | sink = d) }
}
module MyFlow = TaintTracking::Global<MyConfig>;
from MyFlow::PathNode source, MyFlow::PathNode sink
where MyFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Data Flow from a Gradio source to decoding"
|
如果在数据库上运行此查询,你不会得到任何结果。
创建CodeQL数据库
使用我们的最小代码示例,我们将创建一个CodeQL数据库:
1
|
codeql database create codeql-zth5 --language=python
|
此命令将创建一个新目录codeql-zth5,其中包含CodeQL数据库。
简化查询和快速评估
查询已经简化为谓词和类,因此我们可以使用"快速评估"按钮快速评估它。
抽象语法树(AST)查看器
如果你在识别源节点或汇节点时遇到问题,检查代码的抽象语法树(AST)以确定特定代码元素的类型会很有帮助。
运行快速评估后,你将看到CodeQL识别汇节点的文件。要查看文件的抽象语法树,右键单击你感兴趣的代码元素并选择"CodeQL: View AST"。
getAQlClass谓词
找出你感兴趣的代码元素类型的另一个好策略是使用getAQlClass谓词。
部分路径图:正向
现在我们已经确定在连接源和汇时存在问题,我们应该验证污点流停止的位置。我们可以使用部分路径图来实现这一点,它显示源流向的所有汇以及这些流停止的位置。
污点步骤
最快的方法(虽然不太优雅)是编写一个污点步骤,从任何对象传播到该对象的name属性。
1
2
3
4
5
6
|
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
exists(DataFlow::AttrRead attr |
attr.accesses(nodeFrom, "name")
and nodeTo = attr
)
}
|
再次使用污点步骤?
特定的代码片段有点特殊。我之前提到这个漏洞本质上是一个"二阶"漏洞——我们首先上传恶意文件,然后加载本地存储的文件。通常在这些情况下,我们认为文件的路径是被污染的,而不是文件本身的内容,因此CodeQL通常不会在这里传播。
这就是为什么我们需要另一个污点步骤来从config_file.name传播到open(config_file.name, ‘rb’)。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
predicate osOpenStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
// Connects the argument to `open()` to the result of `open()`
// And argument to `os.open()` to the result of `os.open()`
exists(API::CallNode call |
call = API::moduleImport("os").getMember("open").getACall() and
nodeFrom = call.getArg(0) and
nodeTo = call)
or
exists(API::CallNode call |
call = API::builtin("open").getACall() and
nodeFrom = call.getArg(0) and
nodeTo = call)
}
|
更优雅的污点步骤
本节中编写的CodeQL非常特定于Gradio,你在其他框架中不太可能遇到类似的建模。以下部分是先前污点步骤的更高级版本,适用于那些想要更深入研究编写更可维护解决方案的人。
结论
我们在GHSL Slack上遇到的一些关于跟踪污点的问题可能具有挑战性。像这样的情况不常发生,但当它们发生时,它们成为分享经验教训和撰写博客文章的好候选者。
我希望我追踪污点的故事能帮助你调试查询。如果在尝试了本博客中的提示后,你的查询仍然存在问题,请随时在我们的公共GitHub Security Lab Slack实例或github/codeql讨论中寻求帮助。