CodeQL JS/TS Journey

关于

之前做过的一些使用CodeQL对JS/TS项目做扫描的笔记。

关于构建数据库过程

对于JS/TS的项目来说,CodeQL统一都是 --language=javascript 的参数处理的,而且它主要是扫描,解析,然后构建数据库,对于小项目直接默认参数应该是ok的:

1
2
codeql database create --language=javascript <your_prj>
# codeql database bundle -o <your_prj_db>.zip <your_prj>

但是对于比较大型的项目来说,因为CodeQL是Java写的,所以可能会存在内存不足导致构建数据库失败的情况:

1
2
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed -
JavaScript heap out of memory

默认给的内存是2400MB,大项目必然不够啊,文件太多了。

找了一圈没有解决方案,索性直接掏出JD_GUI把它的jar包给反编译了,发现是通过环境变量控制的:

1
export SEMMLE_TYPESCRIPT_RAM=8000

这个不是给JAVA的那个内存设置(-J-Xmx1234M)

Query

接口函数

1
2
3
4
5
6
7
8
9
10
11
interface FooInterface{
4//...
}
export interface outerApiConfig {

4foo: (params: xxxxx) => Promise<{ // whatever ..}>;
4
4bar: (params: FooInterface) => Promise<{ // whatever..}>;
4
4// ...
}

拿这个Demo为例,很多接口函数统一导出,需要借助InterfaceDeclaration 来找,不过我的方法有点“笨”。

1
2
3
4
5
6
7
8
9
10
11
12
import javascript

predicate isOuterAPIs(Function f){
exists(InterfaceDeclaration apis |
apis.getIdentifier().toString() = "outerApiConfig" and
apis.getAMember().getName() = f.getName()
)
}

from Function f
where isOuterAPIs(f)
select f.getName()

我这里实现很粗暴,就是限制函数名(字符串值)和Interface里成员名字(字符串值)一致,就认为这个函数是导出接口中的函数。

特定参数的处理

在我的需求中,我需要重点关注,参数中带有路径的函数,换言之就是需要识别出这么多接口函数中,参数带有path的情况,那么很直接的思路就是利用正则,但是在实际的场景下,你会发现代码真的写出了“花”,不是常规的query能覆盖的。

1
2
3
4
5
6
7
8
9
10
foo: (params: WTFParams) => Promise<....>;


bar: (params: { arg: string }) => Promise<{ ...}>;

function magic(x) {
return ()=>{
//...
};
}
  • 参数是一个interface,你需要对这个interface再限制,即这个interface的成员是不是path

  • 参数直接就是 {arg : string} 这类情况

  • 奇怪的函数写法,函数体在return里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class PathParamInterfaceType extends InterfaceType{
PathParamInterfaceType(){
getInterface().getAMember().getName().toLowerCase().indexOf("path") > 0
}
}

predicate isParamPath(Function f){
(
f.getAParameter().getType() instanceof PathParamInterfaceType
or
f.getAParameter().getType().toString().toLowerCase().indexOf("path") > 0
) or
(
f.getNumParameter() = 0
and
f.getAReturnStmt().getExpr().(Function).getAParameter().getType().toString().toLowerCase().indexOf("path") > 0
)
}

必须依赖TaintTracking吗

最后一个问题比较简单了,就是有了source,然后再找合适的sink,看有没有路径就行了;但是其实还有一种办法会来得更直接,就是利用传递闭包,但是会带来比较多的误报,好处是实现起来简单,想要排除误报,只需要增加限制即可,看具体需求吧,哪个方法合适用哪个。

CodeQL的JS/TS部分实现不如cpp多,所以有些predicate需要自己手动实现,比如用cpp做query可以:

1
2
3
4
5
6
FunctionCall getFunctionToACall(FunctionCall fc){
result = fc.getBasicBlock().getEnclosingFunction().getACallToThisFunction()
}

select
getFunctionToACall*(FunctionCall fc)

但是JS/TS部分没有getACallToThisFunction ,根据原理,手动实现一个即可:

1
2
3
4
5
6
7
8
9
10
CallExpr getACallToThisFunction(Function f){
exists( CallExpr c |
c.getCalleeName() = f.getName() and
result = c
)
}

CallExpr getFunctionToACall(CallExpr call){
result = getACallToThisFunction(call.getEnclosingFunction())
}

所以,如果想要查询foo函数的传递闭包,就可以:

1
2
3
from CallExpr call
where call.getCalleeName() = "foo"
select getFunctionToACall*(call)

参考

https://xz.aliyun.com/t/7482

CodeQL for research

https://ctftime.org/writeup/22177

https://kernelshaman.blogspot.com/2021/01/building-xnu-for-macos-big-sur-1101.html

https://github.com/D4rkD0g/boringforever/blob/main/xnu/boringanalysis/codeql_xnu.md

https://codeql.github.com/docs/codeql-cli/