JSとPuppeteerで「slider CAPTCHA」をバイパスする方法

次のトピックは[「slider CAPTCHA」をJSで解決する方法です](https://medium.com/@filipvitas/how-to-solve-geetest-slider-captcha-with-js-ac764c4e9905)

![](https://miro.medium.com/max/1100/1*8d-YL34nSPjKw0RQk8xuvg.jpeg)

スパムはウェブサイトの所有者にとって大きな問題です。その一方でCAPTCHAは私をひどくイライラさせるもので、ユーザーエクスペリエンスに悪影響を及ぼします。

CAPTCHAは最悪です。素直に認めましょう。人間かどうか検証する方法はたくさんありますが、どの方法も最悪です。


近年、BOTは日々賢くなっており、BOTからウェブサイトを守ることは困難です。ですが多くの空き時間と十分なリソースがあれば、ほぼどんなCAPTCHAでもバイパスできます。

Puppeteerが[reCAPTCHAを突破](https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-recaptcha)するためのプラグインがあります。 CAPTCHA突破サービスを提供している会社が存在します。たとえば2Captchaがあります。 [Puppeteerと2Captchaの使い方はこちら](https://medium.com/@jsoverson/bypassing-captchas-with-headless-chrome-93f294518337)です。

代替手段として「スライドして確認する」CAPTCHA を実装した ウェブサイトがあります。しかし、なぜ単純なスライダーCAPTCHAを使う人がいるのでしょうか？

![1_0LXnPGyW3gHt_tKZlGL4Bg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884773/813f01fc-30be-4d3c-8f4b-72b9fd2bf65c.png)
 
理由は次のとおりです。
 
- ほとんどのBOTはJSを実行しないため、BOTを止めることができる
- スライダーはユーザーフレンドリー
- スライダーにはモバイルユーザー向けの自然なスワイプアクションがある

したがって、スライダーは人間には使いやすくシンプルなものです。しかし、もっとスマートなBOTで簡単に解決することもできます。


いくつかのスライダーCAPTCHAをバイパスしてみましょう。

## スライドして送信
 
「スライドして送信」フォーム用のjQueryプラグインです。これはフォーム上でスパムを防止するためのCAPTCHAの代替手段です。
 
まず、入力欄にデータを入力します。このスライダーを動かすには次のことを行う必要があります。

- ハンドルの中心にマウスを置く
- マウスをクリックする
- マウスを動かす
- マウスを離す



完了。こんなに簡単なのです。
 
```js
const puppeteer = require('puppeteer' )

async function run () {
const browser = await puppeteer. launch({
      headless: false,
     defaultViewport: { width: 1366, height: 768 }
})
const page = await browser.newPage()

await page.goto('http://kthornbloom. com/slidetosubmit/')
await page.type('input[name="name"]', ‘Puppeteer Bot')
await page.type('input[name="email"]', 'js@automation.com')

let sliderElement = await page.$('.slide-submit' )
let slider = await sliderElement.boundingBox()

let sliderHandle = await page.$('.slide—submit—thumb' )
let handle = await sliderHandle.boundingBox()

await page.mouse.move(handle.x + handle.width / 2, handle.y + handle.height / 2)
await page.mouse.down()
await page.mouse.move(handle.x + slider.width, handle.y + handle.height / 2, { steps: 10 })
await page.mouse.up()

// success!

await browser.close()
}

run()
```

完了。こんなに簡単なのです。
 
## Dipbit登録スライダー
Dipbitはデジタル通貨取引サイトです。ログインページと登録ページの両方に「スライドして確認」要素があります。

![1_V72pGVtq63udlTwpzYPHuA.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884773/fe21d493-e5aa-3c27-b23a-20da40c8c7ef.png)


Dipbitは少し賢くできているので、Puppeteerの実行を隠すコードを追加する必要があります。

```js
const puppeteer = require('puppeteer')

async function run() {
    const browser = await puppeteer. launch({
    headless: false
    defaultViewport: { width: 1366, height: 768 }
})
const page = await browser.newPage()

await page.evaluateOnNewDocument ( () => {
    Object.definaProperty(navigator, 'webdriver', {
         get: () => false
     }}
})

await page.goto('https://www.dipbit.com/auth/login')
await page.type('#email', 'js@automation.com')
await page.type('#password’, 'password123')

let sliderElement = await page.$('.slidetounlock')
let slider = await sliderElement.boundingBox()

let sliderHandle = await page.$('.slidetounlock')
let handle = await sliderHandle. boundingBox()

await page.mouse.move(handle.x + handle.width / 2, handle.y + handle.height / 2)
await page.mouse.down()
await page.mouse.move(handle.x + slider.width, handle.y + handle.height / 2, { steps: 50 })
await page.mouse.up()

// success!

await browser.close()

}

run()

```
![1_JzqzvD-nuy--t7kvfGgu3g.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884773/94b19130-f1ec-d348-b6d2-767cd64f2ca8.gif)

## Taobao

タオバオはAlibabaが運営する中国のオンラインショッピングサイトです。Dipbitと同様に登録時にスライダーが表示されます。唯一の違いは登録フォームがiframe内にあることです。しかしPuppeteerにとってそれは問題ではありません。

```js
const puppeteer = require('puppeteer' )

async function run() {
        const browser = await puppeteer. Launch({
        headless: false,
        defaultViewport: { width: 1366, height: 768 }
})
const page = await browser.newPage()

await page.evaluateOnNewDocument(() => {
        Object.defineProperty(navigator, 'webdriver', {
              get: () => false
         })
 ])

await page.goto('https://world.taobao.com/markets/all/sea/register' )

let frame = page.frames()[1]
await frame.waitForSelector('.nc_iconfont.btn_slide')

const sliderElement = await frame.$('.slidetounlock')
const slider = await sliderElement.boundingBox()

const sliderHandle = await frame.$('.nc_iconfont.btn_slide')
const handle = await sliderHandle. boundingBox()
await page.mouse.move(handle.x + handle.width / 2, handle.y + handle.height / 2)
await page.mouse.down()
await page.mouse.move(handle.x + slider.width, handle.y + handle.height / 2, { steps: 50 })
await page.mouse.up()

// success!

await browser.close()

}

run()
```

![1_zQ7Sa5tr295jOxFLWaW1ng.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884773/50f7d18b-36c4-c78d-107b-e322fb7fd5cd.gif)

## パズル付きスライダーCAPTCHA

「スライドして確認する」Vue コンポーネントに遭遇しました。
人間にとっては簡単で、BOTにとっては難しいはずのものです。


この検証メソッドは画像を取得し、2つのキャンバスと1つのスライダーを作成します。パズルのピースで初期画像をレンダリングします。ユーザーがスライダーを動かすと、パズルのピースが一致します。 2つのピースが一致したらユーザーはスライダーを放す必要があります。これで検証は終了します。

このCAPTCHA はパズルの位置をランダム化してBOTを混乱させます。
 
![1_GMcgXCSRkGW7GORpTh543Q.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884773/ce191614-c6d8-dc56-5192-aaddcf30bc29.png)

ここではMLやOCRのような派手なことはしたくなかったので、そのスライダーを少しずつ動かして、結果の画像と最初の画像を比較します。

[rembrandt.js library](https://github.com/imgly/rembrandt)ライブラリを使用して画像を比較します。差が最も小さい画像を見つけたら、スライダーを最適な位置に移動し、マウスを離します。

```js
const puppeteer = require ('puppeteer')
const Rembrandt = ('rembrandt')

async function run () {
         const browser = await puppeteer. Launch({
             headless: false,
             defaultViewport: { width: 1366, height: 768 }
          })
          const page = await browser.newPage()

          let originalImage = ' '

          await page.setRequestInterception(true)
          page.on('request', request => request.continue())
          page.on('response', async response => {
                  if (response. request().resourceType() === 'image')
                       originalImage = await response.buffer().catch(() => {})
          })

          await page.goto('https://monoplasty.github. io/vue—monop lasty-slide-verify/')

          const sliderElement = await page.$('.slide-verify-slider'
          const slider = await sliderElement.boundingBox()

          const sliderHandle = await page.$('.slide-verify-slider—mask-item' )
          const handle = await sliderHandle. boundingBox()

          let currentPosition = @
          let bestSlider = {
               position: 0,
               difference: 100
          }

          await page.mouse.move(handle.x + handle.width / 2, handle.y + handle.height / 2)
          await page.mouse.down()

          while (currentPosition < slider.width - handle.width / 2) {

                 await page.mouse.move(
                           handle.x + currentPosition,
                           handle.y + handle.height / 2 + Math.random() * 10 - 5
                  )

                  let sliderContainer = await page.$('.slide-verify')
                  let sliderImage = await sliderContainer.screenshot()

                  const rembrandt = new Rembrandt ({
                         imageA: originalImage,
                         imageB: sliderImage,
                         thresholdType: Rembrandt. THRESHOLD_PERCENT
                  })

                  let result = await rembrandt.compare()
                  let difference = result.percentageDifference * 100

                  if (difference < bestSlider.difference) {
                      bestSlider.difference = difference
                      bestSlider.position = currentPosition
                  }     

                  currentPosition += 5

          }

          await page.mouse.move(handle.x + bestSlider.position, handle.y + handle.height / 2, { steps: 10 })
          await page.mouse.up()

         // success

         await browser.close()

}

run()
```

クールな部分を1つ逃した場合。実際のユーザーのマウスの動きをエミュレートするために、Y軸のスライダーの動きをランダム化します😎

```js
await page.mouse.move(
     handle.x + currentPosition,
     handle.y + handle.height / 2 + Math.random() * 10 - 5
)
```

![1_Az5o6MzqAHMzlwTa2_FfnQ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884773/30bc253d-7b6a-5f9d-cc63-e837c1db4816.png)
![1_JymxrPoSiM6uSqegnRbH6Q.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884773/c03cf81d-00b6-c9c5-c18f-130f22da5c7f.png)


すべてのコード例はgithub repoにあるので、必要な要素を自由にコピーしてください。


## 結論
ユーザーエクスペリエンスを向上させるためにCAPTCHAを簡単にバイパスできるようにするか、あるいはBOTから積極的に保護してユーザーエクスペリエンスを低下させるかは、ウェブサイトオーナーがよく悩まされるジレンマです。
ウェブサイトとBOTの間の戦争に終わりはありません。ウェブサイトがどんな検証方法を採用しようとも、誰かがそれを回避する方法を見つけ出すのは時間の問題です。

それにもかかわらず、これは教育目的のためだけのものであり、責任を持ってPuppeteerを使用してください。

同じトピックに関する次のストーリー - [JSを使ってGeeTestの「スライダーCAPTCHA」をどのように突破するか](https://medium.com/@filipvitas/how-to-solve-geetest-slider-captcha-with-js-ac764c4e9905)

読んでくれてありがとう！記事が気に入っていただけた場合1回、2回、または50回拍手してください。
何か質問がある場合は、下にコメントを残すか、[Twitter](https://twitter.com/2captcha)で話しかけてください。