CodeQL 从零到精通第 3 部分:使用 CodeQL 进行安全研究

查询特定的库方法

在上一篇博文中,我们根据名称匹配函数调用、函数和方法调用,例如,在本[挑战](https://github.blog/2023-06-15-codeql-zero-to-hero-part-2-getting-started-with-codeql/#:~:text=Challenge 9—Find all functions with “command” as part of its name)中。不过,可能会发生某个方法调用在多个库中定义的情况,我们希望将结果细化为仅来自一个特定库的方法调用。

例如,在审计新的代码库时,我们可能希望找到对特定库函数或方法的调用,因为我们知道它可能是新的源或接收器(请参阅本系列的第一部分execute())。我们可以使用静态分析和 CodeQL 做到这一点。那么,我们如何编写和查询特定的库方法?让我们以前面的例子为例,假设我们正在审计 Django 应用程序的 SQL 注入,我们对来自且仅来自的调用感兴趣django.db.connection.cursor()

要在 Django 中直接执行自定义 SQL,我们需要通过调用来获取游标对象connection.cursor()django.db.connection然后在其上调用execute()。通常,它看起来像这样:

from django.conf.urls import url
from django.db import connection


def show_user(request, username):
    with connection.cursor() as cursor:
        cursor.execute("SELECT * FROM users WHERE username = %s" % username)

在 Python 版 CodeQL 中,我们使用API图库来引用外部库函数和类。对于动态语言,由于其动态特性,我们无法唯一地确定给定变量的类型,因此 API 图层提供了从导入到潜在用途跟踪类型的机制。

我们可以 使用以下查询execute从库中找到所有方法调用。django.db

/**
 * @id codeql-zero-to-hero/3-1
 * @severity error
 * @kind problem
 */

import python
import semmle.python.ApiGraphs

from API::CallNode node
where node =
    API::moduleImport("django").getMember("db").getMember("connection").getMember("cursor").getReturn().getMember("execute").getACall()
    and
    node.getLocation().getFile().getRelativePath().regexpMatch("2/challenge-1/.*")

select node, "Call to django.db execute"

让我们看一下查询中发生的情况。

首先,我们设置查询元数据。其中最有趣的部分是@kind problem。这意味着每个节点的结果将包含接收器所在的文件名,以及我们在子句中指定的字符串select。总之,它使结果显示更漂亮。

然后,我们定义我们正在寻找API::CallNodes,因此在子句中连接到 API 图(请参阅API模块的文档)的节点from

where子句中,我们过滤nodes来自django带有的库API::moduleImport("django")。然后,我们找到对cursor带有的引用.getMember("db").getMember("connection").getMember("cursor")。这将匹配django.db.connection.cursor。由于我们调用execute对象cursor,我们首先需要使用getReturn()谓词来获取表示创建游标对象的结果的节点 - 这将返回我们django.db.connection.cursor()(请注意末尾的括号)。最后,我们得到代表带有execute方法的节点,getMember("execute")并且getACall()我们得到对节点所代表的方法的实际方法调用execute

乍一看可能很复杂,但其实不然。使用几次后,它就变得非常直观。

挑战 1——查找所有名为“execute”且来自django.db库的方法调用 挑战 2 — 编写查询来查找所有os.system方法调用 挑战 3 — 编写查询以查找所有 Flask 请求

获取与给定结果匹配的所有 QL 类型

CodeQL 中有不同的类型来表示代码库的不同方面。例如,Python 版 CodeQL 中的函数具有 Function 类型或字符串文字,而 Python 版 CodeQL 中的字符串文字具有 Str 类型。如果您不确定某些结果可能具有哪些 QL 类型,则可以使用谓词getAQlClass。例如,我们可以将getAQlClass谓词添加到上一个查询的结果中 — 所有django.db要执行的调用。

/**
 * @id codeql-zero-to-hero/3-4
 * @severity error
 * @kind problem
 */
import python
import semmle.python.ApiGraphs

from API::CallNode node
where node = API::moduleImport("django").getMember("db").getMember("connection").getMember("cursor").getReturn().getMember("execute").getACall()
select node, "The node has type " + node.getAQlClass()

单个节点通常会有许多 QL 类,因此如果您运行此查询,您将看到很多结果,例如MethodCallNodeExecuteMethodCallSqlExecution。如果您在查询所需的内容时遇到问题,getAQlClass谓词可能是一个非常有用的调试工具,但请记住不要在最终查询中使用它,因为使用getAQlClass会影响查询性能。在某些语言中,例如 Java,您可能希望使用 getAPrimaryQlClass 谓词,它返回给定元素所属的主要 CodeQL 类。另请参阅其他调试想法。

getAQlClass挑战 4—运行带谓词的查询

CodeQL 中的污点分析—污点追踪

我在 CodeQL zero to hero 第二部分中简要提到了CodeQL 如何实现污点分析。还有一个挑战展示了如何使用污点分析运行内置的 CodeQL 查询,你一定要尝试一下!

现在我们已经了解了 CodeQL 的基础知识,我们可以编写自己的查询来查找从源到接收器的流。

但首先,让我们先了解一下数据流分析和污点流分析之间的区别。污点流分析允许我们跟踪非值保留步骤。数据流分析则不行。例如,如果受污染的字符串与另一个字符串连接在一起,或者被分配给对象的属性,则污点流分析将允许我们继续跟踪该流,而数据流分析则不允许。有关数据流分析和污点分析的更多信息,请参阅CodeQL zero to hero 第 1 部分。

本地数据流

在深入研究污点分析之前,我们需要介绍两种(子)类型的数据流分析:本地数据流和全局数据流,以及本地污点流和全局污点流。

在 CodeQL 中,本地数据流是指在本地跟踪数据流,例如在单个函数内。与全局数据流相比,它的计算成本更低。本地污点流(在 CodeQL 中称为本地污点跟踪)允许我们跟踪非值保留的流程步骤。

例如,使用本地数据流,我们可以通过查询所有不采用字符串文字的 调用来提高分析的准确性django.dbexecute如果execute调用采用字符串文字,例如:

cursor.execute("SELECT * FROM users WHERE username = 'johndoe'")

或者:

query = "SELECT * FROM users WHERE username = 'johndoe'"
cursor.execute(query)

然后,它不接受任何用户输入,并且我们已经知道它不易受到 SQL 注入

你可能感兴趣的:(安全,sqlite,数据库)