代码分析平台CodeQL学习手记(十六) - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

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

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

导语:在本文中,我们将为读者讲解用于从句法层次分析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/

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

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

扫码支持

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

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2022 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务