代码分析平台CodeQL学习手记(十六) - 嘶吼 RoarTalk – 回归最本质的信息安全,互联网安全新媒体,4hou.com

代码分析平台CodeQL学习手记(十六)

fanyeee 技术 2020-02-18 09:50:00
收藏

导语:在本文中,我们将为读者讲解用于从句法层次分析JavaScript源代码的类和谓词。

代码分析平台CodeQL入门(一)

代码分析平台CodeQL学习手记(二)

代码分析平台CodeQL学习手记(三)

代码分析平台CodeQL学习手记(四)

代码分析平台CodeQL学习手记(五)

代码分析平台CodeQL学习手记(六)

代码分析平台CodeQL学习手记(七)

代码分析平台CodeQL学习手记(八)

代码分析平台CodeQL学习手记(九)

代码分析平台CodeQL学习手记(十)

代码分析平台CodeQL学习手记(十一)

代码分析平台CodeQL学习手记(十二)

代码分析平台CodeQL学习手记(十三)

代码分析平台CodeQL学习手记(十四)

代码分析平台CodeQL学习手记(十五)

在前面的文章中,我们首先为读者展示了如何通过LGTM查询控制台来编写和运行分析JavaScript和TypeScript代码的查询。然后,讲解了CodeQL的JavaScript标准库中用于从文本级别和词法级别分析源代码的常用类及其谓词。在本文中,我们继续讲解CodeQL的JavaScript标准库中的其他类和谓词。

用于分析JavaScript代码的CodeQL标准库

为了帮助人们分析JavaScript代码,CodeQL平台专门提供了一个功能丰富的标准库,来帮助我们分析从JavaScript项目中提取的CodeQL数据库。这个库中的类,不仅能够以面向对象的形式表示数据库中的数据,同时,该库还提供了许多抽象类和谓词,来帮助我们完成各种常见的任务。

前面的文章介绍了用于从文本级别和词法级别分析JavaScript源代码的常用类及其谓词。接下来,我们开始讲解用于从句法层次分析JavaScript源代码的类和谓词。

语法级别

Javascript 库中的大多数类都是用于将 JavaScript 程序表示为抽象语法树(abstract syntax trees,AST)的。其中,类ASTNode不仅提供了表示抽象语法树节点的所有实体,还提供了用于遍历树节点的通用谓词:

· 谓词ASTNode.getChild(i):返回本AST节点的第i个子节点。

· 谓词ASTNode.getAChild():返回本AST节点的所有子节点。

· 谓词ASTNode.getParent():返回本AST节点的父节点(如果有的话)。

需要注意的是,这些谓词只适用于执行通用的AST遍历。若要访问特定AST节点类型的子节点,应该改用在下文中介绍的专用谓词。特别是,查询不应依赖于子节点相对于其父节点的数字索引:因为在这个库的不同版本之间,这些实现细节会随之变化。

顶层代码块

从语法的角度来看,每个JavaScript程序都由一个或多个顶层代码块(或简称顶层)组成。通常情况下,一个代码块属于另一个更大的代码块,而顶层代码块则其他JavaScript代码块。 顶层代码块可以通过类TopLevel及其子类进行表示,这些类的层级结构如下所示:

· 类Toplevel

· 类 Script:一个独立的文件或 HTML < script >元素

- 类ExternalScript:一个独立的 JavaScript 文件

- 类InlineScript:嵌入在 HTML < script >标记中的代码

· 类CodeInAttribute:源自 HTML 属性值的代码块

- 类EventHandlerCode:源自事件处理程序属性(如 onload)的代码

- 类JavaScriptURL:从带有javascript:的URL 中提取的代码

· 类Externs:包含 Externs(详情请参考https://developers.google.com/closure/compiler/docs/api-tutorial3#externs) 定义的 JavaScript 文件

TopLevel类还提供以下成员谓词:

· 谓词TopLevel.getNumberOfLines(),返回顶层代码块中代码的总行数(包括代码行、注释行和空白行)。

· 谓词TopLevel.getNumberOfLinesOfCode() ,返回代码行数,即至少包含一个单词的行数。

· 谓词TopLevel.getNumberOfLinesOfComments(),返回包含或属于注释的行数。

· 谓词TopLevel.isMinified(),根据每行的平均语句数,使用启发式方法确定顶层代码块中是否包含压缩型代码(minified code)。这里所谓的压缩,是指从源代码中删除不必要的字符,使它看起来简单而整洁。

需要注意的是,在默认的情况下,LGTM会过滤掉压缩型顶层代码块中的警报,因为它们通常难以解释。当我们在LGTM查询控制台编写自定义的查询时,这种过滤不是自动执行的,因此,如果希望执行该操作的话,可以显式一个类似and not e.getTopLevel().isMinified()的查询条件,以过滤来自压缩型代码的返回结果。

语句与表达式

除了子类TopLevel之外,类ASTNode最重要的子类就是Stmt和Expr了,通过与其他子类结合使用,这两个子类就能用于表示语句和表达式。接下来,我们为读者介绍用于表示语句与表达式的类和谓词。有关Stmt和Expr的所有子类及其API的完整资料,请参见Stmt.qll(地址为https://help.semmle.com/qldoc/javascript/semmle/javascript/Stmt.qll/module.Stmt.html)和 Expr.qll(地址为https://help.semmle.com/qldoc/javascript/semmle/javascript/Expr.qll/module.Expr.html)。

· 类Stmt:可以使用谓词ControlStmt.getAControlledStmt()来访问包含该语句的最内层函数或顶层代码块。

· 类ControlStmt:用于控制其他语句(即条件语句、循环语句、try语句或 with 语句)执行的语句;可以使用谓词ControlStmt.getAControlledStmt()来访问它控制的语句。

- 类IfStmt:用于表示一个if语句;可以使用谓词IfStmt.getCondition()、IfStmt.getThen()和IfStmt.getElse()来访问其条件表达式的“then”分支和“else”分支。

- 类LoopStmt:表示循环语句;可以使用谓词Loop.getBody()和Loop.getTest()来访问该语句的循环主体和测试表达式。

· 类WhileStmt、 DoWhileStmt:分别表示“while”循环语句和“do-while”循环语句。

· 类ForStmt:表示“for”语句;可以通过谓词ForStmt.getInit()和ForStmt.getUpdate()来访问初始条件和更新条件。

· 类EnhancedForLoop:表示“for-in”或“for-of”语句;可以通过谓词EnhancedForLoop.getIterator()来访问循环迭代器(可能是表达式或变量声明) ,并通过谓词EnhancedForLoop.getIterationDomain()来访问正在迭代的表达式。

· 类ForInStmt、 ForOfStmt:分别用于表示“for-in”和“for-of”循环语句。

· 类WithStmt:表示“with”语句;可以通过谓词WithStmt.getExpr()和WithStmt.getBody()分别访问控制表达式和 with语句的主体部分。

· 类SwitchStmt:表示“switch”语句;可以使用成员谓词SwitchStmt.getExpr()来访问“switch”语句的表达式部分;可以使用成员谓词SwitchStmt.getCase(int)和SwitchStmt.getACase()来访问单个case分支;每个case分支都可以通过类 Case的实例来表示,其成员谓词包括 Case.getExpr()和Case.getBodyStmt(int),前者可用于读取case分支中的表达式,后者用于读取case分支的主体部分。

· 类TryStmt:表示“try”语句;可以通过成员谓词TryStmt.getBody()、TryStmt.getCatchClause() 和TryStmt.getFinally来访问该语句的主体部分、“catch”子句和“finally”语句块。

· 类BlockStmt:表示一个语句块;可以使用成员谓词BlockStmt.getStmt(int)来访问该语句块中的单个语句。

· 类ExprStmt:表示一个表达式语句;可以使用 成员谓词ExprStmt.getExpr()来访问表达式本身。

· 类JumpStmt:表示会打乱结构化控制流的语句,即 break语句、continue语句、return语句和throw语句;可以使用谓词JumpStmt.getTarget() 来确定跳转目标,该目标可以是一个语句或(用于return和未被捕获的throw语句)外层函数。

· 类BreakStmt:表示“break”语句;可以使用成员谓词BreakStmt.getLabel()来访问其(可选)目标标签。

· 类ContinueStmt:表示“continue”语句;可以使用成员谓词ReturnStmt.getExpr()来访问其(可选)目标标签。

· 类ThrowStmt:表示“return”语句;可以使用成员谓词ReturnStmt.getExpr() 来访问其(可选)结果表达式。

· 类ReturnStmt:表示“throw”语句;可以使用ThrowStmt.getExpr()来访问其表达式。

· 类FunctionDeclStmt:表示函数声明语句;其成员谓词,请参见下文。

· 类ClassDeclStmt:表示类声明语句;其成员谓词,请参见下文。

· 类DeclStmt:表示包含一个或多个声明符的声明语句,这些声明符可通过成员谓词 DeclStmt.getDeclarator(int)进行访问。

· 类VarDeclStmt、ConstDeclStmt、LetStmt:分别表示声明var、const 和let 的语句。

· 类Expr:使用成员谓词Expr.getEnclosingStmt()可以获取该表达式所属的最内层语句;通过成员谓词可以确定该表达式是否没有副作用。

· 类Identifier:用于表示标识符;可以使用成员谓词Identifier.getName() 来获取其名称。

· 类Literal:表示字面值;可以使用成员谓词Literal.getValue()来获取其值的字符串表示形式,通过成员谓词Literal.getRawValue()则可以获取其原始源代码文本(包括字符串文本前后的引号)。

· 类NullLiteral、BooleanLiteral、NumberLiteral、StringLiteral、RegExpLiteral:表示不同类型的字面值。

· 类ThisExpr:表示“this”表达式。

· 类SuperExpr:表示“super”表达式。

· 类ArrayExpr:表示数组表达式;可以使用成员谓词ArrayExpr.getElement(i)来获得第i个元素表达式,使用成员谓词ArrayExpr.elementIsOmitted(i)则可以检查第i个元素是否被省略。

· 类ObjectExpr:表示对象表达式;使用成员谓词ObjectExpr.getProperty(i)可以获取对象表达式中的第i个属性;属性可以通过类Property表示,下文中将对其进行更详细的描述。

· 类FunctionExpr:表示一个函数表达式;其成员谓词将在下文中加以解释。

· 类ArrowFunctionExpr:表示ECMAScript2015风格的箭头函数表达式,其成员谓词将在下文中加以介绍。

· 类ClassExpr:表示类表达式,其成员谓词将在下文中加以介绍。  

· 类ParExpr:表示一个带括号的表达式;使用成员谓词ParExpr.getExpression()可以获取操作数表达式;对于任何表达式而言,都可以通过成员谓词Expr.stripParens()递归地去掉所有的括号。

· 类SeqExpr:表示由逗号运算符连接的两个或多个表达式所组成的序列;我们可以使用SeqExpr.getOperand(i)获得第i个子表达式。

· 类ConditionalExpr:表示三值条件表达式;成员谓词ConditionalExpr.getCondition()、ConditionalExpr.getConsequent()和ConditionalExpr.getAlternate()分别用于访问条件表达式、“then”表达式和“else”表达式。

· 类InvokeExpr:表示函数调用或“new”表达式;成员谓词InvokeExpr.getCallee()用来访问用于指定要调用的函数的表达式,成员谓词InvokeExpr.getArgument(i)用于访问第i个参数表达式。

· 类CallExpr:表示函数调用表达式。

· 类NewExpr:表示“new”表达式。

· 类MethodCallExpr:表示一个函数调用表达式,其被调用方表达式为属性访问表达式;可以使用MethodCallExpr.getReceiver来访问方法调用接收方的表达式,并使用MethodCallExpr.getMethodName()来获取方法名称(如果可以静态确定的话)。

· 类PropAccess:一种属性访问表达式,即形式为e.f的“点号”表达式或形式为e[p]的索引表达式;使用PropAccess.getBase()可以获取表示要访问谁的属性的基本表达式(在本例中为e),并使用PropAccess.getPropertyName()确定访问的属性的名称;如果不能静态确定名称的话,那么调用getPropertyName()时不会返回任何值。

· 类DotExpr:表示“点号”表达式。

· 类IndexExpr:表示索引表达式(也称为计算型属性访问表达式)。

· 类UnaryExpr:表示一元表达式;可以使用UnaryExpr.getOperand()来获得操作数表达式。

· 类NegExpr(“-”)、PlusExpr(“+”)、LogNotExpr(“!”)、BitNotExpr(“?”)、TypeofExpr、VoidExpr、DeleteExpr、SpreadElement(“…”):表示各种类型的一元表达式。

· 类BinaryExpr:表示二元表达式;可以使用BinaryExpr.getLeftOperand()和BinaryExpr.getRightOperand()访问操作数表达式。

· 类Comparison:表示各种类型的比较表达式。

· 类EqualityTest:表示各种相等性或不相等性测试表达式。

· 类EqExpr(“==”)、NEqExpr(“!=”):非严格型相等性和不相等性测试表达式。

· 类StrictEqExpr(“===”)、StrictNEqExpr(“!==”):严格的相等性和不相等性测试表达式。

· 类LTExpr(“< ”)、LEExpr(“< =”)、GTExpr(“ >”)、GEExpr(“ >=”):数字比较表达式。

· 类LShiftExpr(“<< ”)、RShiftExpr(“ >>”)、URShiftExpr(“ >>>”):移位运算符表达式。

· 类AddExpr(“+”)、SubExpr(“-”)、MulExpr(“*”)、DivExpr(“/”)、ModExpr(“%”)、ExpExpr(“**”):算术运算符表达式。

· 类BitOrExpr(“|”)、XOrExpr(“^”)、BitAndExpr(“&”):按位运算符表达式。

· 类InExpr:基于in的类型测试表达式。

· 类InstanceofExpr:基于instanceof的类型测试表达式。

· 类LogAndExpr(“&&”),LogOrExpr(“||”):短路型逻辑运算表达式。

· 类Assignment:表示赋值表达式,可以是简单的赋值表达式,也可以是复合的赋值表达式;可以使用Assignment.getLhs()和Assignment.getRhs()分别访问表达式的左侧和右侧部分。

· 类AssignExpr:表示简单的赋值表达式。

· 类CompoundAssignExpr:表示复合赋值表达式。

· 类AssignAddExpr、AssignSubExpr、AssignMulExpr、AssignDivExpr、AssignModExpr、AssignLShiftExpr、AssignRShiftExpr、AssignURShiftExpr、AssignOrExpr、AssignXOrExpr、AssignAndExpr、AssignExpExpr:表示各种类型的复合赋值表达式。

· 类UpdateExpr:表示递增或递减表达式;使用UpdateExpr.getOperand()可以获得操作数表达式。

· 类PreIncExpr,PostIncExpr:表示递增表达式。

· 类PreDecExpr,PostDecExpr:表示递减表达式。

· 类YieldExpr:表示“yield”表达式;通过YieldExpr.getOperand()可以访问(可选的)操作数表达式;通过YieldExpr.isDelegating()可以检查是否为委派型yield *表达式。

· 类TemplateLiteral:表示ECMAScript 2015模板文字;可以通过TemplateLiteral.getElement(i)返回模板的第i个元素,该元素可以是插值表达式或常量模板元素。

· 类TaggedTemplateExpr:ECMAScript 2015带标签的模板文字;使用TaggedTemplateExpr.getTag()可以访问标记表达式,而使用TaggedTemplateExpr.getTemplate()则可以访问被标记的模板文字。

· 类TemplateElement:表示常量模板元素;对于文字,可以使用TemplateElement.getValue()获取元素的值;此外,也可以使用TemplateElement.getRawValue()访问其原始值

· 类AwaitExpr:表示“await”表达式;可以使用AwaitExpr.getOperand()访问操作数表达式。

· 类Stmt和Expr共享一个公共超类ExprOrStmt,对于涉及语句或表达式,但是不牵扯任何其他AST节点的查询来说,它是非常有用的。

下面的查询是用于演示如何使用表达式AST节点的,具体来说就是查找形为e+f>>g的表达式;实际上,为了阐明运算符的优先级,这些表达式应改写为(e+f)>>g:

import javascript
 
from ShiftExpr shift, AddExpr add
where add = shift.getAnOperand()
select add, "This expression should be bracketed to clarify precedence rules."

下图展示了该查询的运行结果:


1.png

函数

JavaScript提供了多种定义函数的方式:在ECMAScript 5中,提供了多种函数声明语句和函数表达式声明语句;在ECMAScript 2015中,又增加了箭头函数表达式。对于这些语法形式来说,我们可以分别通过类FunctionDeclStmt(类Stmt的一个子类)、FunctionExpr(类Expr的一个子类)和ArrowFunctionExpr(也是类Expr的一个子类)来进行表示。实际上,这三个类都是类Function的子类,因此,都提供了访问函数参数或函数体所需的成员谓词:

成员谓词Function.getId()可以返回命名函数的标识符。

成员谓词Function.getParameter(i)和Function.getAParameter()分别用于访问第i个参数或任意参数;而参数通常是由类Parameter来表示的,它是类BindingPattern的子类(详情见下文)。

成员谓词Function.getBody()用于返回函数体,它通常是一个Stmt,但也可能是表示箭头函数表达式和遗留表达式闭包的Expr。

下面是一个查找所有表达式闭包的查询示例:

import javascript
 
from FunctionExpr fe
where fe.getBody() instanceof Expr
select fe, "Use arrow expressions instead of expression closures."

下面是上述代码的返回结果:

2.png

如您所见,在这里的演示项目中并没有找到符合要求的表达式闭包。

接下来,我们看看另一个查询示例,其作用是查找具有两个绑定了同一个变量的参数的函数:

import javascript
 
from Function fun, Parameter p, Parameter q, int i, int j
where p = fun.getParameter(i) and
    q = fun.getParameter(j) and
    i < j and
    p.getAVariable() = q.getAVariable()
select fun, "This function has two parameters that bind the same variable."

上述代码的运行结果如下所示:3.png

如您所见,在演示项目中并没有找到符合要求的函数。

类既可以通过CodeQL类ClassDeclStmt(它是类Stmt的子类)表示的类声明语句进行定义,也可以通过CodeQL类ClassExpr(它是类Expr的子类)表示的类表达式进行定义。实际上,这两个类都是ClassDefinition的子类,并继承了用于访问类的名称、其超类和类主体的成员谓词:

· 成员谓词ClassDefinition.getIdentifier()用于返回命名函数的标识符。

· 成员谓词ClassDefinition.getSuperClass()用于返回指定超类的Expr,该超类可能尚未定义。

· 成员谓词ClassDefinition.getMember(n)用于返回这个类的第n个成员的定义。

· 成员谓词ClassDefinition.getMethod(n)用于将ClassDefinition.getMember(n)限定为返回成员方法(而不是字段)。

· 成员谓词ClassDefinition.getField(n)用于将ClassDefinition.getMember(n)限定为返回字段(而不是方法)。

· 成员谓词ClassDefinition.getConstructor() 用于获取类的构造函数。

注意,类的字段还不是一个标准的语言特性,因此,它们表示的细节可能会发生变化。

方法的定义通常是由类MethodDefinition来表示的,它(与其对应的FieldDefinition类似)是MemberDefinition的子类。该类提供了以下重要成员谓词:

· MemberDefinition.isStatic():对于静态成员,则该判断成立。

· MemberDefinition.isComputed():如果成员的名称是在运行时确定的,则该判断成立。

· MemberDefinition.getName():获取成员的名称(如果可以静态确定的话)。

· MemberDefinition.getInit():获取字段的初始化代码:对于方法来说,初始化代码是一个函数表达式;对于字段来说,其初始化代码可能是一个任意表达式,也可能是未定义的。

此外,这里还有三个用于表示特殊方法的类:类ConstructorDefinition表示构造函数,而类GetterMethodDefinition和SetterMethodDefinition分别表示getter和setter方法。

小结

前面的文章介绍了用于从文本级别和词法级别分析JavaScript源代码的常用类及其谓词,在本文中,我们为读者讲解了用于从句法层次分析JavaScript源代码的类和谓词。

备注:本系列文章乃本人在学习CodeQL平台过程中所做的笔记,希望能够对大家有点滴帮助——若果真如此的话,本人将备感荣幸。

参考资料:https://help.semmle.com/

如若转载,请注明原文地址
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论