ブログに戻る

フォロー&ご登録

英語のみで利用可能

このページは現在英語でのみ閲覧可能です。ご不便をおかけして申し訳ございませんが、しばらくしてからこのページに戻ってください。

自動化と不正な自動化の擁護

Fastly Security Research Team

Fastly Security Research Team, Fastly

Simran Khalsa

Staff Security Researcher

アプリケーションをインターネット上で運用している場合、不正な自動化プログラムの脅威にさらされています。こうした脅威には、コンテンツスクレイピングやクレデンシャルスタッフィング、アプリケーション DDoS、Web フォームの悪用、トークンの推測など、幅広いタイプの攻撃があります。これらのタスクを自動化するソフトウェアはボットと見なされ、自動化されたツールの悪質な使用は自動化された脅威に分類されます。このようなタイプの不正な自動化プログラムは、サイバー犯罪組織を筆頭に、さまざまな攻撃者によって使用されることが多いです。また、個人ユーザーが私的利益を求めて (eBay でのスナイプ入札など) 自動化プログラムを利用する場合もあります。

ビジネスに影響が及ぶ前に、不正な自動化プログラムの脅威を理解して対策を講じるには、攻撃者の手口や手法を把握することが不可欠です。自動化されたシンプルな攻撃と複雑な攻撃の両方をエミュレートすることは、これらの攻撃に対する理解を深め、セキュリティ機能が適切に機能することを確認する上で非常に役立ちます。 

このブログ記事では、レッドチームがテストに利用できる、さまざまな種類のボットやメソッド、ツールについて解説し、それらの選び方や機能を巧妙化させる方法をご説明します。さらに、攻撃をより困難でコストがかかるようにするセキュリティ対策をご紹介します。 

ブラウザベースでないクライアント

最も一般的なタイプの自動化では、Python や Go、curl などのブラウザベースでないクライアントがよく利用されています。このようなタイプのボットは、ベンダーが提供する「既成ツール」からオープンソースプロジェクトまで多岐に渡り、データのスクレイピングやペネトレーションテストの自動化に使用できます。例えば、GitHub で #pentest を検索すると、興味深い結果が得られると思います。  

以下のスニペットは Python で作成されたボットの例を示しています。このボットは、リクエストBeautifulSoup ライブラリを利用して Webサイトからデータをスクレイピングして解析します。

import requests
from bs4 import BeautifulSoup

page = requests.get(url='https://example.com')
content = BeautifulSoup(page.content, 'html.parser')

通常のブラウザベースのクライアントとは異なり、このスクリプトのようにブラウザベースでないクライアントでは、JavaScript コードは実行されません。高度なボット対策ソリューションは JavaScript を使用してブラウザとシステム設定に関する属性を自動的に収集して報告します。これはクライアントサイド検出と呼ばれる手法で、このデータがクライアントリクエストで返されない場合、自動化プログラムが作動している可能性が高いと言えます。

ブラウザ自動化フレームワーク

ブラウザベースでないツールは攻撃者にとって非常に便利ですが、これらは保護対策によって検出されやすいのも事実です。ボット開発者にとって最も大きな障害は、ボットを特定してブロックするセキュリティソリューションです。検出されないようにする最も効果的な手口は、人間によって生成されたトラフィックをまねることです。

PuppeteerPlaywright などのブラウザ自動化フレームワークは、人間をまねたボットを作成するのによく使用されるソリューションです。これらは実際のブラウザを利用するため、JavaScript コードを実行し、ページの読み込みやマウスの移動、テキストのタイピングなど、本物のユーザーの動作をまねる一連のタスクを自動化できます。 

これらの自動化フレームワークを使用することで、グラフィカル・ユーザー・インターフェイス (GUI) の有無にかかわらず、ブラウザをインストルメント化できます。GUI がないブラウザは「ヘッドレス」ブラウザと呼ばれ、実際のブラウザと同じ機能がありますが、グラフィックやその他のユーザーインターフェースのコンポーネントを操作する必要がないため、CPU と RAM の使用量が少なくて済みます。最もよく使用されるヘッドレスブラウザとして、Headless Chrome が挙げられます。 

以下に示す Puppeteer スクリプトの例では、ヘッドレスモードで https://example.com/ に移動し、example.png としてスクリーンショットを保存します。

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com/');
  await page.screenshot({ path: example.png' });

  await browser.close();
})();

コードを作成するのは楽しいかもしれませんが、このようなスクリプトの開発には高度なスキルと時間を要します。幸いなことに、利用可能な便利なツールがあります。例えば、Google は Chrome Developer Tools で Recorder をリリースしました (Chrome バージョン97で提供開始)。Recorder パネルでユーザーフローを記録、再生できるほか、コードを1行も記述することなく、記録されたデータを Puppeteer スクリプトにエクスポートできます。 

購入可能なボット

攻撃者と言えば、わずか数行のコードを作成して数分でシステムに侵入できる「邪悪な天才」といったステレオタイプが想像されがちです。こうしたステレオタイプが当てはまる場合もありますが、一般的には誇張されたイメージであり現実とはかけ離れています。実際には、サイバー犯罪者は通常、抵抗が少ないパスを探しています。ボットはセットアップして実行するのが難しいことで知られています。攻撃者のほとんどが可能な限り素早く目的を達成したがっていることを考えると、ボットを売買するマーケットプレイスが存在することは、当然とも言えます。 

ボットの売買が盛んな業界のひとつが小売業界で、「スニーカーボット」と呼ばれるものが有名です。スニーカーの限定版が発売されると、どの熱狂的なスニーカーコレクターが品切れ前にいち早く購入手続きを完了できるかが競われます。スニーカーボットを利用することで購入プロセスを瞬時に行うことが可能になり、手動でこのプロセスを完了する購入者よりも有利になります。この種のスクレイピングボットの対象はスニーカーに留まらず、数に限りのあるあらゆる商品の購入に使用されています。 

このタイプのボットサービスの多くは違法ではなく、ベンダーがプロダクトのリリースを Twitter で発表することもよくあります。しかし、さまざまな Webサイトの利用規約を侵害していることが多いのも事実です。また、これらのボットのせいで顧客が商品を購入できなくなり、ブランドの評判や顧客のロイヤルティが損なわれるだけでなく、サイトが過負荷の状態になり、インフラコストの増大やサイトパフォーマンスの低下につながります。 

一般的に売買されている別のボットタイプに、マルウェアに感染したマシーンのグループを指す「ボットネット」と呼ばれるものがあります。最近、閉鎖されたサイバー犯罪フォーラム「RaidForums」と同様のフォーラムでボットネットの売買が行われていると報告されています。ボットネットは大量のボットを含み、巨大化する可能性があります。数百万ものマシーンで構成される巨大なボットネットを使って分散型サービス妨害攻撃 (DDoS 攻撃) がしかけられる場合もあれば、特定の重要なシステムに侵入し、政府関係や金融関係のデータなどへのアクセスを試みるのに小さなボットネットが使用されることもあります。

不正な自動化プログラムをエミュレートする

この記事の前半では、ブラウザベースでないクライアント、ヘッドレスブラウザを利用するボット、購入可能なボットなど、異なるタイプのボットについてご紹介しました。このセクションでは、レッドチームが利用できるメソッドやツール、これらの選び方、機能を巧妙化させる方法について解説します。

フェーズ1

まず、最初にご紹介した Python で作成され、リクエストと BeautifulSoup ライブラリを利用して Webサイトからデータをスクレイピングし、解析するボットの例について見てみましょう。

import requests
from bs4 import BeautifulSoup

page = requests.get(url='https://example.com/')
content = BeautifulSoup(page.content, 'html.parser')

これは、GET リクエストを送信し、BeautifulSoup オブジェクトを返す非常にシンプルなスクリプトです。このスクリプトを実行し、送信された HTTP リクエストを確認します。

GET / HTTP/2
Host: example.com
User-Agent: python-requests/2.28.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive

このリクエストについて、いくつか注目すべきポイントがあります。User-Agent ヘッダーを見ると、python-requests/2.28.0 をアドバタイズしているのが分かります。また、Accept ヘッダーは */* に設定されています。Accept ヘッダーは、クライアントが処理可能なコンテンツタイプをサーバーに知らせます。*/* ディレクティブは、あらゆるタイプのコンテンツに対応することを意味します。

次に、同じリクエストを Chrome ブラウザで実行してみましょう。

GET / HTTP/2
Host: example.com
Sec-Ch-Ua: "-Not.A/Brand";v="8", "Chromium";v="102"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

2つのリクエストには明らかに大きな違いが見られます。User-Agent ヘッダーと Accept ヘッダーが異なるだけでなく、Python で作成されたボットに比べて、ブラウザリクエストでは9つのヘッダーが追加されています。これらの機能は、ユーザーが人間かボットかを分類するフィンガープリントベースのアプローチで使用されます。セキュリティソリューションは、このセルフアドバタイズ情報を迅速に利用してボットトラフィックをブロックできます。

これを念頭に置いて、ブラウザ経由で実行されたリクエストに似せる形で Python ボットに変更を加えます。

import requests
from bs4 import BeautifulSoup


headers = {
    'Sec-Ch-Ua': '"-Not.A/Brand";v="8", "Chromium";v="102"',
    'Sec-Ch-Ua-Mobile': '?0',
    'Sec-Ch-Ua-Platform': 'macOS',
    'Upgrade-Insecure-Requests': '1',
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
    'Sec-Fetch-Site': 'none',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-User': '?1',
    'Sec-Fetch-Dest': 'document',
    'Accept-Encoding': 'gzip, deflate',
    'Accept-Language': 'en-US,en;q=0.9',
}

page = requests.get(url='https://example.com/')
content = BeautifulSoup(page.content, 'html.parser')

このスクリプトを実行し、送信された HTTP リクエストを見てみましょう。

GET / HTTP/2
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36
Accept-Encoding: gzip, deflate
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Connection: keep-alive
Sec-Ch-Ua: "-Not.A/Brand";v="8", "Chromium";v="102"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: macOS
Upgrade-Insecure-Requests: 1
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Language: en-US,en;q=0.9

一見すると、すべてのヘッダーがリクエストに含まれているように見えます。しかしよく見ると、Python のスクリプトでは、リクエストヘッダーの順序が保持されていません。RFC 仕様では、ヘッダーの順序は重要ではないとされているため、ほとんどのライブラリでは順序が定義されていません。ただし、主要ブラウザは HTTP ヘッダーを特定の順序で送信するため、わずかな順序の違いを検出して自動化プログラムによるトラフィックを特定することができます。幸いなことに、Python のリクエストライブラリによって、ここで作成したスクリプトでヘッダーの順序を保持することができます。  

フェーズ2

検出を回避するためにプログラムをより巧妙化させる上で、セキュリティソリューションはボットと人間を区別するのに動作ベースのアプローチに依存している点に留意する必要があります。一般的にボットは正規のユーザーとかなり異なる動作をします。通常、動作ベースのアプローチでは、ユーザーの動作を IP アドレス (IP レピュテーション) にリンクさせることで、セッション中のユーザーの動作をトラックしています。

IP アドレスにリンクされる動作パターンには以下のタイプがあります。

  • リクエストの総数

  • 訪問したページの総数 

  • ページを閲覧する間隔

  • ページを訪問した順序

  • ページで読み込まれたリソースのタイプ

ここで作成したサンプルスクリプトを使って大量のデータをスクレイピングしようとすると、上記のパターンのいずれかによって検出またはブロックされる可能性があります。そこで、レート制限やブロックを回避するため、数回試行するごとに IP アドレスをローテーションするとします。これはプロキシサーバーを使用することで可能です。プロキシサーバーはクライアントとインターネットのその他の部分をつなぐ中継役として機能します。リクエストがプロキシサーバーを介して送信されると、プロキシの IP アドレスが表示され、送信元のクライアント (またはボット) の IP アドレスをマスクできます。 

必要なプロキシのタイプは回避しようとする制限によって異なります。データセンターや VPN、既知のプロキシサーバー、Tor ネットワークなどに属する IP アドレスは分析中に目立ったり、すでに疑わしい IP アドレスとしてフラグされていたりする可能性が高いです。そのため、巧妙なボットの大半は、あまり知られていないレジデンシャルプロキシを利用することが多いです。レジデンシャル IP アドレスは正規のユーザーによって使用されるため、他のタイプのものよりも信用度が高いという特徴があります。   

実際にプロキシサービスをセットアップする前に、対象のアプリケーションが X-Forwarded-For や True-Client-IP、X-Real-IP など、接続するクライアントのリモート IP アドレスを特定するリクエストヘッダーを信用するように設定されているかどうかを確認します。攻撃者は偽装した IP アドレスを提供することでこの設定を悪用できます。実際、Fastly のセキュリティリサーチチームは、この欠陥を悪用し、プロキシの動作をエミュレートする目的で、任意の IP アドレスをあらゆる HTTP リクエストヘッダーに挿入できる機能を Nuclei提供しています。 

IP アドレスと並んで、TLS フィンガープリントもサイトと通信しているクライアントのタイプを示すツールとして、広く使用されています。このようなフィンガープリントは、サポートする TLS 暗号スイートを使用してデバイスを特定します。TLS フィンガープリントに関する詳細は、Fastly のブログ記事「TLS フィンガープリントに関する最新情報」をご参照ください。ただし、クライアントによって ClientHello パケットがコントロールされ、ボットによるフィンガープリントの変更が可能であることから、この検出方法には注意が必要です。uTLS などのツールを使って、TLS シグネチャを偽装することができます。 

フェーズ3

ここまでは、サーバーサイドの機能に関連するアプローチをご紹介してきましたが、サーバーサイドの検出のみに依存するボットソリューションは効果が限られています。ブラウザベースでないボットと異なり、高度なボットは実際のブラウザを利用する、ブラウザの自動化フレームワークを使用して、正規のユーザーと同じ HTTP と TLS フィンガープリントを偽造し、JavaScript コードを実行することができます。undefined 

このような高度なボットを検出するには、クライアントサイドでの検出機能が欠かせません。クライアントサイドの検出では、マウスの動きやキー入力、ディスプレー解像度、デバイスのプロパティなど、JavaScript を使用して収集されるイベントや属性などがチェックされます。これらのスクリプトは、ヘッドレスブラウザやインストルメント化フレームワークで見られる属性が存在するかどうかをテストします。 

例えば、クライアントサイドでの検出に使用される手法に、オーディオとビデオのコーデックの存在をテストする方法があります。以下のコードスニペットでは canPlayType 関数を使用して、サポートされているメディアタイプをチェックします。

const audioElt = document.createElement('audio')
return audioElt.canPlayType('audio/aac')

正規の Chrome ブラウザの場合、「probably」の値が返されます。これを念頭に置いて、上記のサンプルで使用した Puppeteer スクリプトに変更を加え、期待通りの値が返されるかどうか検証してみましょう。

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.screenshot({ path: example.png' });

  const audioElt = await page.evaluate(() => {
    let obj = document.createElement('audio');
    return obj.canPlayType('audio/aac');
  })
  console.log(audioElt);

  await browser.close();
})();

残念ながら、空の値が返されます。さらに、ヘッドレス Chrome がユーザーエージェントを介してサーバーサイドで特定されています。

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/103.0.5058.0 Safari/537.36

デフォルトでは、Puppeteer は明らかな検出シグナルの特徴をマスクするどころか、自動化されていることを露呈させます。Puppeteer の主な目的は Webサイトのテストを自動化することであり、人間のように動作し、検出されるのを回避できるボットを作成することではありません。しかし思ったとおり、ステルスプラグインを提供する puppeteer-extra と呼ばれるオープンソースライブラリが存在します。ボット開発者はこのプラグインを使用してデフォルト設定を容易に変更し、さまざまな手法を用いてヘッドレス puppeteer の検出を困難にすることができます。

主な違いは、puppetter の代わりに puppeteer-extra をインポートする点のみです。

const puppeteer = require('puppeteer-extra');
puppeteer.use(require('puppeteer-extra-plugin-stealth')());

これで、ボットのユーザーエージェントとコーデック出力が正規の Chrome ブラウザのものと一致することが確認できます。

const userAgent = await page.evaluate(() => {
  return navigator.userAgent;
})
console.log(userAgent);
// Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5109.0 Safari/537.36

const audioElt = await page.evaluate(() => {
  let obj = document.createElement('audio');
  return obj.canPlayType('audio/aac');
})
console.log(audioElt);
// probably

巧妙に仕組まれたこのブラウザの動作を検出する方法は他にもいろいろあるので、アプリケーションに対する現在の保護対策の限界を理解し、リスクをより最小限に抑える方法を検討することが重要です。

まとめ

不正な自動化プログラムを検出してブロックするには、高度な分析と巧妙な手法が必要です。すべてのボットによるトラフィックが悪質とは限りません。企業が自社のサービスへのアクセス向上を目指して複数の言語でクライアントライブラリを提供する場合など、無害なこともあります。そのため、自社のインフラストラクチャに接続することが予想されるトラフィックとデバイスのタイプを理解することが極めて重要です。良し悪しの判断は、自動化を使用する既知の許容範囲の動作からの逸脱とみなされる基準と環境によります。

自社のインフラストラクチャに不正な自動化プログラムをしかけることによって、何を「許容範囲」と定義すべきかについてより深いインサイトが得られます。さまざまなタイプのボットを理解し、攻撃者が使用する手法を自ら試すことで、不正な自動化プログラムに対して適切な対策を講じ、組織にとって最も重要なものを保護することが可能になります。