ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

CodeQL分析python代码2-分析python代码的CodeQL库

2022-01-27 20:31:11  阅读:216  来源: 互联网

标签:语句 CodeQL 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代码的CodeQL库

当我们需要分析python程序时,可以利用python的CodeQL库中大量的类。

关于分析python代码的CodeQL库

CodeQL平台专门提供了一个功能丰富的库,来帮助我们分析从Python项目中提取的CodeQL数据库。这个库中的类能够以面向对象的形式呈现数据库中的数据,并提供了许多抽象类和谓词来帮助我们完成常见的分析任务。这个库是通过一组QL模块(即扩展名为.qll的文件)的形式来实现的。其中,模块Python.qll的作用是导入这个库的所有核心模块,因此,我们可以在查询代码的开头部分通过下面的语句来导入完整的库

import python

分析python代码的CodeQL库包含大量类,每个类要么对应python源代码中的一种实体,要么对应于可以使用静态分析从源代码派生的实体。这些类可以分为四类:

  • 语法型 表示python源代码中实体的类
  • 控制流型 表示控制流图中实体的类
  • 数据流型 表示数据流图中实体的类
  • API图型 表示API图中实体的类

下面我们对这些类型分别介绍

用于分析语法的类

这些都是用于描述python源代码的,其中Module,ClassFunction类分别对应于python语言中的模块,类和函数。这些都被统称为Scope类,也就是作用域类。同时,每个作用域类实际上就是一个语句列表,表中的每个语句可以由STMT类的子类来进行表示。除此之外,还有一些其他的类,专门用于表示非常复杂的表达式(例如列表推导式,又称为列表解析式)的各个组成部分。总的来说,这些类都是AstNode的子类,并对应于相应的抽象语法树(AST)。同时,每棵AST树的根节点都是一个模块。

另外,符号信息通常以变量(由Variable表示)的形式附加到AST树上面

作用域 Scope

python程序通常都是由一组模块构成的,从技术上讲,模块只是一个语句列表,但我们通常认为它是由类和函数组成的。这些源代码中的顶级实体,即模块,类和函数,对应于三个CodeQL类,即Module,Class和Function类,它们都是Scope类的子类,其层次关系如下所示:

  • Scope
    • Module
    • Class
    • Function

(这一部分比较难以理解,打一个不太好的比方,大家可以思考如果自己是一个初代的python解释器,对于一份python代码会怎么去解析它。我们可以先读入代码,然后删除掉里面的单行注释和多行注释,因为这些对程序的结果是不影响的,然后因为python会import很多的包文件,所以我们需要把导入的包也进行读入。现在程序里面还剩下什么,python语句?变量声明?函数?还是类?事实上就是一段又一段的语句,而语句又构成了类和函数,但也会有位于类和函数之外的语句,所以语句,类,函数就可以囊括python程序在预处理之后的所有内容了。如果你要问for或者try这些在哪里?它们自然是属于上面三类的子集了)

本质上来说,无论ModuleClass还是Function都是一个语句列表,尽管Scope类具有额外的属性,例如名称等。

例如,以下查询查找函数作用域(声明它们的作用域)仍然是函数的函数。(也就是寻找函数中的函数)

import python

from Function f
where f.getScope() instanceof Function
select f

点击查询结果可见

语句

python源代码中的语句由Stmt类表示,它大约由20个子类,表示各种语句,例如Pass,Return,For语句。语句通常由多个部分组成,其中最常见的组成部分就是表达式。CodeQL中专门提供了一个Expr类来表示表达式。例如对以下的for循环代码

for var in seq:
    pass
else:
    return 0

CodeQL中如果我们想要查询项目中的for语句,需要用到For类,该类提供了很多成员谓词,用于访问for的各个组成部分,例如:

  • getTarget() 返回变量varExpr表达式
  • getIter() 返回表示变量seqExpr表达式
  • getBody() 返回for语句列表主体
  • getStmt(0) 返回第一条语句,编号从0开始。在上面的代码中,返回的就是pass语句
  • getOrElse() 返回包含return语句的StmtList语句列表

直接这么说比较抽象,这里我们对一个flask项目进行测试,使用getTarget()

import python
from For tempFor
select tempFor.getTarget()


点击第一个结果,即x

可以看到就是for循环语句for x in range(len(s)-1, 1, -1):中的临时变量名

表达式

大多数语句都是由表达式组成的,Expr类是所有表达式类的父类,大概有30个类涉及调用,推导,元组,列表和算术运算。例如,我们可以使用BinaryExpr类来表示python表达式a+2

  • 成员谓词getLeft()返回表示a的表达式Expr,这里的成员谓词其实可以见名知意
  • 成员谓词getRight()返回表示2的表达式Expr

如果我们想要在项目中查找例如a+2这种左侧是简单名称而右侧是数字常量形式的表达式,我们可以使用以下查询

import python

from BinaryExpr bin
where bin.getLeft() instanceof Name and bin.getRight() instanceof Num
select bin

在我本地项目中的查询结果如下

这种类型的可以用于污点追踪

变量

python源代码中的变量可以使用CodeQL库中的Variable类来表示,该类具有两个子类(从名字就可以看出实际上是变量作用域的不同):

  • LocalVariable用于表示函数和类级别的变量
  • GlobalVariable用于表示模块级别的变量

源代码中的其他元素

虽然程序的语义可以通过诸如ScopeStmtExpr等语法元素进行表示,但是源代码中的某些部分仍然无法通过抽象语法树来进行覆盖。例如,源代码中的注释,这里我们是使用Comment类进行表示

分析语法的一些栗子

在前面的学习中我们了解到:CodeQL平台在处理python项目的时候,会将源代码中的每个语法元素都记录在CodeQL数据库中,我们通过相应的类来查询项目中的这些语法元素。

  • 查找所有的finally语句
    我们使用Try类
import python

from Try t
select t.getFinalbody()
  • 寻找无所事事的except语句
    一个无所事事的ezcept语句,即这种
try:
    //省略
except:
    pass
//省略

也就是说除了pass语句之外不包含任何其他的语句,我们编写QL查询为

import python
 
from ExceptStmt ex
where not exists(Stmt s | s = ex.getAStmt() | not s instanceof Pass)
select ex

可能这里有一点复杂,因为用到了双重否定

exExceptStmt类的一个实例,ExceptStmt类用来表示except语句,s = ex.getAStmt()获取项目中的except语句中的内容,s的类型不能是Pass

exists(Stmt s | s = ex.getAStmt() | not s instanceof Pass)的意思就是except块中的所有语句都不是Pass类型。

最后在条件外部加上not取反,整句话的意思变成了:except块中所有语句都是Pass类型

我们也可以使用逻辑量词forall来进行表示
forall(Stmt s | s = ex.getAStmt() | s instanceof Pass)

这时候的查询语句变成了

import python

from ExceptStmt ex
where forall(Stmt s | s = ex.getAStmt() | s instanceof Pass)
select ex

查询之后的结果如图:

点击一个进去看

分析语法部分总结

我们介绍了使用CodeQL表示语法时最常用的标准类:ModuleClassFunctionStmt以及 Expr类,它们都是AstNode的子类

抽象语法树 Abstract syntax tree

  • AstNode
    • Module -- python模块
    • Class -- 类
    • Function -- 函数
    • Stmt -- 语句
      • Assert -- assert语句
      • Assign
        • AssignStmt -- 赋值语句 x=y
        • ClassDef -- 类定义语句
        • FunctionDef -- 函数定义语句
      • AugAssign -- 自增赋值语句 x+=y
      • Break -- break语句
      • Continue -- continue语句
      • Delete -- del语句
      • ExceptStmt -- try语句中的except部分
      • Exec -- exec语句
      • For -- for语句
      • If -- if语句
      • Pass -- pass语句
      • Print -- print语句
      • Raise -- raise语句
      • Return -- return语句
      • Try -- try语句
      • While -- while语句
      • With -- with语句
    • Expr -- 表达式
      • Attribute -- 类属性 obj.attr
      • Call -- 函数调用 f(arg)
      • IfExp -- 条件表达式 x if cond else y
      • Lambada -- lambda表达式
      • Yield -- yield表达式
      • Bytes -- 字节文字,b"x"或(在python2中)的"x"
      • Unicode -- Unicode文字,u"x"或(在python3中)的"x"
      • Num -- 数字文字 3或者4.2这种
        • IntegerLiteral 整型
        • FloatLiteral 浮点型
        • ImaginaryLiteral (这是啥类型)
      • Dict -- 字典,{'a':2}
      • Set -- 集合,{'a','b'}
      • List -- 列表,['a','b']
      • Tuple -- 元组,('a','b')
      • DictComp -- 字典推导式,{k:v for ...}
      • SetComp -- 集合推导式,{x for ...}
      • ListComp -- 列表推导式,[x for ...]
      • GenExpr -- 生成器表达式,(x for ...)
      • Subscript -- 下标操作,seq[index]
      • Name -- 对变量的引用,var
      • UnaryExpr -- 一元运算,-x
      • BinaryExpr -- 二元运算,x+y
      • Compare -- 比较操作,0<x<10
      • BoolExpr -- 短路逻辑运算,x and yx or y

变量 Variables

  • Variable -- 变量
    • LocalVariable -- 函数或者类的局部变量
    • GlobalVariable -- 全局(模块级)变量

其他

  • Comment -- 注释

控制流类

CodeQL库中的这一部分表示Scope类(即类,函数和模块)的控制流图。每个Scope类都包含一个ControlFlowNode元素构成的图。每个Scope都有一个入口点和至少一个(可能很多)的出口点。为了提高分析控制流和数据流的速度,控制流节点通常会被分组为基本构造块。

示例

如果我们想要找到最长的没有任何分支的代码序列,我们需要考虑控制流。根据定义,一个BasicBlock就是一个没有任何分支的代码序列,所有我们只需要找到最长的BasicBlock即可。

首先,我们需要引入一个谓词bb_length(),它与BasicBlock的长度相关

int bb_length(BasicBlock b) {
    result = max(int i | exists(b.getNode(i))) + 1
}

由于BasicBlock中的各个ControlFlowNode都是从0开始连续编号的,因此,BasicBlock的长度等于该基本块中最大索引+1

那么我们应该如何利用bb_length()找出最长的BasicBlock呢?显然,满足我们要求的BasicBlock基本构造快具有这样的特点:其长度与所有基本构造块中长度最长的那个相等。翻译成QL代码如下:

import python

int bb_length(BasicBlock b) {
    result = max(int i | exists(b.getNode(i)) | i) + 1
}

from BasicBlock b
where bb_length(b) = max(bb_length(_))
select b

可以注意到,这里用到了max(bb_length(_)),其中_是特殊的下划线变量,表示任意值,在这里也就是表示所有的基本构造块。

控制流类总结

CodeQL库中控制流部分的类为:

  • ControlFlowNode -- 控制流节点。AST抽象语法树节点和控制流节点之间存在一对多的关系
  • BasicBlock -- 表示一组没有分支的控制流节点

END

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

标签:语句,CodeQL,python,代码,控制流,--,表达式
来源: https://www.cnblogs.com/Cl0ud/p/15851164.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有