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


    前言

    我们已经学习了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

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

    GIF GIF
  • 相关阅读:
    MT【305】丹德林双球
    MT【304】反射路径长度比
    MT【303】估计
    MT【302】利用值域宽度求范围
    MT【301】值域宽度
    MT【300】余弦的三倍角公式
    MT【299】对数型数列不等式
    MT【298】双参数非齐次
    xadmin 自定义actions
    xadmin 添加自定义权限
  • 原文地址:https://www.cnblogs.com/Cl0ud/p/15851164.html
Copyright © 2020-2023  润新知