在How to Query a CPG 章节中,提到了简单地对代码属性图进行查询的操作,本节对查询操作进行深入的学习。
How to Query a CPG
一旦Ocular为您的应用程序创建了一个代码属性图(CPG),您就可以使用查询来仔细检查CPG(以及您的应用程序)。
有两种类型的查询:默认和自定义。
注意:在研究数据流时,我们建议使用策略来代替查询。本文的最后将包含关于查询和策略之间关系的高级信息。
Queries
查询是使用可视化查询语言(OQL)编写的。您可以仅对活动的CPG或已加载到工作区的内存中的所有CPG运行查询。您还可以组合查询,每个查询都适用于一个或多个cpg。
下面的示例演示了如何使用OQL来编写查询以及如何使用查询。
Getting the Active CPG
活动CPG是您的工作空间中最近加载的CPG。您可以使用CPG.method.fullname.l获得活动的CPG。
如果您的工作空间中有多个cpg,那么您可以使用CPGs.flatmap(_.method.fullName.l)查询它们并连接结果。请注意,此查询将连续针对单个cpg运行;cpg没有被合并。
Combining Queries for Multiple CPGs
要组合来自多个cpg的查询,请使用cpg.flatMap {cpg = > cpg.method.l}
Investigating Security Profiles
安全配置文件包含发现的技术漏洞的摘要。与CPG的其他层不同,您必须手动生成和加载安全配置文件。
您可以使用以下命令查询安全配置文件:
cpg.finding.p.<finding>
or
cpg.finding.toJsonPretty
Query Results
如果您的查询结果是广泛的,您可以使用以下命令来打开一个类似于lesslike (pager)的应用程序并滚动结果:browse(<query>)。请确保将<query>替换为您的实际查询。
Writing Query Results to a File
创建一个新文件并将结果写入文件,使用:<query>.l |> "output.txt"
如果希望将结果附加到现有文件(例如,output.txt),请运行:<query>.l |>> "output.txt"(L小写+竖杠)
关于列表格式的注意事项:
使用.l(而不是.p)生成特定于Scala的列表格式的输出。
如果你用.l将结果写入文件(即,cpg.finding.l |> "test.txt"),该命令附加对象引用。因此,我们建议您附加所有字符串,例如由cpg.method.toJsonPretty这样的查询产生的字符串。
Queries and Policies
策略只是查询的集合。有默认策略和自定义策略。
默认策略:用于确定应用程序如何与外部世界通信、数据上存在哪些转换以及哪些信息流应该被视为安全违规。通过运行默认查询获得的结果被合并到您的安全配置文件中,该配置文件通过汇总漏洞和数据泄漏来自动进行代码分析。
自定义策略:是由您创建的,以构建在由眼部自动生成的结果上。例如,您可能对您的代码有额外的了解,您希望通过目视系统来考虑这些信息——这些信息将通过自定义策略来提供。
Examples for Using the Ocular Query Language (OQL)
本文包含使用旨在帮助您开始第一次查询的可视化查询语言(OQL)的示例。注意:Ocular使用的查询语言是基于Scala的内部特定于域的语言(这意味着您可以在查询中使用所有Scala)。(交互式)
Ocular的交互式外壳,让您探索您的代码和工艺您的查询。它包括语法补全,以帮助您学习可视化查询语言,您可以浏览高亮显示的语法代码,而不需要离开工具本身。您还可以将查询结果和代码导入文件中,以便与第三方工具集成。
1 导入脚本:如果您有想要运行的现有脚本,您可以在启动时导入它们,以供Ocular使用,如下所示
mkdir -p scripts/myocularhax/
echo 'println("Loading my hacks")' > scripts/myocularhax/hacks.sc
mkdir -p ~/.shiftleft/ocular/
echo 'runScript("myocularhax", cpg)' >> ~/.shiftleft/ocular/predef.scala
sl ocular
runScript(myocularhax, cpg) // <--- or kick-off manually after loading CPG
2 复杂性度量:下面的示例演示如何使用cpg方法来获得关于代码复杂程度的信息。
识别具有4个以上参数的函数:cpg.method.where(_.parameter.size > 4).l
识别具有> 4控制结构的函数(又称圈复杂度):cpg.method.where(_.controlStructure.size > 4).l
用超过500行代码标识函数:cpg.method.where(_.numberOfLines >= 500).l
使用多个返回语句标识函数:cpg.method.where(_.ast.isReturn.l.size > 1)
识别具有4个以上循环的函数:cpg.method.where(_.ast.isControlStructure.parserTypeName("(For|Do|While).*").size > 4).l
识别嵌套深度大于3的函数:cpg.method.where(_.depth(_.isControlStructure) > 3).name.l
3 调用库:下面的示例展示了如何使用cpg方法来获得关于在应用程序中使用/调用的库的信息。
获取程序使用的所有外部方法的名称:cpg.method.external.name.l.distinct.sorted
把所有的调用都接到strcpy:cpg.call(”str.*”).code.l
获取所有调用strcpy的方法:cpg.call(“str.*”).method.name.l
查看参数:sprintf的第二个参数不是字面量:cpg.call(“sprintf”).argument(2).filterNot(_.isLiteral).code.l
查找所有只有一个类型i8*的参数的方法,该类型对应于void*和char:cpg.method.where(m => m.parameter.size == 1 && m.parameter.typ.nameExact("i8*").size == 1).name.p
4 频繁调用的函数
查看应用程序最频繁调用的方法:
cpg.method.map(x => (x.start.callIn.size,
x.name)).l.sorted.reverse.take( 100)
res16: List[(Int, String)] = List(
(108003, "<operator>.indirectMemberAccess" ), (87500, "<operator>.assignment" ),
(42012, "<operator>.memberAccess" ),
(22498, "<operator>.addressOf" ),
(20280, "<operator>.computedMemberAccess" ), ...
(5436, "free"),
(3262, "msg_Dbg"),
)
5 通过隐式转换扩展cpg方法
你可以扩展cpg.method如下:
implicit class MyMethod(method : Steps[Method]) {
def top(n : Int) =
method.map(x => (x.start.callIn.size,
x.name)).l.sorted.reverse.take( 100)
}
defined class MyMethod
检查您的更改
cpg.method.top(10)
res16: List[(Int, String)] = List(
(108003, "<operator>.indirectMemberAccess" ), (87500, "<operator>.assignment" ),
(42012, "<operator>.memberAccess" ),
...
)
Integration
要集成Ocular,将其输出(称为转储的过程)并将数据导入您选择的工具。
// Dump all methods that match `.*parse.*` to the shell (syntax-highlighted)
ocular> cpg.method.name(".*parse.*").dump
// Dump all methods that match `.*parse.*` to file (no highlighting) ocular> cpg.method.name(".*parse.*").dumpRaw |> “/tmp/foo.c”
// View all methods that match `.*parse.*` in a pager (e.g., less) ocular> browse(cpg.method.name(".*parse.*").dump)
// Dump dot representations of ASTs for all methods that
// match `parse` into file
ocular> cpg.method.name(".*parse.*").dot |> “/tmp/foo.dot”
Using Ocular with an IDE
除了在交互式Shell中工作之外,您还可以使用现有的ide来处理更复杂的与程序相关的任务。
1 检测写循环
下面的两个示例演示如何检测写循环。注意,第一种语言不使用修饰语言,而第二种语言使用修饰语言(导致代码更短):
// Return (arrayName, List(subscripts))
// Noisy version without decoration language
cpg
.call(".*assign.*")
.argument(1).ast.isCall
.name(".*op.*computedMemberAccess.*")
.map { call =>
val subscripts = call.argument(2).ast.isIdentifier.code.toSet
(call.argument(1), subscripts)
}
// Return (arrayName, List(subscripts))
// Expressive version with decoration language
cpg
.assignment.target.isArrayAccess
.map { a =>
(a.array, a.subscripts.code.toSet)
}
2 向代码属性图添加知识(增量地)
随着时间的推移,您获得了希望包含在代码属性图(CPG)中的知识,您可以按如下方式添加它:
// Create a new graph to hold an additive diff (DiffGraph)
implicit val diffGraph = new io.shiftleft.passes.DiffGraph()
// Tag all methods that match `parse` with key-value pair (“interesting”, “foo”)
cpg.method.name(".*parse.*").newTagNodePair("interesting", "foo").store
// Tag all results obtained by running your script `filename_parameters`
filename_parameters(cpg).newTagNodePair ("filename").store
// Apply diff to cpg
diffGraph.apply(cpg)
// Retrieve all filename parameters as tagged by previous analysis!
cpg.tag.name("filename").parameter...
3 查询基于堆的缓冲区溢出
下面的查询查找对malloc的调用:第一个参数包含一个算术表达式、已分配的缓冲区流作为第一个参数进入memcpy、memcpy的第三个参数不等于malloc的第一个参数:
这是在31C3首次显示的old-joern查询的一个改编,它发现VLC的MP4 demuxer (CVE-2014-9626)中存在缓冲区溢出:
val src = cpg.call("malloc").filter(_.argument(1).arithmetics).l
cpg.call("memcpy").whereNonEmpty { call =>
call.argument(1).reachableBy(src.start)
.filterNot(_.argument(1).codeExact(call.argument(3).code))
}
4 将查询封装在方法中,以便在以后的代码扫描中使用
如果希望重用编写的查询,可以将它们封装在方法中,以便将来进行代码扫描
def buffer_overlows(cpg : io.shiftleft.codepropertygraph.Cpg ) = {
val src = cpg.call("malloc").filter(_.argument(1).arithmetics).l
cpg.call("memcpy").whereNonEmpty { call =>
call.argument(1).reachableBy(src.start)
.filterNot(_.argument(1).codeExact(call.argument(3).code))
}
}
defined function buffer_overflows
要运行脚本,使用:buffer_overlows(cpg)
5 贡献脚本
要访问其他人为在您的环境中使用而提供的脚本,请使用:scripts
响应的格式如下:
List[ScriptManager.ScriptDescription] = List(
ScriptDescription (
"ast-for-funcs" ,
"Returns the corresponding AST for each function as Json object."
), ScriptDescription (
"ast-for-funcs-dump" ,
"Prints the corresponding AST for each function as Json string to a file."
), ScriptDescription (
"cfg-for-funcs" ,
"Returns the corresponding CFG for each function as Json object."
),
...
]
使用脚本:runScript("name-of-script", cpg)(确保用正确的值替换name-of-script(例如,ast-for-funcs)。)
How to Identify Dependencies
本文将向您展示如何查询CPG来收集关于应用程序依赖项的信息(内部包和外部/开源包)。
1 获取依赖项
cpg.dependency.map(x => (x.name, x.version)).l
作为响应,您应该得到如下所示的依赖项列表
res1: List[(String, String)] = List(
("jasypt-spring-boot", null),
("jasypt-spring-boot-starter", null),
...
)
拥有完整的依赖列表的一个有用的应用程序是,它可以与来自漏洞数据库的已知cve进行比较(例如,NVD)。
2 应用程序与依赖项代码
在使用Ocular时,您将检查应用程序代码以确定脆弱的依赖关系。这个过程的一部分包括代码属性图(CPG)的创建,它只包括应用程序代码本身和对依赖项代码的引用。
Ocular通过智能检查输入文件来区分应用程序和依赖/库代码。
下面的示例将向您展示如何在创建CPG之前为应用程序获取名称空间信息。
2.1 显示所有应用程序名称空间:
namespaces(<inputPath>)
2.2 显示所有应用程序包:
appNamespaces(<inputPath>)
2.3 显示依赖名称空间:
depNamespaces(<inputPath>)
How to Identify Methods
本文将向您展示如何识别应用程序中出现的方法。
1 列出HTTP端点处理程序方法:
如果有一个函数使用Java注释将端点URI链接到一个通过端点URI处理用户提供的数据的函数,那么下面的查询将找到它。
更具体地说,下面的查询返回一个方法列表,其中的参数注释为一个PathVariable。这些方法没有父调用程序,这意味着它们是用户控制的API端点处理程序。
cpg.annotation.name(".*PathVariable.*").parameter.method.filterNot(_.callIn).name.l
#响应
res1: List[String] = List(
"removeCustomer",
"addInterestToAccount",
...
)
2 按名称筛选方法
下面的查询返回包含字符串控制器的所有方法的列表:
cpg.method.fullName(".*Controller.*").name.l
res21: List[String] = List(
"doGetSearch",
...
)
How to Identify Types and Packages
本文将向您展示如何识别应用程序中使用的类型(类)和包,以及调用它们的方法。
1 列出所有使用的类型:
此查询返回代码属性图(CPG)中出现的类型声明的列表
cpg.typeDecl.fullName.l
#响应
res1: List[String] = List(
"java.lang.CharSequence[]",
...
)
2 列出所有使用的外部包
此查询返回CPG中不同的外部名称空间类型声明的排序列表
cpg.typeDecl.external.namespace.name.l.distinct.sorted
#响应
res4: List[String] = List(
"com.ulisesbocchio.jasyptspringboot.annotation", "java.io",
..
)
3 筛选并列出当前的类型声明
此查询将返回CPG中包含Http的类型声明列表。
cpg.typeDecl.name(".*Http.*").name.l
#响应
res27: List[String] = List(
"CloseableHttpResponse",
...
)
How to Identify Parameters
本文将向您展示如何识别应用程序方法的参数。
1 列出给定类型的所有参数
此查询返回所有参数的列表,其evalType为HttpServletRequest:
cpg.parameter.evalType(".*HttpServletRequest.*").name.l
响应
res1: List[String] = List(
"this",
"request",
..
)
2 列出所有包含给定类型参数的方法
这个查询列出了所有包含参数的方法,这些参数的evalType是HttpServletRequest:
cpg.parameter.evalType(".*HttpServletRequest.*").method.name.l
#响应
res1: List[String] = List(
"getParameter",
"getAccountList",
...
)
How to Trace Data Flows
本文将向您展示如何通过应用程序跟踪数据流。
1 背景
一旦确定了应用程序中接受用户输入(如HTTP处理程序)、转换数据或处理数据(如日志记录器)的方法,就可以确定这些方法之间的数据流动方式。
当我们跟踪数据时,我们将数据来源的方法称为源(sources),而从源接收数据的方法称为汇(sinks)。
在跟踪数据流时,需要两个基本步骤:
a 设置变量以指示感兴趣的源和sink
b 查找从源到接收器的数据流(反之亦然)
step1 设置源和接收器变量
下面的示例将向您展示如何创建源和sink变量。注意,这些变量与参数绑定,而不是与方法本身绑定。
1 创建源变量:下面的示例查询创建了一个名为source的临时变量,并将getLedger方法的所有参数分配给它。
val source = cpg.method.name("getLedger").parameter
2 创建Sink变量:下面示例查询创建了一个名为sink的临时变量,并将属于其名称中包含运行时和exec的方法的所有参数分配给它。
val sink = cpg.method.fullName(".*Runtime.*exec.*").parameter
step2 查找从源到接收器的数据流
下面的查询将打印从源到接收器发生的所有数据流:
例子: 查找从接收器到源的数据流;除了跟踪数据如何从源流向汇聚之外,还允许您跟踪从sink流向源的数据流。
所示的查询:定义接收器(在本例中,全名为Runtime或exec的任何方法)
使用.caller查找调用接收器的所有方法;这是一个递归操作,直到Ocular找到一个可以从外部访问的方法(例如isPublic),并且具有java.Lang.String类型的一个或多个参数。
cpg.method.fullName(".*Runtime.*exec.*").repeat(m=>m.caller).until(m=> m.isPublic.parameter.evalType("java.lang.String")).emit().fullName.l
响应将类似如下所示:
res1: List[String] = List("io.shiftleft.controller.LedgerController.getLedger:java.lang.String(java.lang.Long)")
Sample Use Cases
下面的用例演示了如何使用Ocular来调查攻击面、特定漏洞类型等应用程序。
How to Uncover the Attack Surface
应用程序的攻击面是可用于实施安全攻击的所有漏洞的总和。攻击表面的大小应加以限制,以防止未经授权和恶意的用户。您可以使用Ocular 检查代码的攻击面。这通常是调查和减轻漏洞的第一步,这样您就可以优先加强最脆弱的攻击点。
这个用例基于HelloShiftLeft。重点是AdminController中的一个对象反序列化漏洞。
...
@Controller
public class AdminController {
...
@RequestMapping(value = "/admin/login", method = RequestMethod.POST)
public String doPostLogin(
@CookieValue(value = "auth", defaultValue = "notset") String auth,
@RequestBody String password, HttpServletResponse response,
HttpServletRequest request) throws Exception {
...
if (!auth.equals("notset")) {
if(isAdmin(auth)) {
request.getSession().setAttribute("auth",auth);
return succ;
}
}
...
}
...
private boolean isAdmin(String auth) {
try {
ByteArrayInputStream bis = new ByteArrayInputStream(
Base64.getDecoder().decode(auth));
ObjectInputStream objectInputStream = new ObjectInputStream(bis);
Object authToken = objectInputStream.readObject();
return ((AuthToken) authToken).isAdmin();
} catch (Exception ex) {
System.out.println(" cookie cannot be deserialized: "
+ex.getMessage());
return false;
}
}
...
在这段代码中,通过HTTP接收cookie,最终反序列化以创建Java对象,这是一种乐观的做法,攻击者经常利用它来执行任意代码。
代码属性图(CPG)包含有关在不同抽象级别上处理的代码的信息:依赖项、类型层次结构、控制流、数据流和指令级信息。在交互式或非交互式模式下,可以使用Ocular 来查询CPG。
使用交互式查询,探索程序依赖项:cpg.dependency.name.l
返回所有依赖项名称的完整列表。由于眼部查询语言(OQL)是一种基于scala的DSL,因此它还支持函数组合符。例如,要输出(名称、版本)对,请使用表达式:cpg.dependency.map(x => (x.name, x.version)).l,输出列表如下:
List[(String, String)] = List(
("zt-exec", "1.9"),
("httpclient", "4.3.4"),
("lombok", "1.16.6"),
("commons-io", "2.5"),
("joda-time", "unknown"),
("jasypt", "1.9.2"),
("jackson-databind", "unknown"),
("spring-boot-starter-web", "unknown"),
("jasypt-spring-boot-starter", "1.11"),
("spring-boot-starter-test", "unknown"),
("spring-web", "unknown"),
("hsqldb", "unknown"),
("jackson-mapper-asl", "1.5.6"),
("spring-boot-starter-actuator", "unknown"),
("spring-boot-starter-data-jpa", "unknown"),
("logback-core", "1.1.9"),
("spring-web", "4.3.6.RELEASE"),
("tomcat-embed-websocket", "8.5.11"),
...
)
也可以使用外部程序将CPG子图导出为JSON来处理它们。例如,命令:cpg.dependency.toJson |> "/tmp/dependencies.json"
将完整的依赖项信息转储到文件“/tmp/dependencies.json”中json格式。可以使用正则表达式查询CPG的字段。因此,要确定应用程序是否使用Spring框架,可以快速查询:
cpg.dependency.name(".*spring.*").l.nonEmpty
=> true
由于HelloShiftLeft应用程序使用Spring,所以查找表示攻击者控制的变量的典型Java注释是有意义的。
cpg.annotation.name(".*(CookieValue|PathVariable).*").l
从注释中,查看使用的参数:
cpg.annotation.name(".*(CookieValue|PathVariable).*").parameter.name.l
输出:
List[String] = List("customerId", "customerId", "customerId", "accountId", "accountId", "accountId", "accountId", "auth", "auth")
跟踪这些攻击者控制的变量,可以显示所有源自这些变量的数据流。为此,首先将接收集定义为由CookieValue或PathVariable注释的所有参数
val sources = cpg.annotation.name(".*(CookieValue|PathVariable).*").parameter
然后将汇点集定义为所有参数:
val sinks = cpg.method.parameter
最后,枚举从源到汇的所有流:
sinks.reachableBy(sources).flows.p
可以手动或自动检查流。例如,确定作为数据流结果而控制的参数
sinks.reachableBy(sources).flows.sink.parameter.l
该查询确定源可访问的汇,并检查相应的数据流。最后一个流元素提取了通过pathElemens的每个流。最后一个指令,并检索相应的参数。查询的结果可以存储在一个变量中进行进一步处理,这在确定大量数据流时非常有用
val controlled = sinks.reachableBy(sources).flows.sink.parameter.l
现在检索参数索引(“ast子编号”和方法全名)
controlled.map(x => s"Controlling parameter ${x.order} of ${x.start.method.fullName.l.head}").filterNot(_.contains("<operator>")).sorted
输出
"Controlling parameter 0 of java.io.ObjectInputStream.readObject:java.lang.Object()",
"Controlling parameter 0 of java.lang.String.equals:boolean(java.lang.Object)",
"Controlling parameter 1 of io.shiftleft.controller.AdminController.isAdmin:boolean(java.lang.String)",
"Controlling parameter 1 of io.shiftleft.repository.AccountRepository.findOne:java.lang.Object(java.io.Serializable)",
"Controlling parameter 1 of io.shiftleft.repository.AccountRepository.findOne:java.lang.Object(java.io.Serializable)",
"Controlling parameter 1 of io.shiftleft.repository.AccountRepository.findOne:java.lang.Object(java.io.Serializable)",
"Controlling parameter 1 of io.shiftleft.repository.AccountRepository.findOne:java.lang.Object(java.io.Serializable)",
"Controlling parameter 1 of io.shiftleft.repository.CustomerRepository.delete:void(java.io.Serializable)",
"Controlling parameter 1 of io.shiftleft.repository.CustomerRepository.exists:boolean(java.io.Serializable)",
"Controlling parameter 1 of io.shiftleft.repository.CustomerRepository.exists:boolean(java.io.Serializable)",
"Controlling parameter 1 of io.shiftleft.repository.CustomerRepository.findOne:java.lang.Object(java.io.Serializable)",
"Controlling parameter 1 of java.io.ByteArrayInputStream.<init>:void(byte[])",
"Controlling parameter 1 of java.io.ObjectInputStream.<init>:void(java.io.InputStream)",
"Controlling parameter 1 of java.lang.Long.valueOf:java.lang.Long(long)",
"Controlling parameter 1 of java.lang.Long.valueOf:java.lang.Long(long)",
"Controlling parameter 1 of java.lang.Long.valueOf:java.lang.Long(long)",
"Controlling parameter 1 of java.lang.Long.valueOf:java.lang.Long(long)",
"Controlling parameter 1 of java.util.Base64$Decoder.decode:byte[](java.lang.String)",
"Controlling parameter 2 of javax.servlet.http.HttpSession.setAttribute:void(java.lang.String,java.lang.Object)"
特别是,请注意ObjectInputStream方法的实例参数(索引为0)。控制readObject,即存在反序列化漏洞。这显示了一种更探索性的识别漏洞的方法。
How to Identify Call Chains
除了数据流之外,还可以使用脚本和REPL来标识调用链。这种能力是通过使用commons-io来演示的
在继续之前,为commons-io生成一个CPG
/.shiftleft/ocular/java2cpg.sh -f protobufzip -o commons-io-2.5.bin.zip commons-io-2.5.jar -nb #在继续之前,为commons-io生成一个CPG
sl ocular
loadCpg("commons-io-2.5.bin.zip") #启动Ocular并加载你创建的CPG
您可以搜索感兴趣的方法(其中之一是java.lang.Runtime.exec)
cpg.method.fullName(".*exec.*").fullName.p
java.lang.Runtime.exec:java.lang.Process(java.lang.String[])
回答“数据从何而来”和“数据可以控制吗?”,使用关键字caller查找调用堆栈:
cpg.method.fullName(".*exec.*").caller.fullName.p
org.apache.commons.io.FileSystemUtils.openProcess:java.lang.Process(java.lang.String[])
cpg.method.fullName(".*exec.*").caller.caller.fullName.p
org.apache.commons.io.FileSystemUtils.performCommand:java.util.List<java.lang.String>(java.lang.String[],int,long)
cpg.method.fullName(".*exec.*").caller.caller.caller.fullName.p
org.apache.commons.io.FileSystemUtils.freeSpaceUnix:long(java.lang.String,boolean,boolean,long)
org.apache.commons.io.FileSystemUtils.freeSpaceWindows:long(java.lang.String,long)
cpg.method.fullName(".*exec.*").caller.caller.caller.caller.fullName.p
org.apache.commons.io.FileSystemUtils.freeSpaceOS:long(java.lang.String,int,boolean,long)
org.apache.commons.io.FileSystemUtils.freeSpaceOS:long(java.lang.String,int,boolean,long)
org.apache.commons.io.FileSystemUtils.freeSpaceOS:long(java.lang.String,int,boolean,long)
org.apache.commons.io.FileSystemUtils.freeSpaceOS:long(java.lang.String,int,boolean,long)
[..]
cpg.method.fullName(".*exec.*").caller.caller.caller.caller.caller.caller.caller.fullName.p
org.apache.commons.io.FileSystemUtils.freeSpaceKb:long()
org.apache.commons.io.FileSystemUtils.freeSpaceKb:long()
org.apache.commons.io.FileSystemUtils.freeSpaceKb:long()
org.apache.commons.io.FileSystemUtils.freeSpaceKb:long()
cpg.method.fullName(".*exec.*").caller.caller.caller.caller.caller.caller.caller.caller.fullName.p
[no results]
即使在一个小JAR中,也需要7个步骤才能找到调用堆栈的“开始”,其中没有调用者。虽然很清楚数据来自调用堆栈的某个地方,但是不知道数据是否可以控制。
查看调用堆栈中的每个方法,并检查它是否使用了正确的参数,是非常耗时的。因此,重复这些步骤,直到引入和发出。下面的查询从一个名为exec的方法开始,并重复该方法。调用者步骤,直到找到一个公共方法并使用java.lang.String类型的参数
cpg.method.name("exec").repeat(method=>method.caller).until(method=> method.isPublic.parameter.evalType("java.lang.String")).emit().fullName.p
org.apache.commons.io.FileSystemUtils.openProcess:java.lang.Process(java.lang.String[])
org.apache.commons.io.FileSystemUtils.performCommand:java.util.List<java.lang.String>(java.lang.String[],int,long)
org.apache.commons.io.FileSystemUtils.freeSpaceUnix:long(java.lang.String,boolean,boolean,long)
org.apache.commons.io.FileSystemUtils.freeSpaceWindows:long(java.lang.String,long)
org.apache.commons.io.FileSystemUtils.freeSpaceOS:long(java.lang.String,int,boolean,long)
org.apache.commons.io.FileSystemUtils.freeSpaceOS:long(java.lang.String,int,boolean,long)
org.apache.commons.io.FileSystemUtils.freeSpaceOS:long(java.lang.String,int,boolean,long)
org.apache.commons.io.FileSystemUtils.freeSpaceOS:long(java.lang.String,int,boolean,long)
org.apache.commons.io.FileSystemUtils.freeSpaceKb:long(java.lang.String,long)
org.apache.commons.io.FileSystemUtils.freeSpace:long(java.lang.String)
org.apache.commons.io.FileSystemUtils.freeSpaceKb:long(java.lang.String,long)
org.apache.commons.io.FileSystemUtils.freeSpace:long(java.lang.String)
org.apache.commons.io.FileSystemUtils.freeSpaceKb:long(java.lang.String,long)
org.apache.commons.io.FileSystemUtils.freeSpace:long(java.lang.String)
org.apache.commons.io.FileSystemUtils.freeSpaceKb:long(java.lang.String,long)
org.apache.commons.io.FileSystemUtils.freeSpace:long(java.lang.String)
这个查询回答了“数据来自何处”这个问题。因为这些方法是公开的,所以数据可能是可控的。但是,还不清楚数据是否真的从方法的参数流向exec方法。为了将上述查询的结果标记为源,请使用exchange.fullname.p以及.parameter,而sink是我们的exec方法。
val source = cpg.method.name("exec").repeat(m=>m.caller).until(m=> m.isPublic.parameter.evalType("java.lang.String")).emit().parameter
val sink = cpg.method.name("exec").parameter
sink.reachableBy(source).flows.p
你可以看到:org.apache.commons.io.FileSystemUtils.freeSpace:long(java.lang.String)被发现是公开可用的,并且在此参数和exec之间存在数据流。
------ Flow with 15 elements ------
path 142 freeSpace org/apache/commons/io/FileSystemUtils.java
path 143 freeSpace org/apache/commons/io/FileSystemUtils.java
path 259 freeSpaceOS org/apache/commons/io/FileSystemUtils.java
path 269 freeSpaceOS org/apache/commons/io/FileSystemUtils.java
path 381 freeSpaceUnix org/apache/commons/io/FileSystemUtils.java
path 401 freeSpaceUnix org/apache/commons/io/FileSystemUtils.java
param1 <operator>.assignment N/A
param0 <operator>.assignment N/A
cmdAttribs[2] 401 freeSpaceUnix org/apache/commons/io/FileSystemUtils.java
cmdAttribs 398 freeSpaceUnix org/apache/commons/io/FileSystemUtils.java
cmdAttribs 473 performCommand org/apache/commons/io/FileSystemUtils.java
cmdAttribs 484 performCommand org/apache/commons/io/FileSystemUtils.java
cmdAttribs 537 openProcess org/apache/commons/io/FileSystemUtils.java
cmdAttribs 538 openProcess org/apache/commons/io/FileSystemUtils.java
param0 exec java/lang/Runtime.java
How to Detect 0-Day Vulnerabilities
一个零日的漏洞对于开发人员和安全研究人员来说是未知的,或者没有得到解决,并且被认为是一个严重的威胁。在一个0天的漏洞被识别和缓解之前,黑客可以利用它。
这个用例基于CVE-2018-19859,一个允许攻击者通过OpenRefine执行任意文件写的漏洞。
CVE-2018-19859描述了一种目录遍历攻击,可以作为任意文件写。该漏洞源于对ZIP文件的不安全处理,安全研究人员、分析人员和渗透测试人员经常看到这种漏洞模式。
OpenRefine被其作者描述为“一个自由的、开放源码的强大工具,用于处理复杂的数据并对其进行改进”。OpenRefine的一个常见用例是在统计计算之前对混乱的公共数据集进行清理,为此它提供了一些功能,比如导入和导出可能分散在多个文件或归档中的数据。
1 Creating the CPG
OpenRefine并不是像可视化工具java2cpg所期望的那样作为单个JAR文件分发的。但是,只要输入是ZIP格式的,java2cpg就不要求它的输入文件符合特定的文件结构。这种格式基本上相当于一个JAR归档;带有.jar后缀的ZIP文件包含感兴趣的.class文件就足够了。基于可以作为.tar.gz存档下载的OpenRefine源代码,通过执行来为java2cpg准备.jar
wget https://github.com/OpenRefine/OpenRefine/releases/download/3.1/openrefine-linux-3.1.tar.gz#下载
tar xfz openrefine-linux-3.1.tar.gz#解压
find openrefine-3.1 -name "*.class" | zip openrefine.jar -@
然后使用-w标志运行java2cpg,后面跟着一个用逗号分隔的包名列表(com.google.refine, org.openrefine) ,这些包名将包含在CPG中。只有带有包前缀的应用程序部分使用com.google.refine和org.openrefine包含在CPG中。对于相对较大的应用程序(如OpenRefine),关注特定的应用程序部分可以节省计算和分析时间。
./java2cpg.sh openrefine.jar -w com.google.refine,org.openrefine -nb -o openrefine.bin.zip
最后,将新创建的CPG并加载
sl ocular
loadCpg("openrefine.bin.zip")
2 Identifying Sources
输入源(导入器)表示可能有恶意(攻击者控制的)数据进入系统的程序点。使用目视,你可以搜索和定义一个输入源。
OpenRefine依赖于导入和导出数据。使用搜索策略通过标识在其完整方法名称中包含子字符串导入的所有方法来查找导入。此搜索策略返回502个方法,数量太大,无法手动检查。您可以过滤搜索,以进一步缩小方法的数量。
cpg.method.fullName(".*Import.*").toList.size
res1: Int = 502
根据在JavaServlet代码中经常发现的命名约定,通过只包含通过HTTP访问的方法,缩小了方法的范围。这是通过在搜索模式中添加do(Get|Post)来实现的:将HTTP Get处理程序命名为doGet,而将HTTP Post处理程序命名为doPost。通过执行这个查询,结果的数量减少到只有13个方法,这对于手动检查来说已经足够少了。
cpg.method.fullName(".*Import.*do(Get|Post).*").toList.size
res2: Int = 13
有些方法存储在DefaultImportingController类中,顾名思义,从安全性的角度来看,这个类可能很有趣。通过使用DefaultImportingController替换Import,查询得到了增强。新的搜索结果由doGet和doPost方法组成
cpg.method.fullName(".*DefaultImportingController.*do(Get|Post).*").fullName.p
com.google.refine.importing.DefaultImportingController.doGet:void(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
com.google.refine.importing.DefaultImportingController.doPost:void(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
根据这些结果,仅通过发出三个查询,就可以找到一个源作为安全分析的起点。下面的查询通过应用搜索过滤器定义了一个源。更具体地说,该查询还指定了所需的参数类型(即HttpServletRequest)
val source = cpg.method.fullName(".*DefaultImportingController.*do(Get|Post).*").parameter.evalType(".*HttpServletRequest.*")
3 Looking for the Sink
接收是安全敏感的程序点,恶意的攻击者控制的输入(来自接收)可能会流向这些点。
基于OpenRefine CVE,您可以找到并定义带有目视的、特别是解压缩漏洞的汇。
使用一个非常基本的查询,先确定哪些方法是zip包的一部分;其中,调用getName作为其名称的一部分的方法。结果是explodeArchive
cpg.method.fullName(".*zip.*getName.*").caller.fullName.p
com.google.refine.importing.ImportingUtilities.explodeArchive:boolean(java.io.File,java.io.InputStream,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress)
从它的名字来看,这个函数explodeArchive似乎是值得研究的。这个查询将explodeArchive方法的参数标记为一个接收器
val sink = cpg.method.name("explodeArchive").parameter
要查找源和汇之间可能的数据流,请发出reachableBy查询,如代码片段所示
sink.reachableBy(source).flows.p
4 Resulting Flow
通过发出reachableBy查询,提供了关于数据流的详细信息,该信息从doPost方法和类型为HttpServletRequest的名为request的参数开始。回顾这个流程,很明显,OpenRefine:
1 从POST请求中检索内容:retrieveContentFromPostRequest
2 一个名为download的方法使用一个名为urlString的变量
3 saveStream正在使用一个url
4 文件被分配(allocateFile)
5 调用tryOpenAsArchive方法
6 使用名为archiveIS的ZipInputStream变量解压缩explodeArchive。
总之,OpenRefine基于URL下载数据,将其读取为ZipInputStream,并尝试使用explodeArchive方法解压缩数据。
________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________
| param | type | method | signature |
|=======================================================================================================================================================================================================================================================================================================================================================================|
| request(1) | javax.servlet.http.HttpServletRequest | doPost | com.google.refine.importing.DefaultImportingController.doPost:void(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) |
| request | javax.servlet.http.HttpServletRequest | doPost | com.google.refine.importing.DefaultImportingController.doPost:void(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) |
| request(1) | javax.servlet.http.HttpServletRequest | doLoadRawData | com.google.refine.importing.DefaultImportingController.doLoadRawData:void(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.util.Properties) |
| request | javax.servlet.http.HttpServletRequest | doLoadRawData | com.google.refine.importing.DefaultImportingController.doLoadRawData:void(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.util.Properties) |
| request(1) | javax.servlet.http.HttpServletRequest | loadDataAndPrepareJob | com.google.refine.importing.ImportingUtilities.loadDataAndPrepareJob:void(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.util.Properties,com.google.refine.importing.ImportingJob,org.json.JSONObject) |
| request | javax.servlet.http.HttpServletRequest | loadDataAndPrepareJob | com.google.refine.importing.ImportingUtilities.loadDataAndPrepareJob:void(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.util.Properties,com.google.refine.importing.ImportingJob,org.json.JSONObject) |
| request(1) | javax.servlet.http.HttpServletRequest | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| request | javax.servlet.http.HttpServletRequest | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| param0(1) | javax.servlet.http.HttpServletRequest | parseRequest | org.apache.commons.fileupload.servlet.ServletFileUpload.parseRequest:java.util.List(javax.servlet.http.HttpServletRequest) |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| tempFiles | java.util.List | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| tempFiles | java.util.List | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| this(0) | java.util.List | iterator | java.util.List.iterator:java.util.Iterator() |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| l12_0 | java.util.Iterator | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| l12_0 | java.util.Iterator | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| this(0) | java.util.Iterator | next | java.util.Iterator.next:java.lang.Object() |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| $r2 | java.lang.Object | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| $r2 | java.lang.Object | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| param1(2) | ANY | <operator>.cast | <operator>.cast |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| fileItem | org.apache.commons.fileupload.FileItem| retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| fileItem | org.apache.commons.fileupload.FileItem| retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| this(0) | org.apache.commons.fileupload.FileItem| getInputStream | org.apache.commons.fileupload.FileItem.getInputStream:java.io.InputStream() |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| stream | java.io.InputStream | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| stream | java.io.InputStream | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| param0(1) | java.io.InputStream | asString | org.apache.commons.fileupload.util.Streams.asString:java.lang.String(java.io.InputStream) |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| urlString | java.lang.String | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| urlString | java.lang.String | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| urlString(6)| java.lang.String | download | com.google.refine.importing.ImportingUtilities.download:void(java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$SavingUpdate,java.lang.String) |
| urlString | java.lang.String | download | com.google.refine.importing.ImportingUtilities.download:void(java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$SavingUpdate,java.lang.String) |
| urlString(6)| java.lang.String | download | com.google.refine.importing.ImportingUtilities.download:void(java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$SavingUpdate,java.lang.String,java.lang.String) |
| urlString | java.lang.String | download | com.google.refine.importing.ImportingUtilities.download:void(java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$SavingUpdate,java.lang.String,java.lang.String) |
| param0(1) | java.lang.String | <init> | java.net.URL.<init>:void(java.lang.String) |
| url | java.net.URL | download | com.google.refine.importing.ImportingUtilities.download:void(java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$SavingUpdate,java.lang.String,java.lang.String) |
| url | java.net.URL | download | com.google.refine.importing.ImportingUtilities.download:void(java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$SavingUpdate,java.lang.String,java.lang.String) |
| url(2) | java.net.URL | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| url | java.net.URL | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| this(0) | java.net.URL | getPath | java.net.URL.getPath:java.lang.String() |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| localname | java.lang.String | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| localname | java.lang.String | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| param0(1) | java.lang.String | append | java.lang.StringBuilder.append:java.lang.StringBuilder(java.lang.String) |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| $r1 | java.lang.StringBuilder | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| $r1 | java.lang.StringBuilder | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| this(0) | java.lang.StringBuilder | append | java.lang.StringBuilder.append:java.lang.StringBuilder(java.lang.String) |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| $r2 | java.lang.StringBuilder | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| $r2 | java.lang.StringBuilder | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| this(0) | java.lang.StringBuilder | toString | java.lang.StringBuilder.toString:java.lang.String() |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| localname | java.lang.String | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| localname | java.lang.String | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| name(2) | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| name | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| this(0) | java.lang.String | substring | java.lang.String.substring:java.lang.String(int,int) |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| name | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| name | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| name | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| name | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| param1(2) | java.lang.String | <init> | java.io.File.<init>:void(java.io.File,java.lang.String) |
| file | java.io.File | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| file | java.io.File | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| file | java.io.File | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| file | java.io.File | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| file(2) | java.io.File | postProcessRetrievedFile | com.google.refine.importing.ImportingUtilities.postProcessRetrievedFile:boolean(java.io.File,java.io.File,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress) |
| file | java.io.File | postProcessRetrievedFile | com.google.refine.importing.ImportingUtilities.postProcessRetrievedFile:boolean(java.io.File,java.io.File,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress) |
| file(1) | java.io.File | tryOpenAsArchive | com.google.refine.importing.ImportingUtilities.tryOpenAsArchive:java.io.InputStream(java.io.File,java.lang.String,java.lang.String) |
| file | java.io.File | tryOpenAsArchive | com.google.refine.importing.ImportingUtilities.tryOpenAsArchive:java.io.InputStream(java.io.File,java.lang.String,java.lang.String) |
| param0(1) | java.io.File | <init> | java.io.FileInputStream.<init>:void(java.io.File) |
| $r7 | java.io.FileInputStream | tryOpenAsArchive | com.google.refine.importing.ImportingUtilities.tryOpenAsArchive:java.io.InputStream(java.io.File,java.lang.String,java.lang.String) |
| $r7 | java.io.FileInputStream | tryOpenAsArchive | com.google.refine.importing.ImportingUtilities.tryOpenAsArchive:java.io.InputStream(java.io.File,java.lang.String,java.lang.String) |
| param0(1) | java.io.InputStream | <init> | java.util.zip.ZipInputStream.<init>:void(java.io.InputStream) |
| $r6 | java.util.zip.ZipInputStream | tryOpenAsArchive | com.google.refine.importing.ImportingUtilities.tryOpenAsArchive:java.io.InputStream(java.io.File,java.lang.String,java.lang.String) |
| $r6 | java.util.zip.ZipInputStream | tryOpenAsArchive | com.google.refine.importing.ImportingUtilities.tryOpenAsArchive:java.io.InputStream(java.io.File,java.lang.String,java.lang.String) |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| archiveIS | java.io.InputStream | postProcessRetrievedFile | com.google.refine.importing.ImportingUtilities.postProcessRetrievedFile:boolean(java.io.File,java.io.File,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress) |
| archiveIS | java.io.InputStream | postProcessRetrievedFile | com.google.refine.importing.ImportingUtilities.postProcessRetrievedFile:boolean(java.io.File,java.io.File,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress) |
| archiveIS(2)| java.io.InputStream | explodeArchive | com.google.refine.importing.ImportingUtilities.explodeArchive:boolean(java.io.File,java.io.InputStream,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress) |
5 Detecting a Vulnerable Flow
在查找源和汇之后,您可以改进搜索以检测实际的漏洞。
ZipInputSteam已经被控制,但是对于这个漏洞,您需要找到一个文件流,或者更好的是FileOutputStream。在这个查询中,将FileOutputStream构造函数用作接收器,更具体地说,用作构造函数的第一个参数。
val source = cpg.method.name("explodeArchive").parameter
val sink = cpg.method.fullName(".*FileOutputStream.*init.*").parameter.index(1)
通过发出reachableBy查询,可以找到各种流,因为有许多FileInputStream接收器。要减少流的数量,请提供额外的专家知识:通过通过getName方法传递恶意输入,攻击者可以控制提取ZIP文件的目标路径。在流上应用了pass过滤器之后,流的数量减少到2
sink.reachableBy(source).flows.l.size
res28: Int = 1847
sink.reachableBy(source).flows.passes("getName").l.size
res29: Int = 2
结果流如下所示:
___________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________
| param | type | method | signature |
|==========================================================================================================================================================================================================================================================================|
| archiveIS(2)| java.io.InputStream | explodeArchive | com.google.refine.importing.ImportingUtilities.explodeArchive:boolean(java.io.File,java.io.InputStream,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress)|
| archiveIS | java.io.InputStream | explodeArchive | com.google.refine.importing.ImportingUtilities.explodeArchive:boolean(java.io.File,java.io.InputStream,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress)|
| param1(2) | ANY | <operator>.cast | <operator>.cast |
| param1(2) | ANY | <operator>.assignment| <operator>.assignment |
| zis | java.util.zip.ZipInputStream| explodeArchive | com.google.refine.importing.ImportingUtilities.explodeArchive:boolean(java.io.File,java.io.InputStream,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress)|
| zis | java.util.zip.ZipInputStream| explodeArchive | com.google.refine.importing.ImportingUtilities.explodeArchive:boolean(java.io.File,java.io.InputStream,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress)|
| this(0) | java.util.zip.ZipInputStream| getNextEntry | java.util.zip.ZipInputStream.getNextEntry:java.util.zip.ZipEntry() |
| param1(2) | ANY | <operator>.assignment| <operator>.assignment |
| ze | java.util.zip.ZipEntry | explodeArchive | com.google.refine.importing.ImportingUtilities.explodeArchive:boolean(java.io.File,java.io.InputStream,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress)|
| ze | java.util.zip.ZipEntry | explodeArchive | com.google.refine.importing.ImportingUtilities.explodeArchive:boolean(java.io.File,java.io.InputStream,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress)|
| param1(2) | ANY | <operator>.assignment| <operator>.assignment |
| ze | java.util.zip.ZipEntry | explodeArchive | com.google.refine.importing.ImportingUtilities.explodeArchive:boolean(java.io.File,java.io.InputStream,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress)|
| ze | java.util.zip.ZipEntry | explodeArchive | com.google.refine.importing.ImportingUtilities.explodeArchive:boolean(java.io.File,java.io.InputStream,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress)|
| this(0) | java.util.zip.ZipEntry | getName | java.util.zip.ZipEntry.getName:java.lang.String() |
| param1(2) | ANY | <operator>.assignment| <operator>.assignment |
| fileName2 | java.lang.String | explodeArchive | com.google.refine.importing.ImportingUtilities.explodeArchive:boolean(java.io.File,java.io.InputStream,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress)|
| fileName2 | java.lang.String | explodeArchive | com.google.refine.importing.ImportingUtilities.explodeArchive:boolean(java.io.File,java.io.InputStream,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress)|
| name(2) | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| name | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| this(0) | java.lang.String | substring | java.lang.String.substring:java.lang.String(int,int) |
| param1(2) | ANY | <operator>.assignment| <operator>.assignment |
| name | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| name | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| param1(2) | ANY | <operator>.assignment| <operator>.assignment |
| name | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| name | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| param1(2) | java.lang.String | <init> | java.io.File.<init>:void(java.io.File,java.lang.String) |
| file | java.io.File | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| file | java.io.File | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| param1(2) | ANY | <operator>.assignment| <operator>.assignment |
| file2 | java.io.File | explodeArchive | com.google.refine.importing.ImportingUtilities.explodeArchive:boolean(java.io.File,java.io.InputStream,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress)|
| file2 | java.io.File | explodeArchive | com.google.refine.importing.ImportingUtilities.explodeArchive:boolean(java.io.File,java.io.InputStream,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress)|
| file(2) | java.io.File | saveStreamToFile | com.google.refine.importing.ImportingUtilities.saveStreamToFile:long(java.io.InputStream,java.io.File,com.google.refine.importing.ImportingUtilities$SavingUpdate) |
| file | java.io.File | saveStreamToFile | com.google.refine.importing.ImportingUtilities.saveStreamToFile:long(java.io.InputStream,java.io.File,com.google.refine.importing.ImportingUtilities$SavingUpdate) |
| param0(1) | java.io.File | <init> | java.io.FileOutputStream.<init>:void(java.io.File)
6 Verifying the Vulnerability
控制从doPost到explodeArchive和从explodeArchive到FileOutputStream实例的流。为了验证漏洞的存在,只剩下一个问题:这个文件实际上是写的吗?要回答这个问题,请查看FileOutputStream的写方法,该方法用作接收器。除了reachableBy查询之外,还要使用passesNot过滤器,它确保只在没有uncompressFile方法的情况下流动,使用uncompressFile方法处理.gz和.bz2。
val source = cpg.method.name("explodeArchive").parameter
val sink = cpg.method.fullName(".*FileOutputStream.*write.*").parameter.index(1)
sink.reachableBy(source).passesNot("uncompressFile").p
结果如下
___________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________
| param | type | method | signature |
|==========================================================================================================================================================================================================================================================================|
| archiveIS(2)| java.io.InputStream | explodeArchive | com.google.refine.importing.ImportingUtilities.explodeArchive:boolean(java.io.File,java.io.InputStream,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress)|
| archiveIS | java.io.InputStream | explodeArchive | com.google.refine.importing.ImportingUtilities.explodeArchive:boolean(java.io.File,java.io.InputStream,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress)|
| param1(2) | ANY | <operator>.cast | <operator>.cast |
| param1(2) | ANY | <operator>.assignment| <operator>.assignment |
| zis | java.util.zip.ZipInputStream| explodeArchive | com.google.refine.importing.ImportingUtilities.explodeArchive:boolean(java.io.File,java.io.InputStream,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress)|
| zis | java.util.zip.ZipInputStream| explodeArchive | com.google.refine.importing.ImportingUtilities.explodeArchive:boolean(java.io.File,java.io.InputStream,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress)|
| stream(1) | java.io.InputStream | saveStreamToFile | com.google.refine.importing.ImportingUtilities.saveStreamToFile:long(java.io.InputStream,java.io.File,com.google.refine.importing.ImportingUtilities$SavingUpdate) |
| stream | java.io.InputStream | saveStreamToFile | com.google.refine.importing.ImportingUtilities.saveStreamToFile:long(java.io.InputStream,java.io.File,com.google.refine.importing.ImportingUtilities$SavingUpdate) |
| this(0) | java.io.InputStream | read | java.io.InputStream.read:int(byte[]) |
| bytes | byte[] | saveStreamToFile | com.google.refine.importing.ImportingUtilities.saveStreamToFile:long(java.io.InputStream,java.io.File,com.google.refine.importing.ImportingUtilities$SavingUpdate) |
| bytes | byte[] | saveStreamToFile | com.google.refine.importing.ImportingUtilities.saveStreamToFile:long(java.io.InputStream,java.io.File,com.google.refine.importing.ImportingUtilities$SavingUpdate) |
| param0(1) | byte[] | write | java.io.FileOutputStream.write:void(byte[],int,int) |
7 结论
OpenRefine中的漏洞CVE-2018-19859可以检测,分别使用explodeArchive和write(来自FileOutputStream)作为源和汇。验证了该漏洞的存在性。但是,您仍然需要创建一个利用来查看它是否真的可以被利用。
How to Discover HTTP Cookie Poisoning
HTTP cookie的主要用途是识别用户,并根据用户的配置文件为他们准备定制的Web页面。Cookie中毒是攻击者修改Cookie以滥用应用程序功能的行为。cookie存在三个方面的潜在安全风险:
1 cookie可能会以纯文本的形式添加敏感信息,不建议这样做。恶意用户可以嗅探连接以捕获通过网络传输的任何数据。因此,应该使用加密。
2 开发人员可能没有设置HttpOnly属性。HttpOnly是一条命令,让浏览器不更改cookie,也不将其交给JavaScript引擎。设置HttpOnly属性可以限制XSS漏洞造成的损害。
3 开发人员没有设置Secure属性。开发人员通常将各种信息存储在cookie中。因为cookie很容易操作,所以它们可以将信息从服务器交换到浏览器,反之亦然。当不使用Secure属性时,风险是中间人攻击。
发现cookie中毒漏洞的困难之处在于cookie的使用通常与其他几个程序语句交织在一起。对于正在实现的功能,这些其他语句更重要,因为它们最终隐藏了cookie的重要性(及其安全风险)。因此,开发人员或安全研究人员可能觉得没有必要关注cookie。
使用Ocular,您可以通过执行以下命令来识别cookie中毒漏洞:
val source = cpg.method.fullName(".*Cookie.<init>.*").parameter
val sink = cpg.method.name("addCookie").parameter
sink.reachableBy(source).flows.passesNot("setHttpOnly").passesNot("setSecure").p
val source = cpg.method.name("addCookie").parameter
val sink = cpg.method.fullName(".*javax.servlet.RequestDispatcher.forward.*").parameter
sink.reachableBy(source).flows.passesNot("setHttpOnly").passesNot("setSecure").p
How to Track Non Atomic Data Types
一般来说,进程不会自动执行,因为操作系统可能会在两个指令之间中断进程,从而允许其他进程运行。如果应用程序的进程没有为这些中断做好准备,那么另一个进程可能会干扰它,导致数据结构在它们之间执行任意代码时处于不一致的状态。
非原子条件是受不可信进程干扰的脆弱性。这些条件是由运行其他不同程序的进程造成的,这些进程在程序的步骤之间引入了其他操作。这些其他程序可能被攻击者调用。
这个用例演示了如何使用Ocular来分析非原子数据类型,使用FreeRTOS实时操作系统内核作为示例目标应用程序。
1 下载FreeRTOS应用程序:下载FreeRTOS应用程序并将源代码解压缩到Ocular主题目录,例如subjects/FreeRTOS。
2 创建FreeRTOS应用程序CPG:
createCpg("subjects/"FreeRTOS") #创建FreeRTOS应用程序CPG
##FreeRTOS的CPG会自动加载到内存和您的工作空间中。
val primitiveTypes = List("int", "float", "double", "void", "size_t", "ANY", "void", "char", "short") #声明一个基元类型数组
def getLineNumber(ln : Option[Integer]) = (ln match { case Some(x) => x ; case None => 0 }).asInstanceOf[Int] #调用一个方便的函数来获得LineNumber
case class UDT(uType : String, uName : String, methodName : String, fileName : String, lineNumber : Integer) #声明用户定义类型的数据结构
val udtList = cpg.identifier.l.map {
i => UDT(i.typeFullName, i.name, i.start.method.name.l.head, i.start.file.name.l.head, getLineNumber(i.lineNumber))
} filter {
p => !primitiveTypes.exists(e => p.uType.contains(e))
} distinct #将所有标识符获取到UDT数据结构中,原始类型上的负过滤器。
import collection.mutable._
val udtMap = new HashMap[String,Set[UDT]] with MultiMap[String,UDT]
udtList.foreach {
item => udtMap.addBinding(item.methodName, item)
} #通过标识的方法名将结果存储在一个多重映射值中
import collection.mutable._
val udtMapByType = new HashMap[String,Set[UDT]] with MultiMap[String,UDT]
udtList.foreach {
item => udtMapByType.addBinding(item.uType, item)
}
implicit def flat[K,V](kv: (K, Option[V])) = kv._2.map(kv._1 -> _).toList #根据标识的类型将结果存储在一个多重映射值中
def getRange(start : Int , end : Int) = start to end toList #调用一个方便的函数来获得给定开始和结束的范围
def getCallOutDetails(fnName:String) = cpg.method.name(fnName).callOut.l.map(co => (co.name , getLineNumber(co.lineNumber))).sortBy(_._2) #使用functionName返回函数范围内的所有调用
def getCallOutDetails(fnName:String) = cpg.method.name(fnName).callOut.l.par.map(co => (co.name , getLineNumber(co.lineNumber))).toList.sortBy(_._2) #优化的名字替换
case class WithCriticalSection(fnName : String, fileName : String, csRange : List[(String, Int, Int)]) #调用一个方便的函数来保存具有临界段和范围的函数
指定要优化测距的函数:用于一个方法(函数)中存在多个临界段的情况。
def getBounds(dataTuples : List[(String,Int)]) =
dataTuples.foldLeft((List.empty[(String, Int)], Option.empty[(String, Int)])) {
case ((state, previousSignal), signal) =>
if (previousSignal.exists(_._1.contains("EXIT")) && signal._1.contains("EXIT")) {
(state.dropRight(1) :+ signal, Some(signal))
} else {
(state :+ signal, Some(signal))
}
}
def getRange(enterExits : (List[(String, Int)], Option[(String, Int)])) =
enterExits._1.foldLeft(
(List.empty[(String, Int, Int)], Option.empty[(String, Int, Int)])) {
case ((state, accSignal), signal) =>
if (signal._1.contains("ENTER")) {
(state, Some(("CRITICAL_SECTION_RANGE", signal._2, 0)))
} else {
val enterExt = accSignal.map(elem => elem.copy(_3 = signal._2))
(state :+ enterExt.get, Option.empty)
}
}._1
val callMap = cpg.method.filter(_.callOut.name("taskENTER_CRITICAL")).l map {
s => Map(s.name -> (s.start.file.name.head, getCallOutDetails(s.name.replaceAll("\\*",""))))
} reduce(_ ++ _) #获取包含taskENTER_CRITICAL的每个方法的整个callOut跟踪
val callMapWithoutCS = cpg.method.filterNot(_.callOut.name("taskENTER_CRITICAL")).l map {
s => Map(s.name -> (s.start.file.name.headOption.getOrElse("NOT_IDENTIFIED"), getCallOutDetails(s.name.replaceAll("\\*",""))))
} reduce(_ ++ _) #获取不包含taskENTER_CRITICAL的每个方法的整个callOut跟踪
优化替代
def timeTaken[R](block: => R): R = {
val t0 = System.nanoTime()
val result = block // call-by-name
val t1 = System.nanoTime()
println("Elapsed time: " + (t1 - t0) + "ns")
result
}
val mWithoutCS = cpg.method.filterNot(_.callOut.name("taskENTER_CRITICAL")).l
timeTaken {
val callMapWithoutCS = mWithoutCS.map {
s => Map(s.name -> (s.start.file.name.headOption.getOrElse("NOT_IDENTIFIED"), getCallOutDetails(s.name.replaceAll("\\*",""))))
} reduce(_ ++ _)
}
过滤callMap并与criticalsection相匹配
val callMapFiltered = callMap map {
case(k,v) => k -> (v._1 , getRange(getBounds(v._2.filter(t => t._1.contains("taskENTER_CRITICAL") || t._1.contains("taskEXIT_CRITICAL")))))
} map { case(k,v) => k -> WithCriticalSection(k,v._1,v._2) }
使用标识符导航
…
"SIZEOF_LONG_LONG" -> Set(
UDT("SIZEOF_LONG_LONG", "lng", "tfp_format", "../../Downloads/dahling-fw/Src/utils/tinystdio.c", 398),
UDT("SIZEOF_LONG_LONG", "lng", "tfp_format", "../../Downloads/dahling-fw/Src/utils/tinystdio.c", 412),
UDT("SIZEOF_LONG_LONG", "lng", "tfp_format", "../../Downloads/dahling-fw/Src/utils/tinystdio.c", 408)
...
val nonAtomicUsedInCS =callMap.getOrElse("tfp_format","NOT_FOUND")
val nonAtomicUsedInNoCS =callMapWithoutCS.getOrElse("tfp_format","NOT_FOUND")
例如xQueueAddToSet。从udtMapByType中选择任何类型,例如tfp_format。
…
"SIZEOF_LONG_LONG" -> Set(
UDT("SIZEOF_LONG_LONG", "lng", "tfp_format", "../../Downloads/dahling-fw/Src/utils/tinystdio.c", 398),
UDT("SIZEOF_LONG_LONG", "lng", "tfp_format", "../../Downloads/dahling-fw/Src/utils/tinystdio.c", 412),
UDT("SIZEOF_LONG_LONG", "lng", "tfp_format", "../../Downloads/dahling-fw/Src/utils/tinystdio.c", 408)
...
val nonAtomicUsedInCS =callMap.getOrElse("tfp_format","NOT_FOUND")
val nonAtomicUsedInNoCS =callMapWithoutCS.getOrElse("tfp_format","NOT_FOUND")
如果(nonAtomicUsedInCS.size> 0 &nonAtomicUsedInNoCSsize>0),这意味着非原子数据类型既在保护的上下文中使用,也不在保护的上下文中使用,这可能会导致死锁或饥饿。
确定使用非原子数据类型的位置细节:
val udtSet = udtMap("xStreamBufferReset")
val callSiteData = callMapFiltered.get("xStreamBufferReset").get
udtSet.foreach {
udt => callSiteData.csRange.map { item =>
if((udt.lineNumber > item._2) && (udt.lineNumber < item._3)) {
printf("[%s] is bound in critical section at [%d] between [%d] and [%d] in methodName [%s] located at [%s]\n", udt.uName, udt.lineNumber, item._2, item._3, udt.methodName, udt.fileName)
}
}
}
Integrating Ocular with Jenkins Pipelines
用于Ocular 的Jenkins插件允许您在构建过程中使用眼部自动化代码分析。
要求:本文假设您已经安装了Jenkins并拥有一个现有的管道项目。
为Ocular设置Jenkins插件
下载sl-ocular-scan插件并将其上传到您的环境中。
接下来,通过将以下内容添加到Jenkinsfile,将Jenkins设置为在管道项目中运行Ocular作为最后的构建步骤
pipeline {
agent any
stages {
stage('Ocular Scan') {
steps {
slOcularScan(
artifact: "ARTIFACT_URL",
threadFix: false,
debug: true,
ocularArgs: "-J-Xmx4000m",
orgId: "ORG_ID",
accessToken: "ACCESS_TOKEN"
)
}
}
}
}
}
注意,slOcularScan需要几个参数;一定要在配置期间适当地更改它们
参数 | 描述 |
artifact | 要分析的文件的链接 |
threadFix | 可选的。是否需要生成ThreadFix文件 |
debug | 可选的。是否需要调试信息 |
ocularArgs | 可选的。您希望在命令中包含的任何参数都可以运行Ocular |
orgId | 您的ShiftLeft组织ID。可以在仪表板的帐户设置下找到 |
accessToken | 您的ShiftLeft访问Token。可以找到在仪表板下的帐户设置 |
一旦配置好插件,就可以运行构建来查看结果。
最后,提供了Ocular API documentation