在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