Google reCAPTCHA v2はどうするとスパム判定されるのか?

Google reCAPTCHA v3の続き。

https://qiita.com/kaibadash@github/items/dad2234ed0b47b373678

# Google reCAPTCHA v2

いわゆる 「ロボットではありません」です。

v3はそれが表示されないのですが、v3が新しくてよい、というわけではなく以下のような区別のようです。

- v2: スパムの判定をGoogleに任せる
- v3: スパムの判定を自分で行い、細かい制御を行う
    - ハンドリングしてあとから制御したりできるメリットが有る


# key, secretの取得

https://www.google.com/recaptcha/admin/create
ここからkeyとsecretを発行します。

# フロントエンド

```html
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
```

```html
<div class="g-recaptcha" data-sitekey="YOUR_KEY"></div>
```

以下のようなHTMLが生成されます。iframeですね。

```html
<iframe title="reCAPTCHA" src="https://www.google.com/recaptcha/api2/anchor?ar=1&amp;k=xxxxxxxx&amp;co=xxxxxxx.&amp;hl=ja&amp;v=xxxxxxx&amp;size=normal&amp;cb=xxxxxx" width="304" height="78" role="presentation" name="a-xxxxxx" frameborder="0" scrolling="no" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-top-navigation allow-modals allow-popups-to-escape-sandbox"></iframe>
```

# バックエンド

`g-recaptcha-response` というパラメータが渡されるので、それをバックエンドからGoogleのAPIをリクエストすることで検証します。
今回はAPIのレスポンスを表示しています。

```php
$captcha = $_POST['g-recaptcha-response'];

if (!isset($captcha)) {
  echo "g-recaptcha-response is not set";
  return;
}

$secret = "YOUR_SECRET";
$verifyResponse = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=".$secret."&response=".$captcha);
echo $verifyResponse;
```

# テストページ

今回、ターゲットになるフォーム
僕の実験サイトなので好きに触ってみてください。

https://pokosho.com/a/recaptcha/v2.html

# 試してみた

## 正常系

```json
{
  "success": true,
  "challenge_ts": "2022-02-23T08:36:24Z",
  "hostname": "pokosho.com"
}
```

## JSでクリックしてみる

iframeなので操作できません。
これができたら、Twitterをiframeで表示して、ようこそ @kaiba さん！ って出せてしまいますからね。

ちなみにCORSのエラーですので、自社サービス間とかは許可することで取得できますね。

```
Blocked a frame with origin "https://pokosho.com" from accessing a cross-origin frame.
```

## headless chrome(puppeteer)

JSで取得できないので座標で押してみる。
`.g-recaptcha` で座標を取得して、チェックボックス内をクリックします。
左端から右、下に30pxをクリックします。

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

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://pokosho.com/a/recaptcha/v2.html',
    {'timeout': 10000, 'waitUntil':'load'}
  );
  await page.waitForTimeout(1000); 
  
  const selector = ".g-recaptcha";
  const recaptchaPosition = await page.evaluate((selector) => {
    var el = document.querySelector(selector);
    var rect = el.getBoundingClientRect();
    return {
        height: rect.height,
        width: rect.width,
        x: rect.left,
        y: rect.top
    };
  }, selector);
  const margin = 30;
  await page.mouse.move(recaptchaPosition.x + margin, recaptchaPosition.y + margin, { steps: 1 });
  await page.mouse.click(recaptchaPosition.x + margin, recaptchaPosition.y + margin);
  
  await page.waitForTimeout(1000); 
  await page.click('#submit');
  const json = await page.content();
  console.log(json);
  await browser.close();
})();
```

```json
{
  "success": true,
  "challenge_ts": "2022-02-23T09:05:21Z",
  "hostname": "pokosho.com"
}
```
え… マジで…?

# まとめ

- Google reCAPTCHA v2「ロボットではありません」ならマウスの動きとかを見てロボットかどうか判定してくれる。そう考えてた時期が俺にもありました。
- Google reCAPTCHA v2のボタンはiframeなのでJSでは押しづらい。
- Puppeteerで座標を指定してクリックするとスパムにはならなかった。