震驚! 滑動驗證碼居然能這樣破解

最近西部世界第二季很火, 小編常常分不清誰是機器人誰是真人css

爲了區分人類和機器, 有我的發明了一種測試, 他叫圖靈~git

驗證碼就是一個典型的圖靈測試, 英文名 captcha, 全稱以下github

Completely Automated Public Turing test to tell Computers and Humans Apartweb

全自動區分計算機和人類的圖靈測試canvas

目前主流的驗證碼有api

  • 圖形驗證碼瀏覽器

  • 短信驗證碼微信

  • 滑塊驗證碼dom

  • 圖中點選驗證碼async

但如今的人工智能過於強大, 大部分扭曲的圖形驗證碼均可以被機器破解, 已經再也不是一個可靠的圖靈測試

並且圖形驗證碼體驗不好, 輸入困難

這時候, 滑動驗證碼出現了, 最具表明性的就是 Geetest(極驗)

滑動驗證碼對機器破解有兩大難點, 第一個是須要經過圖像識別知道滑到哪裏, 第二個是須要模仿人類作出滑動的手勢

滑動驗證碼 操做簡便, 破解難度大, 很快就流行起來了

WebDriver 標準

正好 W3C 近日發佈了一個瀏覽器自動化操做的標準, 名叫 WebDriver

https://www.w3.org/TR/webdriver1/

小編就拿極驗滑動驗證碼開刀, 和你們一塊兒感覺 WebDriver 的強大功能

先附上小編成果(本視頻20秒, 沒有聲音)

 

安裝 WebDriver

WebDriver 已是既定標準, 各大瀏覽器最新版都自然支持 WebDriver 協議, 使用門檻大大下降

小編本次使用的是支持度較好的瀏覽器 firefox

從 https://github.com/mozilla/geckodriver 官方倉庫中便可下載 firefox 的 diver

使用 selenium-webdriver

WebDriver 標準自己只是定義了一系列操做瀏覽器的 HTTP 協議, 但 selenium 已經爲咱們封裝成了 sdk, 直接調用函數便可

const webdriver = require('selenium-webdriver')

打開極驗demo頁面

咱們先用 WebDriver 實現一個最簡單的例子: 打開一個網頁

 

const webdriver = require('selenium-webdriver')

 

!async function() {

  // 新建一個 firefox 的 driver 實例

  let driver = await new webdriver.Builder().forBrowser('firefox').build()

 

  // 訪問極驗demo頁

  await driver.get('http://www.geetest.com/type/')

  console.log('success')

}()

運行這段代碼, 咱們能看到瀏覽器打開了極驗的 demo 頁

此時瀏覽器的地址欄是黃色的, 表示該瀏覽器被控制了, 就好像電視劇裏面被心靈控制的人眼睛是紅色的

選擇滑動行爲驗證

極驗第一個標籤是智能組合驗證, 第二個標籤纔是咱們要破解的滑動驗證, 所以須要先切換至滑動驗證

咱們經過 CSS 選擇器選出要點擊的標籤, 而後使用 WebDriver 點擊這個元素

await driver.findElement(webdriver.By.css('.products-content li:nth-child(2)')).click()

點擊驗證按鈕

來到了滑動行爲驗證區域, 接下來就要點擊驗證按鈕, 一樣也是先用 CSS 選擇器選出按鈕, 而後再點擊

await driver.findElement(webdriver.By.css('.geetest_radar_tip')).click()

區域截圖

到這一步, 滑動驗證碼已經彈出

咱們碰到了破解過程當中的第一個難點, 圖像識別

要知道拼圖須要滑動到哪裏, 先要知道完整圖片以及缺一塊的圖片, 放一塊兒對比, 才能知道滑塊須要滑動到哪裏

WebDriver 提供了兩種截圖方式, 一種是全屏截圖, 一種是元素截圖

這裏咱們僅須要獲取拼圖缺失的背景圖, 所以使用元素截圖

 

// 隱藏原圖再截圖

await driver.executeScript(`document.querySelector('.geetest_canvas_fullbg').style.display = 'none'`)

 

// 找到驗證碼背景圖元素, 是一個 canvas

const bgCanvas = await driver.findElement(webdriver.By.css('.geetest_canvas_bg'))

 

// 得到一個 base64 格式的 png 截圖

const bgPng = await bgCanvas.takeScreenshot()

找到滑動點

小編並不懂圖像識別, 爲了下降實現難度, 用了一個簡單取巧的方法

由於拼圖缺失的區域會有 陰影, 而陰影通常比較黑

所以咱們把題目從 尋找拼圖丟失區 改爲了 尋找比較黑的點

固然這個草率的規則正確率並不高, 但爲了demo演示已經足夠了

那怎麼定義 比較黑 呢? 最簡單的方法就是挨個讀取圖像中的像素

咱們把 R, G, B 三值相加, 數字越小就認爲越黑, 最黑的 rgb(0, 0, 0) 就是0

附上小編用的讀取像素庫 get-pixels

開始滑動

滑動操做使用了 WebDriver 中的 actions api, 能夠完成一系列操做, 好比鍵盤輸入, 鼠標移動, 點擊等

 

// 獲取拼圖滑塊按鈕

const button = await driver.findElement(webdriver.By.css('.geetest_slider_button'))

 

// 獲取按鈕位置等信息

const buttonRect = await button.getRect()

 

// 初始化 action

let actions = driver.actions({async: true})

 

// 把鼠標移動到滑塊上, 而後點擊

actions = actions.move({

  x: x + 10,

  y: y + 10,

  duration: 100

}).press()

 

// 花一秒鐘把滑塊拖動至拼圖缺失區, 鬆開鼠標

await actions.move({

  x: x + 10 + point.x - 5,

  y: y + 10,

  duration: 1000

}).release().perform()

寫完代碼, 執行, 看着拼圖順暢的向前滑動, 等待着奇蹟的發生

然而奇蹟並無發生, 只有一行黃字映入眼簾

怪物吃了拼圖, 請3秒後重試

重複好屢次, 咱們發現

即使徹底吻合, 也沒法繞過極驗的認證!

低估, 徹底的低估!

拼圖吻合只是必要條件, 破解的基礎門檻

真正的難點是拖動過程當中的 滑動軌跡!

模仿人類滑動

咱們再次想到了西部世界二, 最後的彩蛋威廉在世外山谷絕望的問艾米莉

威廉: Verify what?

艾米莉: Fidelity

Fidelity, 真實度

破解到了這一步, 可否模仿人類的滑動軌跡成了關鍵

咱們反覆不斷的調教滑動代碼, again and again

小編進行了屢次嘗試, 好比把 move action 分紅多段, 按照不一樣的速度進行拖動, 甚至加入各類隨機數, 但始終沒法經過極驗的軌跡檢查

最後小編仿照微信隨機紅包的方式, 先把要滑動的距離切成幾十份, 而且容許有負數

人在滑動拼圖的時候很容易出現, 滑過了再滑動回去的場景, 所以負數能夠增長 Fidelity

 

const count = 30 // 小編分紅30步進行滑動

const steps = getSteps(distance, count)

const totalDuration = 8000 // 一共耗時8秒, 慢才能充實軌跡~
 

_.reduce(steps, (actions, step) => {

  return actions.move({

    x: x + 10 + step,

    y: y + 10 + _.random(-5, 40), // 加上y軸隨機數

    duration: parseInt(_.random(totalDuration / count / 2, totalDuration / count * 2)) // 加上時長隨機數

  })

}, actions)

 

// 隨機拆成n份

function getRandomDistribution(total, count) {

  let item = total / count

  item = item + _.random(-item * 2, item * 3)

  item = parseInt(item)

  if (count === 1) {

    return [total]

  } else {

    return [item].concat(getRandomDistribution(total - item, count - 1))

  }

}

 

// 獲取每次滑動的X座標

function getSteps(total, count) {

  let distribution = getRandomDistribution(total, count)

  return _.map(distribution, (item, i) => {

    return _.sum(distribution.slice(0, i + 1))

  })

}

保存, 執行

小編放下鼠標, 端起桌上的馬克杯, 看着 WebDriver 再次控制瀏覽器

打開網頁, 點擊按鈕, 拖動滑塊, 滑塊曲折前行...

摩擦摩擦, 似魔鬼的步伐, 似老奶奶顫巍巍的手

終於, 極驗顯示出一個清爽綠色的橫幅, 彷彿在向咱們招手: 歡迎你, 人類

相關文章
相關標籤/搜索