前回の記事では、iPhoneのSafariから「小説家になろう」を開くと403になり、iCloudプライベートリレーを経由しないよう「IPアドレスを公開」にすると閲覧できることを確認した。
症状の切り分け方や閲覧するための対処方法は、前の記事にまとめている。
iPhoneのSafariで小説家になろうが403になる。原因はiCloudプライベートリレーだった | Kuromica
iPhoneのSafariだけ「小説家になろう」が403になり、Chromeでは閲覧できる現象に遭遇した。原因の切り分け方と、iCloudプライベートリレーを無効にせず閲覧する方法をまとめる。
https://kuromica.com/blog/syosetu-safari-403-icloud-private-relay/
その後も確認を続けると、Private Relayを有効にしていれば必ず403になるわけではなかった。
| Private Relayの出口 | 結果 |
|---|---|
| Cloudflare | 今回確認した3IPは403 |
| Akamai | 403 / 正常表示 |
| Fastly | 正常表示 |
| 「IPアドレスを公開」 | 正常表示 |
同じiPhone、同じSafari、同じURLでも、Private Relayで割り当てられる出口IPによって結果が変わっている。
この記事では、仮に今回の403が攻撃・自動アクセス対策の一部だとしたら、IPやネットワーク単位の制限が何に効くのかを考える。
そのうえで、一般読者まで403にする前に、配信側でオリジンへのアクセス自体を減らせないか、自分ならどう設計するかまで考えてみる。
ヒナプロジェクトの内部設定や今回の403ルールは公開されていない。特定のIPやIPレンジ、ASN、IP Reputation、Proxy判定、レート制限など、実際にどの条件で拒否されているかは外から確認できない。
ヒナプロジェクトがDDoSを現実の問題として抱えているのは事実
IDCフロンティアが2024年に公開した導入事例では、ヒナプロジェクトがIDCFクラウドとDDoS対策サービスを利用していることが紹介されている。
導入理由としても、外部からの悪意ある攻撃によってWebサイトへアクセスしにくくなることがあったと説明されている。
株式会社ヒナプロジェクト様
株式会社ヒナプロジェクト様は、「小説家になろう」をはじめとした日本最大級の小説投稿・閲覧サイトを運営する企業です。 今回は、サービス提供が途切れないことを第一に運営している「小説家になろう」のインフラとしてIDCFクラウドを選定された理由や経緯についてお話を伺いました。
https://www.idcf.jp/case/hinaproject/
2025年3月18日には「小説家になろうグループのサイトを含むネットワーク」に対する大規模DDoS攻撃が発生し、4月9日にも再び大規模DDoS攻撃を受けている。
【解消済】小説家になろうグループ接続障害のお知らせ
https://blog.syosetu.com/article/view/article_id/4855/
【解消済】小説家になろうグループ接続障害のお知らせ
https://blog.syosetu.com/article/view/article_id/4878/
その前日の3月17日には、アクセス解析システムKASASAGIも海外からDDoS攻撃を受けたと告知されている。
【解消済】KASASAGI システムに対するDDoS攻撃発生の件
https://blog.syosetu.com/article/view/article_id/4853/
そのため、今回の403を見て攻撃・自動アクセス対策を連想すること自体は不自然ではない。
ただし、「DDoS対策」という言葉だけでは範囲が広すぎる。
帯域を埋めるDDoSとHTTP Floodは分けて考える
DDoSには、回線帯域を埋める攻撃もあれば、通常のHTTPリクエストを大量に送る攻撃もある。
| 攻撃・負荷の位置 | 主な例 | 主な対策 |
|---|---|---|
| L3/L4 | UDP Flood、ICMP Flood、SYN Floodなど | Scrubbing、Anycast、BGP FlowSpec、SYN Proxyなど |
| TLS / Connection | TLS handshake大量発生、connection exhaustion | Edge TLS終端、Connection Limitなど |
| L7 | HTTP Flood、API Flood、bot、scraping | WAF、Rate Limit、Bot Managementなど |
| Application / オリジン | 重いDB処理、検索、キャッシュミス大量発生 | Cache、Load Shedding、Concurrency Limitなど |
今回観測したのはHTTP 403だ。HTTPリクエストを受けたうえでアクセス可否を判断しているため、今回のような制御がDDoS対策と関係しているとしても、L7 HTTP Floodやbot、自動アクセス対策に近い。
帯域を埋める攻撃は、オリジン側で403を返しても、その手前のネットワーク帯域を守れない。
Internet
↓
大量のトラフィック
↓
回線・上流ネットワーク
↓
Edge / LB / オリジン
一方、
GET /n4402bd/1/
GET /n4402bd/1/
GET /n4402bd/1/
...
のようなHTTP Floodなら、WAFやRate Limitで早い段階に落とす意味がある。
IP・ネットワーク単位の制限は何に効くのか
目的ごとに見ると、向いているものと限界が分かる。
| 想定する問題 | IP・ネットワーク制限の効果 | 限界 | 正規ユーザーへの影響 |
|---|---|---|---|
| Volumetric DDoS | 主対策にはなりにくい | オリジン側の403では上流の帯域飽和を防げない | 制限対象だけ利用不能になる |
| SYN / TLS系DDoS | 主対策ではない | より前段の接続制御が必要 | 同上 |
| L7 HTTP Flood | 一定の効果がある | 分散されるとIP単位では弱い | 共有IPの読者を巻き込む |
| 大量スクレイピング | 単純なbotには効く | IPローテーションやResidential Proxyで回避できる | Private Relay利用者も対象になり得る |
| 脆弱性スキャン | ノイズ削減には有効 | 攻撃元を変えれば継続できる | 同じネットワークの正常アクセスも失う |
| 高度な分散bot | 限定的 | IPだけでは識別しにくい | 誤検知が増えやすい |
| 広告・リワード不正 | 補助信号にはなる | IP単独では判定できない | 正規閲覧や広告表示まで失う可能性 |
ネットワーク属性を第一防衛線の一つとして使うこと自体は珍しくない。
AWS WAFにもIP Reputationルールがあり、IP ReputationやRate Limitは入口で使える対策の一つになっている。
https://docs.aws.amazon.com/ja_jp/waf/latest/developerguide/aws-managed-rule-groups-ip-rep.html
https://docs.aws.amazon.com/ja_jp/prescriptive-guidance/latest/bot-control/static-controls.html
ただし、回避する側は別のクラウド、VPN、Residential Proxy、モバイル回線などへ移れる。
IPを見ること自体が問題なのではなく、IPだけでは正規ユーザーと攻撃者を強く識別できない。
WAFを入れればPrivate Relayが403になるわけではない
一般的なWAFを導入しただけでPrivate Relayを一律に拒否するのが標準動作、というわけではない。
WAFやIP Intelligenceサービスには、IP Reputation、匿名Proxy、ホスティングネットワーク、ASN、レート制限などを判定材料として使う機能がある。
Apple自身も、Private RelayのIPがGeoIPデータベースなどで is_relay、is_hosting、privacy_service、privacy_proxy といった属性で分類される場合があると説明している。
iCloudプライベートリレーに向けたネットワークやWebサーバの準備 - iCloud - Apple Developer
iCloudプライベートリレーは、iCloudに組み込まれた新しいインターネットプライバシーサービスです。ユーザーは、よりプライベートかつ安全にWebサイトにアクセスし、ブラウジングできるようになります。
https://developer.apple.com/jp/icloud/prepare-your-network-for-icloud-private-relay/
Private Relayの出口IPリスト、WAF、Rate Limit、GeoIP、アクセス解析まで含めたWebサービス側の対応は、別の記事にまとめた。
iCloud Private Relay対応でWebサイト側が確認したいこと。WAF・Rate Limit・GeoIP・アクセス解析 | Kuromica
iCloud Private Relayの共有IPを前提に、WAF、Rate Limit、GeoIP、アクセス解析、ログ設計と、Private Relayを利用させない場合の403設計を確認する。
https://kuromica.com/blog/icloud-private-relay-web-server-support/
例えば次のどれでも、今回に近い挙動は作れる。
Private RelayのIPレンジ
→ ブロック
特定ASN
→ ブロック
privacy_proxy
→ 高リスク扱い
IP Reputationが一定以下
→ ブロック
IP単位のレート上限超過
→ ブロック
今回確認したCloudflareの出口IPは3つとも403だった。一方、Akamaiでは403になるIPと正常表示できるIPの両方があった。
少なくとも「Private Relay全体を拒否」「Akamai全体を拒否」という単純なルールでは、この結果を説明できない。
Cloudflareについても、今回確認した3IPが403だったことから、CloudflareのASN全体を拒否しているとまでは言えない。
配信構成の追加調査で、403の見方も変わった
403を調べる途中で、DNSとHARから配信先も追い直した。
当初は「IDCFとさくらインターネットのどちらへ接続されるかで403が変わる」と考えていたが、追加調査では、本文HTMLの ncode.syosetu.com はIDCF側、JavaScriptやCSSなどの静的ファイルはさくらインターネット側から配信されていた。
つまり、本文HTMLの接続先がIDCF側でも、Private Relayの出口IPによって403と正常表示が分かれている。
DNS、Amazon Route 53、ASN、HAR、IDCFとさくらインターネットの役割分担、マルチクラウドの可用性については別の記事にまとめた。
小説家になろうの配信構成を外から調べた。Route 53、IDCF、さくらインターネットを追う | Kuromica
Private Relayの403調査をきっかけに、「小説家になろう」の公開DNSとHARを確認した。Amazon Route 53、IDCF、さくらインターネットがどこで使われているのかを追い、マルチクラウドと障害耐性の見え方まで考える。
https://kuromica.com/blog/syosetu-infrastructure-route53-idcf-sakura/
この記事では、ここから配信インフラそのものではなく、IPベースのアクセス制御に頼りすぎずにオリジンをどう守るかを考える。
本文は完成HTMLに含まれている
実際の小説ページをブラウザから確認すると、本文はJavaScriptであとからAPI取得する構成ではなく、初期HTMLに含まれていた。
例として確認した作品ページはこちら。
https://ncode.syosetu.com/n4402bd/
作品情報やエピソード一覧も初期HTMLに含まれ、エピソードページでは本文そのものがHTMLとして返される。
フロントエンドから確認できるのは、本文を含む完成HTMLをサーバーから返すMPA型の構成だ。
内部でリクエストごとにHTMLを生成しているのか、一部を事前生成しているのか、ncode.syosetu.com の本文HTMLにどのような共有キャッシュがあるのかまでは外から確認できない。
追加調査では、JavaScriptやCSSなどの静的ファイルについては、さくらインターネット側でApache Traffic Serverを使ったキャッシュ配信が行われていることを確認している。
そのため、ここで考えたいのは静的ファイルではなく、本文を含むHTMLの方だ。
寿命の違う情報が同じHTMLに入っている
小説本文は、作者が編集しなければほとんど変わらない。
一方、同じページには評価、感想への導線、推薦など、本文とは更新頻度の違う情報も含まれている。
概念的には次のような状態になる。
エピソードHTML
├─ 小説本文 ほぼ不変
├─ タイトル 低頻度
├─ 前後リンク 低頻度
├─ 評価・リアクション 随時変化
├─ 感想関連 随時変化
└─ 推薦 随時変化
2012年に公開された本文と、最近変化する情報が同じレスポンスの単位になり得る。
ページ単位でHTMLを組み立てる構成では珍しくないが、CDNや共有キャッシュを強く効かせる場合はキャッシュ寿命を決めにくくなる。
本文だけなら長くキャッシュできても、同じHTMLに短い寿命の情報が入れば、ページ全体のキャッシュ寿命をそちらへ引っ張られやすい。
オリジンまでリクエストが届くなら、入口で落とす意味は大きい
現在の内部構成は分からないが、仮に次のような経路なら、L7アクセス制御を強める理由は分かる。
User
↓
WAF / LB
↓
Web / Application
↓
アプリケーションキャッシュ
↓
DB
100万リクエストが来れば、WAFで落とさない限り後段へ流れる。
RedisのようなアプリケーションキャッシュがあればSQLへの到達は減らせる。
100万 requests
↓
Application 100万
↓
Redis 100万
↓
DBはキャッシュミスだけ
DBを守るうえでは有効でも、ApplicationやRedisまで100万回処理すること自体は変わらない。
このような構成を守るなら、入口で不要なアクセスを早く落とすことには意味がある。
問題は、そこでPrivate Relayを使う普通の読者まで巻き込む必要があるのか、という点だ。
ここからは、自分ならどう守るかという設計案
公開されている本文HTMLの性質を踏まえて、自分ならオリジン負荷をどう減らすかを考える。
まず匿名の本文ページをマイクロキャッシュする
全面的に作り直さなくても、匿名ユーザーの小説本文ページだけ数秒から数十秒キャッシュするところから試せる。
User
↓
CDN / Reverse Proxy
↓
マイクロキャッシュ
↓ キャッシュミスだけ
既存のページ生成処理
キャッシュされた後の同一ページへのアクセスはオリジンまで届かなくなる。
さらにCDNによっては、同じキャッシュキーに対する同時のキャッシュミスをまとめ、オリジンへの問い合わせを1つに近づけるRequest Collapsingも利用できる。CloudFrontにも同一オブジェクトへの同時リクエストをまとめる仕組みがある。
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/RequestAndResponseBehaviorCustomOrigin.html
不要なQuery StringやCookieまでキャッシュキーへ含めると、同じ本文でも別オブジェクトとして扱われてヒット率が下がる。
本文の表示に不要な値をキャッシュキーから外す設計も必要になる。
次にコンテンツごとの寿命を決める
マイクロキャッシュで問題がなければ、どこまで長く保持できるかを設計する。
| コンテンツ | キャッシュ方針の例 |
|---|---|
| 小説本文 | 長いTTL、編集時にパージ |
| タイトル・前後リンク | 長め、作品更新時にパージ |
| 作品トップ | 数分〜数十分、投稿時にパージ |
| 推薦 | 短TTL |
| 感想一覧 | 短TTL |
| ブックマーク・評価済み状態 | ユーザー単位、共有キャッシュから分離 |
| 投稿・編集 | 動的処理 |
寿命が違うからといって、すべてを細かいAPIへ分割すればよいわけでもない。
HTTPリクエストを増やし過ぎれば、ブラウザ側のオーバーヘッドや実装の複雑さが増える。
例えば本文HTMLと、ユーザー固有状態を返す1本のAPIくらいからでも始められる。
更新時にパージする
作者が第10話を編集したとする。
作者が第10話を編集
↓
DB更新
↓
/n4402bd/10/ のキャッシュをパージ
↓
次回アクセスで再生成
これなら、普段は本文を長くエッジへ置いておける。
IDCFにもCDNサービスがあり、URLやSurrogate-Key単位のキャッシュ削除を利用できる。
https://cdn.www.idcf.jp/help/cdn/guide/purge_using_api.html
IDCFを利用しているからエッジキャッシュを使えない、という話ではない。
必要なら本文を静的に書き出す
さらに進めるなら、小説本文は投稿・編集をイベントとしてHTMLを書き出す設計とも相性がいい。
作者が投稿・編集
↓
DB更新
↓
本文HTML生成
↓
Object Storage / CDN
↓
読者には静的配信
コメント、ブックマーク、評価などは別に扱う。
最初から全作品を完全静的化する必要はなく、
マイクロキャッシュ
↓
エッジキャッシュ
↓
更新時パージ
↓
必要なら静的書き出し
くらいの順序で試したい。
WAFだけにオリジン保護を依存しない
CDNの前後どこにWAFを置くかは製品や構成によって異なり、エッジ側でキャッシュヒットを含めてリクエストを検査する構成もある。
そのため、「キャッシュすればWAFを通らなくなる」という話ではない。
重要なのは、キャッシュ可能な本文GETをオリジンまで到達させないことだ。
User
↓
CDN / WAF
├─ キャッシュ済みの小説本文 → エッジで応答
│
└─ 動的処理・キャッシュミス
↓
Application
↓
Cache / DB
こうしてオリジンへ流すリクエストを減らせれば、L7のアクセス制御だけにオリジン保護を依存しなくて済む。
コメント投稿、ログイン、検索、評価、ブックマークなど、本当に動的処理が必要な部分には引き続きWAFやRate Limitを使える。
小説本文を100万回GETするbotに対しても、アクセス元を毎回細かく判定するだけでなく、「100万回来ても、その大半をエッジで返してオリジンへ届かせない」という防御が取れる。
それでもネットワーク単位のブロックには意味がある
エッジキャッシュがあればアクセス制御が不要になるわけではない。
悪質なbotを止めれば、
- CDN自体のリクエスト量
- ログ量
- キャッシュミス
- スキャンのノイズ
- 動的APIへの攻撃
- Bot Managementや後段処理のコスト
を減らせる。
ただ、正規ユーザーを広く巻き込むブロックなら話は別だ。
Private Relayは、誰でも匿名で使えるオープンProxyではない。
Appleは有効なAppleデバイスとiCloud+サブスクリプションを確認し、Private Relay側にも不正利用を抑える仕組みを持っている。Webサイト運営者には、Private Relayの共有IPを大規模なCGNATや企業ネットワークのように扱い、IPベース判定を調整するよう案内している。
そこで一般のSafari利用者まで403になるなら、「そのIPをどう判定するか」だけでなく、「本文GETをオリジンへ到達させずに済まないか」も考えたい。
読者から見れば、403の理由は関係ない
攻撃対策として成立しているかどうかと、読者体験は別の話だ。
読者には、
小説を開く
↓
403
としか見えない。
Private Relayが原因だと調べる人もいるだろうし、Chromeなら見られることに気付く人もいるだろう。
同じ作品が別のサービスにも掲載されていれば、読者がそちらへ移る可能性もある。
攻撃者には出口IPを変える選択肢がある一方、一般読者はサービスから離れるかもしれない。この非対称性は無視しにくい。
まとめ
今回の403がどのルールによるものなのかは外から確認できない。
ただ、HTTP 403という挙動と実際の出口IPごとの違いを見ると、帯域飽和型DDoSの主対策というより、L7 HTTP Flood、bot、scrapingなどに対するアクセス制御として考える方が近い。
IP、ASN、IP Reputation、Rate Limitを入口で使うことには意味がある。一方、Private Relayのように多数の正規ユーザーが共有する出口IPを強く制限すると、攻撃とは関係のない読者も巻き込む。
実際の小説ページでは本文が完成HTMLに含まれている。現在どのような共有キャッシュが使われているかは外から確認できないが、公開済みの小説本文そのものは読み取り中心で、キャッシュしやすい性質を持っている。
自分なら、一般ユーザーを含むネットワークを広く拒否する前に、まず匿名の本文ページをマイクロキャッシュする。そこからキャッシュ寿命とキャッシュキーを設計し、投稿・編集時のパージを追加し、必要なら本文の静的書き出しまで進める。
Private Relayを巻き込むアクセス制御にもL7対策としての意味はある。ただ、小説本文のようなコンテンツなら、アクセス元を広く拒否するだけでなく、オリジンへ届く本文GETそのものを減らす設計も並行して考えたい。