1. 程式人生 > >常見驗證碼的弱點與驗證碼識別

常見驗證碼的弱點與驗證碼識別

http://drops.wooyun.org/tips/141

0x00 簡介

驗證碼作為一種輔助安全手段在Web安全中有著特殊的地位,驗證碼安全和web應用中的眾多漏洞相比似乎微不足道,但是千里之堤毀於蟻穴,有些時候如果能繞過驗證碼,則可以把手動變為自動,對於Web安全檢測有很大的幫助。

全自動區分計算機和人類的圖靈測試(英語:Completely Automated Public Turing test to tell Computers and Humans Apart,簡稱CAPTCHA),俗稱驗證碼,是一種區分使用者是計算機和人的公共全自動程式。在CAPTCHA測試中,作為伺服器的計算機會自動生成一個問題由使用者來解答。這個問題可以由計算機生成並評判,但是必須只有人類才能解答。由於計算機無法解答CAPTCHA的問題,所以回答出問題的使用者就可以被認為是人類。(from wikipedia)

大部分驗證碼的設計者都不知道為什麼要用到驗證碼,或者對於如何檢驗驗證碼的強度沒有任何概念。大多數驗證碼在實現的時候只是把文字印到背景稍微複雜點的圖片上就完事了,程式設計師沒有從根本上了解驗證碼的設計理念。

驗證碼的形式多種多樣,先介紹最簡單的純文字驗證碼。

純文字驗證碼

純文字,輸出具有固定格式,數量有限,例如:

•1+1=?

•本論壇的域名是?

•今天是星期幾?

•複雜點的數學運算

這種驗證碼並不符合驗證碼的定義,因為只有自動生成的問題才能用做驗證碼,這種文字驗證碼都是從題庫裡選擇出來的,數量有限。破解方式也很簡單,多重新整理幾次,建立題庫和對應的答案,用正則從網頁裡抓取問題,尋找匹配的答案後破解。也有些用隨機生成的數學公式,比如 隨機數 [+-*/]隨機運算子 隨機數=?,小學生水平的程式設計師也可以搞定……

這種驗證碼也不是一無是處,對於很多見到表單就來一發的spam bot來說,實在沒必要單獨為了一個網站下那麼大功夫。對於鐵了心要在你的網站大量灌水的人,這種驗證碼和沒有一樣。

下面講的是驗證碼中的重點,圖形驗證碼。

圖形驗證碼

先來說一下基礎:

識別圖形驗證碼可以說是計算機科學裡的一項重要課題,涉及到計算機圖形學,機器學習,機器視覺,人工智慧等等高深領域……

簡單地說,計算機圖形學的主要研究內容就是研究如何在計算機中表示圖形、以及利用計算機進行圖形的計算、處理和顯示的相關原理與演算法。圖形通常由點、線、面、體等幾何元素和灰度、色彩、線型、線寬等非幾何屬性組成。計算機涉及到的幾何圖形處理一般有 2維到n維圖形處理,邊界區分,面積計算,體積計算,扭曲變形校正。對於顏色則有色彩空間的計算與轉換,圖形上色,陰影,色差處理等等。

在破解驗證碼中需要用到的知識一般是 畫素,線,面等基本2維圖形元素的處理和色差分析。常見工具為:

•支援向量機(SVM)

•OpenCV

•影象處理軟體(Photoshop,Gimp…)

•Python Image Library

支援向量機SVM是一個機器學習領域裡常用到的分類器,可以對圖形進行邊界區分,不過需要的背景知識太高深。

OpenCV是一個很常用的計算機影象處理和機器視覺庫,一般用於人臉識別,跟蹤移動物體等等,對這方面有興趣的可以研究一下

PS,GIMP就不說了,說多了都是淚啊……

Python Image Library是pyhon裡面帶的一個圖形處理庫,功能比較強大,是我們的首選。

20130605190615_98443.png

SVM影象邊界區分

20130605192502_65273.png

SVM原理,把資料對映到高維空間,然後尋找能夠分割的超平面

識別驗證碼需要充分利用圖片中的資訊,才能把驗證碼的文字和背景部分分離,一張典型的jpeg圖片,每個畫素都可以放在一個5維的空間裡,這5個維度分別是,X,Y,R,G,B,也就是畫素的座標和顏色,在計算機圖形學中,有很多種色彩空間,最常用的比如RGB,印刷用的CYMK,還有比較少見的HSL或者HSV,每種色彩空間的維度都不一樣,但是可以通過公式互相轉換。

20130605193040_40334.png

RGB色彩空間構成的立方體,每個維度代表一種顏色

20130605193155_34999.png

HSL(色相飽和度)色彩空間構成的錐體,可以參考:

瞭解到色彩空間的原理,就可以用在該空間適用的公式來進行畫素的色差判斷,比如RGB空間裡判斷兩個點的色差可以用3維空間中兩座標求距離的公式:

distance=sqrt[(r1-r2)^2+(g1-g2)^2+(b1-b2)^2]

更加直觀的圖片,大家感受一下:

20130605194036_39590.png

隨便把一張圖片的每個畫素都對映到RGB色彩空間裡就能獲得一個這樣的立方體。

通過對畫素顏色進行統計和區分,可以獲得圖片的顏色分佈,在驗證碼中,一般來說使用近似顏色最多的畫素都是背景,最少的一般為干擾點,干擾線和需要識別文字本身。

對於在RGB空間中不好區分顏色,可以把色彩空間轉換為HSV或HSL:

20130605194730_56543.png

0x01 驗證碼識別的原理和過程

第一步:    二值化

所謂二值化就是把不需要的資訊通通去除,比如背景,干擾線,干擾畫素等等,只剩下需要識別的文字,讓圖片變成2進位制點陣。

20130605195023_13222.png

第二步: 文字分割

為了能識別出字元,需要對要識別的文字圖圖片進行分割,把每個字元作為單獨的一個圖片看待。

20130605200952_55267.png

第三步:標準化

對於部分特殊的驗證碼,需要對分割後的圖片進行標準化處理,也就是說盡量把每個相同的字元都變成一樣的格式,減少隨機的程度

最簡單的比如旋轉還原,複雜點的比如扭曲還原等等

第四步:識別

這一步可以用很多種方法,最簡單的就是模板對比,對每個出現過的字元進行處理後把點陣變成字串,標明是什麼字元後,通過字串對比來判斷相似度。

在文章的後半部分會詳細解釋每步的各種演算法

20130605220655_80781.png

二值化演算法

對於大部分彩色驗證碼,通過判斷色差和畫素分佈都能準確的把文字和背景分離出來,通過PS等工具把圖片開啟,用RGB探針對文字和背景圖的顏色分別測試,在測試多張圖片後,很容易可以發現文字和背景圖的RGB差距總是大於一個固定的閾值,即使每次圖片的文字和背景顏色都會變化,比如:

新浪和discuz的驗證碼

20130607213048_17172.jpg20130607213105_77426.jpg

通過對文字部分和干擾部分取樣可以發現,文字部分的R、G值一般在100左右,B值接近255,但是背景干擾的R、G值則大大高於文字部分,接近200,比較接近文字輪廓部分的畫素的RG值也在150以上。通過程式遍歷一遍畫素就可以完全去掉背景。

20130607213146_11252.jpg

Discuz的驗證碼同理

對於一些和文字顏色相同但是較為分散和單一的干擾畫素點,我們可以用判斷相鄰畫素的方法,對於每個點判斷該點和相鄰8個點的色差,若色差大於某個值,則+1,如果周圍有超過6個點的色差都比較大,說明這個點是噪點。對於影象邊界的一圈畫素,周圍沒有8個畫素,則統統清除,反正文字都在圖片的中間位置。

如下圖:假如當前畫素的座標是x,y  圖形座標系的原點是影象的左上角

20130607213238_98076.jpg

對於1個畫素粗細的干擾線,在字元為2個畫素以上的時候,可以用去噪點演算法作為濾鏡,多執行幾次,就可以完美的把細干擾線去掉。

對於畫素數比干擾點稍大的干擾色塊,可以採用的演算法有:

油漆桶演算法(又叫種子填充演算法,Floodfill)

種子填充演算法可以方便的計算出任意色塊的面積,對於沒有粘連字元或者粘連但是字元每個顏色不一樣的驗證碼來說,去除干擾色塊的效果很好,你只需要大概計算一下最小的和最大的字元平均佔多少畫素,然後把這段區間之外畫素數的色塊排除掉即可。

Recursive_Flood_Fill_4_%28aka%29.gif                    Recursive_Flood_Fill_8_%28aka%29.gif

上下左右4個方向填充還有8個方向填充的不同

判斷顏色分佈:

對於大多數彩色驗證碼來說,文字基本在圖片中心的位置,每個字元本身的顏色是一樣的,也就是說對於文字來說,同一種顏色基本都集中在一個固定的區域範圍內,通過統計圖片中的畫素,按近似顏色分組,同時分析每個顏色組在圖片中的分佈範圍,假如說有一種顏色大部分畫素都在圖片邊緣,那麼這個顏色肯定不屬於要識別的字元,可以去掉。

對於干擾線,並沒有一種十分有效的方式能完全去除並且不影響到文字,不過如果能夠成功分割字元的話,少量干擾線對於識別率影響不大。

字元分割演算法

破解驗證碼的重點和難點就在於能否成功分割字元,這一點也是機器視覺裡的一道難題,對物件的識別能力。對於顏色相同又完全粘連的字元,比如google的驗證碼,目前是沒法做到5%以上的識別率的。不過google的驗證碼基本上人類也只有30%的識別率

對於字元之間完全沒有粘連的驗證碼,比如這個->_-> captcha.php

分割起來是非常的容易,用最基本的掃描線法就可以分割,比如從最左側開始從上到下(y=0---|||||y=n)掃描,如果沒有遇到任何文字的畫素,就則往右一個畫素然後再掃描,如果遇到有文字畫素存在,就記錄當前橫座標,繼續向右掃,突然沒有文字畫素的時候,就說明到了兩個字元直接的空白部分,重複這個步驟再橫向掃描就能找到每個字元最邊緣4個畫素的位置,然後可以用PIL內建的crop功能把單獨的字元摳出來。

對於有少許粘連但是隻是在字元邊角的地方重疊幾個畫素的驗證碼,可以用垂直畫素直方圖的統計方法分割。如下圖:

20130607230457_19520.png

圖上半部分是垂直畫素直方圖的一種直觀展示,假如圖片寬度為100畫素,則把圖片切割為100個1畫素的豎線,下面的紅色部分為當前x座標上所有黑色畫素的總和。這麼一來可以很容易的通過直方圖的波峰波谷把4個字母分割開。圖片的下半部分是掃描線分隔法,因為干擾線和字元旋轉的存在,只有M和5直接才出現了連續的空白部分。

除了垂直畫素直方圖,還可以從不同的角度進行斜線方向的畫素數投影,這種方式對於每次全體字元都隨機向一個角度旋轉的驗證碼效果很好。對於每次字元大小和數量都一樣的驗證碼還可以用平均分割法,也就是直接先把中間的文字部分整體切出來,然後按寬度平均分成幾份,這種方式對字元粘連比較多用其他方式不好分割的驗證碼很有用,之前的megaupload的3位字母驗證碼就是通過這種方式成功分割的。

另外對於彩色的驗證碼,還可以用顏色分割,比如12306的:

20130607233647_17982.png

12306的驗證碼,每個字元顏色都不一樣,真是省事啊。

作為驗證碼識別裡的難點,分割字元還有很多種演算法,包括筆畫分析曲線角度分析等等,不過即便如此,對粘連的比較厲害的字元還是很難成功的。

標準化

標準化的意思是指對於同一個字元,儘可能讓每次識別前的樣本都一致,以提高識別率。而驗證碼設計者則會用隨機旋轉,隨機扭曲還有隨機字型大小的方式防止字元被簡單方法識別。

還原隨機旋轉的字元一般採用的是旋轉卡殼演算法:

20130607235719_94258.png

此演算法非常簡單,對一張圖片左右各旋轉30度的範圍,每次1度,旋轉後用掃描線法判斷字元的寬度,對於標準的長方形字型,在完全垂直的時候肯定是寬度最窄的。嗯?納尼?上面的圖是中間的最窄?好像的確是這樣,不過只要每次旋轉後的結果都一樣,對於識別率不會有影響。

扭曲還原的演算法比較蛋疼,效果也不怎麼樣(其實我不會),不過如果識別演算法好的話,對扭曲的字元只要人能認出來,識別率也可以達到接近人類的水準。

還有一些常用到的演算法,對於提高識別率和減少樣本數量有一定幫助:

骨架細化:腐蝕演算法

20130608000722_87311.png

腐蝕演算法的原理有點像剝洋蔥,從最外層沿著最外面的一層畫素一圈一圈的去掉,直到裡面只剩下一層畫素為止。腐蝕演算法裡面需要用到另一個演算法,叫做凸包演算法,用來找一堆畫素點裡面最外圍的一層。

最後就是把字元變成統一大小,一般而言是把全部字元都縮到和驗證碼裡出現過的最小的字元一個大小。

詳情請自行google……

20130608001005_74310.png

分割演算法差不多就到這裡了,都是一些比較基礎的內容。下面是最終的識別。

0x02 識別

其實到了這一步,單獨的字元已經分離出來了,可以訓練tesseract ocr來識別了,樣本數量多的話,識別率也是很高的。不過在這裡還是要講一下,如何自己來實現識別過程。

第一步,樣本現在應該已經是一個矩陣的形式了,有畫素的地方是1,背景是0,先肉眼識別一下,然後把這個矩陣轉換為字串,建立一個鍵值對,標明這串字串是什麼字元。之後就只需要多蒐集幾個同樣字元的不同字串變形,這就是製作模板的過程,。

蒐集了足夠多的模板後,就可以開始識別了,最簡單的方法:漢明距離,但是如果字元有少許扭曲的話,識別率會低的離譜。對比近似字串用的最多一般是 編輯距離演算法(Levenshtein Distance),具體請自己google。

兩種演算法的差別在於,對同樣兩個字串對比10010101和10101010,漢明距離是6,但是編輯距離是2。

最後一種最NB的識別演算法,就是神經網路,神經網路是一種模擬動物神經元工作模式的演算法,神經網路有多種不同的結構,但是基本架構分為輸入層,隱含層和輸出層,輸入和輸出均為二進位制。

20130608003739_59697.png

對於驗證碼識別來說,輸入和輸出節點不宜過多,因為多了很慢……所以如果樣本矩陣為20x20 400個畫素的話,需要對應的也要有400個輸入節點,因此我們需要對整個矩陣提取特徵值,比如先橫向每兩個數字XOR一下,然後再豎向每兩個數字XOR。

Python有很多封裝好的神經網路庫,你所需要的只是把特徵值輸入神經網路,再告訴他你給他的是什麼(字元),這樣多喂幾次之後,也就是訓練的過程,隨著訓練的進行,神經網路的內部結構會改變,逐漸向正確的答案靠攏。神經網路的優勢是,對於扭曲的字元識別成功率非常高。另外神經網路在資訊保安中還可以起到很多其他作用,比如識別惡意程式碼等等。

動畫驗證碼

有些不甘寂寞的程式設計師又玩出了些新花樣,比如各種GIF甚至flv格式的動畫驗證碼,下面我來分析一下騰訊安全中心的GIF驗證碼。

20130608005708_49515.gif

晃來晃去的看似很難,放慢100倍一幀一幀再看看?

20130608010202_83349.gif

基本上每幀都有一個字元和其他的分開,用最簡單的掃描法就能分割出來。

剩下的就很輕鬆了,旋轉還原之後,先填充內部空白,縮小細化之後做成模板對比,識別率怎麼也得有90%了。

原本一張圖就能搞定的事情,偏偏給了我們8張圖,而且每張圖還有一點區別,平白無故增大了很多資訊量。

另外就是一些所謂的高使用者體驗的驗證碼,比如freebuf的:

20130608010939_57828.png

拖動解鎖按鈕會觸發執行一段js,生成一串隨機字串,ajax給後端程式判斷。

破解方式就當留給大家的思考題了,假如我想刷評論的話,怎麼辦。

還有就是聲音驗證碼的識別,現在很多驗證碼為了提高使用者體驗和照顧視覺障礙的使用者,都有聲音驗證碼,一般來說是機器生成一段讀數字的語音。但是在這方面上很多程式設計師都偷懶了,預先找了10個數字的聲音錄音,然後生成的時候把他們隨機拼到一起,結果就是這樣:

20130608011512_96225.png

前3秒為語音提示,後面的是數字,有沒有發現什麼?

聲音也是可以做成模板的哦

最後就是應該怎麼樣去設計驗證碼

•整體效果


•字元數量一定範圍內隨機


•字型大小一定範圍內隨機


•波浪扭曲(角度方向一定範圍內隨機)


•防識別


•不要過度依賴防識別技術


•不要使用過多字符集-使用者體驗差


•防分割 


•重疊粘連比干擾線效果好


•備用計劃


•同樣強度完全不同的一套驗證碼

附件新增一個破解驗證碼的例項包括程式大家自行研究吧:驗證碼識別