<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/tips/11704

            0x00 前言


            驗證碼的初衷是人機識別。不過大多時候,只是用來增加一些時間成本,降低頻率而已。

            如果僅僅是為了消耗時間,能否不用圖片,完全用程序來實現?

            0x01 如何消耗時間


            先來思考一個問題:寫一個能消耗對方時間的程序。

            消耗時間還不簡單,休眠一下就可以了:

            #!cpp
            Sleep(1000)
            

            這確實消耗了時間,但并沒有消耗 CPU。如果開了變速齒輪,瞬間就能完成。

            要消耗 CPU 也不難,寫一個大循環就可以了:

            #!cpp
            for i = 0 to 1000000000
            end
            

            不過這和 Sleep 并無本質區別。對方究竟有沒有運行,我們從何得知?

            所以,我們需要一個返回結果 —— 只有完整運行才有正確答案。

            #!cpp
            result = 0
            for i = 0 to 1000000000
                result = result + i
            end
            return result
            

            通過返回的結果,我們就能判斷對方是否完整的運行了程序。

            不過上面這個問題,畢竟還是 too simple。小學生都知道,用數列公式就可以直接算出結果,根本不用花時間去跑循環。

            那么有什么算法,是無法用公式推測的?

            顯然,單向散列函數就是。例如一個經典的問題:

            #!cpp
            MD5(X) == X
            

            就無法用公式來解決了。要找出答案,只能一個個窮舉過去,從而花費大量時間。

            但對于驗證者,只需將收到的答案,計算一次就可判斷對錯,因此可輕易校驗。

            這就是 PoW(Proof-of-Work),用來證明對方投入工作的方法。

            當然,上面的例子太困難了,而且答案可以重復使用,所以還需改進。例如:

            #!cpp
            MD5("問題" + X) == "0000......"
            

            我們只要求散列結果的前幾位是 0 就可以。這樣位數越小,答案就越容易找到。同時增加一個鹽值,讓答案不能重復使用。

            事實上,比特幣就用到了類似的方式,使用了 SHA-256 作為散列函數。這樣只能窮舉,無法用更快的方法投機取巧,體現了挖礦工作的價值。

            用散列函數實現的 PoW,就叫 Hashcash

            0x02 傳統應用


            Hashcash 早不是新鮮事,曾經在反垃圾郵件中就已使用。

            例如用戶寫完郵件時,客戶端將「收件地址 + 郵件內容」作為 Salt,然后計算符合條件的答案:

            #!cpp
            Hash(X, Salt) == "000000..."
            

            最后將找到的 X 附加在郵件中并發送。服務端收到后,即可鑒定發送這封郵件,是否花費了計算工作。

            對于正常用戶來說,額外的幾秒并不影響使用;但對于制造垃圾郵件的人,就大幅增加了成本。

            傳統策略,大多通過 IP、賬號等限制。攻擊者可以用大量的馬甲和代理,來繞過這些限制。

            而使用了 PoW,就把瓶頸限制在硬件上 —— 計算有多快,操作才能多快。

            0x03 Web 應用


            同樣的,Hashcash 也能用于 Web。例如論壇,可在發帖時計算:

            #!cpp
            Hash(X, 帖子內容) == "000000..."
            

            不過,不同于郵件客戶端可在后臺自動計算,發帖時如果卡上好幾秒,將會大幅降低用戶體驗。

            因此不能選擇 帖子內容、標題 等這些用戶輸入作為鹽值。而是用傳統驗證碼的方式,后端下發一個隨機數。

            前端使用這個隨機數作為鹽值 —— 這樣頁面打開時,就可以開始計算了。

            #!cpp
            # 后端 - 分配
            session["pow_code"] = rand()
            
            # 前端 - 挖礦
            while Hash(X, pow_code) == "000000..."
                X = X + 1
            end
            

            我們選擇一個適中的難度,例如 10 秒。通過多線程,還可以更快的完成計算任務,同時不影響用戶體驗。

            正常情況下,用戶發帖前就已計算完成。提交時,將其附帶上。

            如果提交時還未算出,則等待計算完成。(發帖太快,有灌水嫌疑)

            #!cpp
            # 前端 - 提交
            wait X
            submit(..., X)
            
            # 后端 - 校驗
            if Hash(X, session["pow_code"]) == "000000..."
                ok
            else
                fail
            end
            

            這樣,就實現了一個「測試機器算力」的驗證碼。

            目前已有提供 hashcash 第三方驗證的網站,例如 hashcash.io

            0x04 Web 性能


            當然在 Web 中使用,性能也是一大問題。如果 10 秒的腳本計算,用本地程序只需 1 秒,那攻擊者就可以使用本地版的外掛了。

            好在如今有 asm.js,可接近原生性能;對于較老的瀏覽器,也可以使用 Flash 作后補。在上一篇文章 0x08 節 中已詳細講解。

            如果算力實在不夠,也可以使用后備方案 —— 傳統圖形驗證碼。

            這樣,高性能用戶可享受更好的體驗,低性能用戶也能保障基本功能。

            這也算是鼓勵大家使用現代瀏覽器吧:)

            0x05 致命缺陷


            不過,語言上的性能差距還是有限的,外掛不會糾結于此,而是使用更強力的武器 —— GPU。

            Hashcash 的本質就是跑 hash,這是 GPU 最擅長的。例如著名的 oclHashcat,和 CPU 完全不在一個數量級。

            對抗硬件的并行計算,大致有如下方案和思路:

            前 3 個在上一篇文章 0x09 節 提到了,下面討論一些不同的。

            0x06 以暴制暴


            如果我們也能在 Web 中調用顯卡計算,那 GPU 版的外掛就毫無優勢了。

            不過,這個想法似乎有些遙遠。盡管目前主流瀏覽器都支持 WebGL,但都只局限于渲染加速上,并未提供通用計算接口。

            當然,也可以通過一些 hack 的方式,例如曾有人嘗試用 WebGL 挖比特幣,但效率并不高。

            如果未來 WebCL 成為標準,或許還能考慮。

            0x07 代碼混淆


            上回討論慢加密時,曾提到為什么要性能優化。因為自己創造加密算法是不推薦的,所以得優化現有的算法。

            不過,相比賬號安全,驗證碼的要求則低得多,而且隨時可以更換算法,因此不妨自己來創造一個。

            自創的加密算法,強度顯然沒有保障。但我們可以從「隱蔽性」上著手 —— 將代碼混淆到難以讀懂,這時,考驗對方的則是逆向能力了。

            這和之前寫的《對抗假人 —— 前后端結合的 WAF》有點類似。不過,如果混淆能做到足夠好,還需要 PoW 機制嗎?

            有勝于無。因為瀏覽器指紋、用戶行為等信息,都是可以通過沙盒模擬的。而工作量計算,必須消耗硬件資源,才能得出結果。

            因此,使用了 PoW 就能增加攻擊者一些硬件成本。

            0x08 串行模式


            Hashcash 的原理,決定了它是可以并行計算的。有什么樣的算法,是無法并行計算的?

            如果每次計算都依賴上次結果,就無法并行了。例如之前討論的 slowhash:

            #!cpp
            function slowhash(x)
                for i = 0 to 1000000000
                    x = hash(x)
                end
                return x
            end
            

            這種串行的計算,自然是無法拆分的。但能用到 PoW 上嗎?

            顯然不行!因為 PoW 雖然計算困難,但得 容易鑒定。而這種方式,鑒定時也得重復算一遍,成本太大了。

            但在現實中,只要設計得當,還是可以嘗試的 —— 我們使用類似 UGC 的模式,讓用戶來貢獻算力!

            首先需要一個訪問量較大的網站,在其中悄悄放置一個腳本。利用在線的用戶,來生成問題和答案。

            #!cpp
            # 隱蔽的腳本
            Q = rand()
            A = slowhash(Q)
            
            submit(Q, A)
            

            當然,這項工作必須足夠隱蔽,防止被好奇的用戶發現,提交錯誤的答案。

            當后端題庫有一定的積累時,就可以使用驗證碼的模式了。用戶訪問時,后端從題庫中隨機抽取一個問題,安排給前端計算:

            #!cpp
            # 后端 - 分配問題
            Q = select_key_from_db()
            session["pow_ques"] = Q
            
            # 前端 - 計算問題
            A = slowhash(Q)
            

            用戶提交時,后端無需任何計算,直接通過查表,即可判斷答案是否正確:

            #!cpp
            # 前端 - 提交
            submit(..., A)
            
            # 后端 - 鑒定
            Q = session["pow_ques"]
            if A == db[Q]
                ok
            else
                fail
            end
            

            使用預先計算的方式,避免了繁重的鑒定工作。同時,把計算交給用戶來完成,可大幅節省硬件成本。

            當然,這種模式還有很多需要考慮的地方,這里只是介紹下基本思路,以后再詳細討論。

            相比 hashcash 題解時間有一定的隨機性,slowhash 的時間是固定的,因此難度更可控。

            0x09 演示


            因為 Hashcash 比較簡單,所以這里演示一個 md5 版的,使用 asm.js 和 flash 實現,并對算法做了一定優化。

            https://github.com/EtherDream/proof-of-work-hashcash

            如果想看詳細的算力速度,可以查看這個 Demo:

            http://www.etherdream.com/FunnyScript/hashcash/js/test.html

            看起來好像不慢,不過對比 GPU 的速度 就相形見絀了。所以,使用經典算法的 Hashcash,簡直就是不堪一擊的。

            至于串行模式的 PoW,涉及到很多策略和數據積累,本文就演示了,下回單獨討論。

            0x0A 總結


            最后來對比下,算力驗證和傳統圖形驗證的區別。

            驗證方式 驗證對象 用戶體驗 攔截假人
            傳統驗證 圖像識別 人腦 需要交互 部分攔截
            算力驗證 問題解答 電腦 無感知 無法攔截

            論效果,當然還是傳統的圖形驗證更好,但這是以犧牲用戶體驗為代價的。

            硬件在不斷的發展,識圖軟件會越來越強大。而人腦始終是有限的,優勢會越來越小,最終導致驗證碼越來越復雜。

            但是算力驗證則不同。硬件的發展,也會帶動瀏覽器的算力提升,最終只需將問題難度調高即可。

            當然,安全防御涉及的領域越多越好。每一個方案都不是無敵的,都只是為了增加一些攻擊成本而已。

            所以算力驗證,結合傳統防御方案,才能出發揮價值。

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线