算力驗證碼的嘗試
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 完全不在一個數量級。
對抗硬體的平行計算,大致有如下方案和思路:
- 硬體瓶頸
- 移植難度
- 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 總結
最後來對比下,算力驗證和傳統圖形驗證的區別。
驗證方式 | 驗證物件 | 使用者體驗 | 攔截假人 | |
---|---|---|---|---|
傳統驗證 | 影像識別 | 人腦 | 需要互動 | 部分攔截 |
算力驗證 | 問題解答 | 電腦 | 無感知 | 無法攔截 |
論效果,當然還是傳統的圖形驗證更好,但這是以犧牲使用者體驗為代價的。
硬體在不斷的發展,識圖軟體會越來越強大。而人腦始終是有限的,優勢會越來越小,最終導致驗證碼越來越複雜。
但是算力驗證則不同。硬體的發展,也會帶動瀏覽器的算力提升,最終只需將問題難度調高即可。
當然,安全防禦涉及的領域越多越好。每一個方案都不是無敵的,都只是為了增加一些攻擊成本而已。
所以算力驗證,結合傳統防禦方案,才能出發揮價值。
相關文章
- 給Django Admin新增驗證碼和多次登入嘗試限制2020-07-25Django
- 簡訊驗證碼測試項2018-07-31
- 驗證碼原理及驗證2020-06-21
- 驗證碼---js重新整理驗證碼2020-04-07JS
- 自動化測試中的驗證碼處理2024-11-01
- 常見驗證碼的弱點與驗證碼識別2020-08-19
- ACCESS 密碼驗證/文字驗證中的小坑2024-06-22密碼
- 坑爹的驗證碼。。2019-05-11
- 自動化測試時對驗證碼的處理2018-05-29
- 嘗試2024-04-23
- JavaScript驗證碼生成和驗證效果2018-05-21JavaScript
- 幽默:證明真人的新驗證碼2024-06-28
- 登入驗證碼生成kaptcha(輸入驗證碼)2024-06-27APT
- 驗證碼機制之驗證碼重複使用2020-11-02
- PHP驗證碼2023-03-10PHP
- Response驗證碼2020-10-23
- Laravel - 驗證碼2019-01-29Laravel
- 【驗證碼逆向專欄】某多多驗證碼逆向分析2024-11-29
- easy-captcha實現驗證碼驗證2024-07-15APT
- 如何解決WEB效能測試中的驗證碼問題2019-08-05Web
- 影片直播app原始碼,傳送驗證碼 驗證碼識別2023-10-11APP原始碼
- volatile的特性程式碼驗證2020-08-13
- Java驗證碼—ValidateCode的使用2018-05-27Java
- 計算機網路驗證性實驗2020-12-25計算機網路
- PHP 驗證身份證號碼2019-07-25PHP
- 福祿克的驗證測試和認證測試的區別2021-05-24
- 嘗試12024-04-23
- 嘗試 Leetcode2019-03-18LeetCode
- 嘗試Ghost2018-07-07
- 直播系統app原始碼,自定義九宮格,計算器佈局,驗證碼認證2022-12-05APP原始碼
- 直播平臺原始碼,flutter 自定義九宮格,計算器佈局,驗證碼認證2022-05-10原始碼Flutter
- 【驗證碼逆向專欄】某驗“初代”滑塊驗證碼逆向分析2023-02-03
- 手機號碼驗證方法(正則驗證)2022-03-14
- 黑客通過崩潰銀行的計算機嘗試入侵 SWIFT2018-06-12黑客計算機Swift
- 驗證碼處理在自動化測試中的應用2024-11-01
- selenium結合tenacity的retry實現驗證碼失敗重試2023-02-21
- 驗證碼識別2024-06-20
- 圖形驗證碼2023-03-10