Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CORSの範囲を制限し、無関係なリクエストを弾く #392

Closed
1 of 3 tasks
ts-klassen opened this issue May 4, 2022 · 37 comments
Closed
1 of 3 tasks

CORSの範囲を制限し、無関係なリクエストを弾く #392

ts-klassen opened this issue May 4, 2022 · 37 comments
Labels

Comments

@ts-klassen
Copy link
Contributor

ts-klassen commented May 4, 2022

内容

Access-Control-Allow-Origin: * は変えたほうが良いかもしれない(要議論)
ヒホさんとTwitterのDMで話した内容を詳しく書きます。

現状

VOICEVOXエンジンとエディタのオリジンが違うため、JavaScriptでのリクエストはCORS上の問題が発生する。現在、エンジン側のヘッダで Access-Control-Allow-Origin: * と返すことで異なるオリジンからのリクエストを許可している。

変更すべき点

現状ではapp以外からのJavaScriptリクエストも許可してしまうため、Access-Control-Allow-Origin: app://. に書き換えるべきである。(要検証)

app以外からも許可している現状の問題点

VOICEVOXを起動した状態で悪意のあるサイトを開くと、知らぬ間にVOICEVOXエンジンへリクエストが飛ぶ可能性がある。

再現方法

VOICEVOX0.11.4をLinuxで起動した。( $ /path/to/VOICEVOX0.11.4/run
google chromeで https://voicevox.su-shiki.com/su-shikiapis/localtests/ にアクセスし、InspectのNetworkを開きリロードした。
送信ボタンを押した。
送信に成功し、 /path/to/VOICEVOX0.11.4/run の標準出力に 127.0.0.1:37768 - "POST /synthesis?speaker=1&enable_interrogative_upspeak=true HTTP/1.1" 200 OK と出力された。

原理

そもそもCORSはブラウザの自主規制である。chromium系であれば起動時のオプションに --disable-web-security とつければ無効化できる他、JavaScriptを介さないリクエストや、ブラウザを使わないリクエスト(curlなど)に対し何ら効果を発揮しない。しかし、Electronがchromiumをベースにしているため、web-securityを無効化しない限り別オリジンへJavaScriptリクエストを出せないのである。

実演

すべてのオリジンを許可するv1apiと、同一オリジンのみ許可するv2apiを用意した。
v1api; https://api.su-shiki.com/v1/localtests/
v2api; https://api.su-shiki.com/v2/localtests/
無論、ブラウザやcurlで直接アクセスすることを妨げるものではない。しかし、別オリジン(ホストが違うなど。スキームやポート番号が違っても別オリジンである。)のJavaScriptでリクエストを飛ばすとブラウザが止めてくれるはずである。
v1api; https://voicevox.su-shiki.com/su-shikiapis/localtests/v1api.html
v2api; https://voicevox.su-shiki.com/su-shikiapis/localtests/v2api.html

VOICEVOXエンジンとエディタは別オリジンである

エンジンが http://127.0.0.1:50021 であるのに対し、エディタは app://. であるため、レスポンスヘッダで Access-Control-Allow-Origin を正しく返さなければならない。

Access-Control-Allow-Originを変える

Access-Control-Allow-Origin: app://. を返すと良いだろう。(要検証。本当に弊害がないか。)

app以外からの利用

例えばブラウザの拡張機能としてVOICEVOXを利用するなど、JavaScriptでリクエストする場合、 Access-Control-Allow-Origin: * とせざるを得ないこともある。

Pros 良くなる点

利用者の意図しない形で、第三者にVOICEVOXエンジンへリクエストや任意の文字列を送信される可能性を減らせる。

Cons 悪くなる点

ユーザーの利用目的や連携先(外部ホスト)に応じて、セキュリティー設定を利用者自身が変更する必要がある。

検証時のVOICEVOXバージョン

0.11.4

検証時のOSの種類/ディストリ/バージョン

  • Windows
  • macOS
  • Linux

Linux inty 5.15.0-27-generic #28-Ubuntu SMP Thu Apr 14 04:55:28 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

Linux kali 5.15.0-kali3-amd64 #1 SMP Debian 5.15.15-2kali1 (2022-01-31) x86_64 GNU/Linux

その他

@github-actions github-actions bot added the OS 依存:linux Linux に依存した現象 label May 4, 2022
@Hiroshiba Hiroshiba added 要議論 実行する前に議論が必要そうなもの 優先度:低 (運用中止) and removed OS 依存:linux Linux に依存した現象 labels May 4, 2022
@Hiroshiba
Copy link
Member

ありがとうございます!!

VOICEVOXを起動している場合、ブラウザからリクエストを飛ばしたり情報を取得できたりするのはセキュリティ的に良くないとずっと思っており、これを防ぐ方法を探りたいなと考えています。

そもそもちゃんと知らないのですが、Access-Control-Allow-Originを設定すると、悪意のあるwebサイトからlocalhostに対してアクセスしても情報が取得ができなくなる感じでしょうか。それともリクエストを飛ばすこともできなくなる感じでしょうか。

あとVOICEVOXエディタからのリクエストはちゃんと受け付けられるようにする方法があればぜひ知りたいです。。

ご存じの方がいらっしゃったらぜひコメントいただけると嬉しいです。

@ts-klassen
Copy link
Contributor Author

ts-klassen commented May 5, 2022

誤解がありました。VOICEVOXエディタもブラウザ(でElectronの真似事をしたとき)同様Origin:http;//127.0.0.1ではじまると思っていましたが、ネットワークを監視した結果、Origin: app://.と返していることがわかりました。

未検証ですが、Access-Control-Allow-Origin: *Access-Control-Allow-Origin: app://.にしても引き続きエディタからアクセスできると思います。

ユーザーが必要に応じてAccess-Control-Allow-Origin: *Access-Control-Allow-Origin: app://.を切り替えられるようにするのが良いかもしれません。(セキュリティー設定の一種として)

@ts-klassen
Copy link
Contributor Author

ts-klassen commented May 5, 2022

質問への回答

悪意のあるwebサイトからlocalhostに対してアクセスしても情報が取得ができなくなる感じでしょうか。それともリクエストを飛ばすこともできなくなる感じでしょうか。

アクセスしても情報が(JavaScript側で)取得ができなくなるパターンと、リクエストを飛ばすこともできなくなるパターンがあります。どちらを使うかはブラウザ側の判断です。(https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#preflighted_requests)

リクエストを飛ばすこともできなくなるパターン

ブラウザは、本命のリクエスト(DELETE, POSTメソッドなど)の前にVOICEVOXエンジンにプリフライトリクエスト(OPTIONSメソッド)を送ります。そのレスポンスのAccess-Control-Allow-Originを見て本命のリクエストを送ったり自粛したりします。
これは、VOICEVOXエンジンが飛んできたリクエストを拒否しているわけでありません。ブラウザ側の自粛なので、ブラウザのセキュリティー設定で無効化することもできます。

アクセスしても情報が取得ができなくなるパターン

ブラウザは、JavaScriptの指示通りにVOICEVOXエンジンにリクエスト(GETやPOSTメソッド)を送ります。VOICEVOXエンジンはレスポンスを返します。ブラウザはレスポンスヘッダのAccess-Control-Allow-Originを見てその内容をJavaScriptに開示するか判断します。
これもブラウザのセキュリティー設定で無効化することができます。

具体的にVOICEVOXを起動してからの様子を追ってみましょう

No. Time Source Destination Protocol Length Info

VOICEVOXの起動

エディタがエンジンにバージョン情報を問い合わせます
1100 131.637552006 127.0.0.1 127.0.0.1 HTTP 456 GET /version HTTP/1.1
エンジンがエディタにバージョン情報を返します
1104 131.644657940 127.0.0.1 127.0.0.1 HTTP/JSON 74 HTTP/1.1 200 OK , JavaScript Object Notation (application/json)

エディタがエンジンにspeakersを問い合わせます
1106 131.651895447 127.0.0.1 127.0.0.1 HTTP 457 GET /speakers HTTP/1.1
エンジンがエディタにspeakersを返します
1110 131.652935278 127.0.0.1 127.0.0.1 HTTP/JSON 1795 HTTP/1.1 200 OK , JavaScript Object Notation (application/json)

エディタがエンジンにspeaker_infoを問い合わせます
1112 131.660723748 127.0.0.1 127.0.0.1 HTTP 511 GET /speaker_info?speaker_uuid=7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff HTTP/1.1
エンジンがエディタにspeaker_infoを返します
1184 131.692254064 127.0.0.1 127.0.0.1 HTTP/JSON 22758 HTTP/1.1 200 OK , JavaScript Object Notation (application/json)

ここまでが起動時の通信です。speaker_infoは(必要なだけ)複数回問い合わせます。これまでの間、プリフライトリクエストはありません。次に、四国めたん(あまあま)に「あ」と言わせます。

音声の合成

エディタがエンジンのaudio_queryにリクエストを送ります
1770 132.344769022 127.0.0.1 127.0.0.1 HTTP 496 POST /audio_query?text=&speaker=0 HTTP/1.1
エンジンがエディタにレスポンスを返します
1774 132.346603003 127.0.0.1 127.0.0.1 HTTP/JSON 266 HTTP/1.1 200 OK , JavaScript Object Notation (application/json)

エディタがエンジンのaccent_phrasesにリクエストを送ります
2943 274.063319046 127.0.0.1 127.0.0.1 HTTP 508 POST /accent_phrases?text=%E3%81%82&speaker=0 HTTP/1.1
エンジンがエディタにレスポンスを返します
2947 274.169124449 127.0.0.1 127.0.0.1 HTTP/JSON 261 HTTP/1.1 200 OK , JavaScript Object Notation (application/json)

エディタがエンジンのsynthesisにプリフライトリクエストを送ります
5846 633.003303670 127.0.0.1 127.0.0.1 HTTP 590 OPTIONS /synthesis?speaker=0&enable_interrogative_upspeak=false HTTP/1.1
エンジンがエディタにレスポンスを返します
5850 633.005024439 127.0.0.1 127.0.0.1 HTTP 68 HTTP/1.1 200 OK (text/plain)

エディタがエンジンのsynthesisにリクエストを送ります
5855 633.006267023 127.0.0.1 127.0.0.1 HTTP/JSON 883 POST /synthesis?speaker=0&enable_interrogative_upspeak=false HTTP/1.1 , JavaScript Object Notation (application/json)
エンジンがエディタにレスポンスを返します
5879 633.663685882 127.0.0.1 127.0.0.1 HTTP 3694 HTTP/1.1 200 OK (audio/wav)

結果

この一連のリクエストでプリフライトリクエストが必要だったのは、synthesisへのリクエストだけです。他はプリフライトリクエストを送っていません。

このように、情報が取得ができなくなるパターンと、リクエストを飛ばすこともできなくなるパターンをリクエストに応じて使い分けています。

例えばsynthesisにプリフライトリクエストを送らず、JavaScriptから情報が取得ができなくなるパターンが選択されたとしましょう。これではリクエスト自体は通常通り処理されるので、CPUやGPUリソースを消費することになります。したがって、リクエストを飛ばすこともできなくなるパターンのほうが安心です。
一方accent_phrasesは、情報が取得ができなくなるパターンが選択されています。つまり、JavaScriptでの閲覧資格に関わらずVOICEVOXエンジン側で処理がされます。accent_phrasesがリソースを食う場合や、(恐ろしい例え話ですが)インジェクションや任意コマンドの脆弱性があった場合などは、CORSのポリシーはこれを守ってくれません。ブラウザ経由でネットにつないでいる限り、常に任意のリクエストを受け取り、実行する可能性があります。

@Hiroshiba
Copy link
Member

Hiroshiba commented May 5, 2022

まとめとご教示ありがとうございます!!

CORSの仕組みを使えば、Access-Control-Allow-Origin: app://.を設定することでブラウザにレスポンスを返さないようにできる一方、リクエストを受け付けないようにするのは難しいのかなと感じました。
また、サードパーティアプリが利用するHTTPリクエストライブラリから手軽に通信できなくなりそうな気もしました。

POSTリクエストを受け付けないようにする場合は、プリフライトリクエストの仕組みを使うことで防止もできそうだということも理解できました!
ただ、synthesisみたいに全てJSONリクエストにする必要があったり、主要なブラウザの仕様をちゃんと把握したりする必要がありそうで、少し手間かもと感じました。


問題を整理すると、3点考えることがありそうです。

  1. 悪意のあるサイトにレスポンスを返さないようにできるか
  2. 悪意のあるサイトからリクエストを無視できるか
  3. サードパーティーアプリから簡単に利用できるか

いろいろ考えたのですが、「Originがhttp/httpsなら無視する処理をサーバー側に持たせる」のが良いのかもと思いました。
ブラウザ経由(http/httpsプロトコル)なら弾くというブラックリスト方式を取る感じです。
まだ試していませんが、おそらくFastAPI内でリクエストを送ってきたOriginを知る方法はある・・・はず。

ただ、サードパーティーが利用するHTTPリクエストライブラリが、Originをhttpとして送信している可能性もあるかもしれません。
Originが http://localhosthttp://127.0.0.1 、もしくはサーバー側のと同じになっているものは通してもいいかも?

あと、http/https以外に、悪意のあるサイトからlocalhostに対してリクエストを飛ばせる仕組みがあるかもと懸念しています。例えばgit://とか・・・(適当なたとえです)。
そういうのがあった場合はこのブラックリスト方式は無力そうです。

@ts-klassen
Copy link
Contributor Author

ts-klassen commented May 5, 2022

あと、http/https以外に、悪意のあるサイトからlocalhostに対してリクエストを飛ばせる仕組みがあるかもと懸念しています。例えばgit://とか・・・(適当なたとえです)。
そういうのがあった場合はこのブラックリスト方式は無力そうです。

これはイタチごっこの予感がしますね!!

一つ突破する方法を思いつきました。FireFoxでは対策済みでしたが、Google ChromeではOriginを偽装できるかもしれません。

これはGoogle Chromeの脆弱性かもしれないので、この場で手法を公開することは差し控えさせていただきます。(TwitterのDMで送ります。)

この場合、Originはnullを返します。

http, httpsに加え、nullもブラックリストに入れると対策できます。弊害として、ブラウザで開いたローカルのHTMLファイルもエンジンにリクエストを出せなくなります。(fileスキームのOriginはnullを返します。)

@ts-klassen
Copy link
Contributor Author

ts-klassen commented May 5, 2022

Originが http://localhost/http://127.0.0.1/ 、もしくはサーバー側のと同じになっているものは通してもいいかも?

Originが http://localhosthttp://127.0.0.1 とそのポート番号違い(http://127.0.0.1:50021 など)の場合は許可して問題ないと思います。(サーバーと同じOriginとはhttp://127.0.0.1:50021 のことです。許可しないとhttp://127.0.0.1:50021/docs のJavaScriptが機能しなくなります。 同一オリジンなので機能することが判明。CORS関係なし。)
加えて、http://127.0.0.1 からhttp://127.255.255.254 も許可して問題ないと思います。(これらはすべてループバックアドレスです。許可する必要があるかは分かりませんが。)

トリッキーなケースとして、自身のローカルIP(例えば http://192.168.10.102 )も許可して問題ないでしょう。(同様に、許可する必要があるかは分かりませんが。)ただし、その他のローカルIPは許可すべきではありません。(正直、全部不許可でいいと思います。)

また、Originを渡さないリクエストも許可して良いと思います。(Originを渡さないのとOrigin: nullは別物です。後者はnullというOriginを渡しています。そうです。nullは立派なOriginなのです。なんとAccess-Control-Allow-Origin: nullでnullだけ許可できほどに。さて、Originを渡さないリクエストに話を戻しますが、)例えば、Google Chromeでhttp://127.0.0.1:50021/docs にアクセスしたとき、ブラウザはOriginを渡しません。(JavaScriptのリクエストではないので当然です。)一見危険に見えるかもしれませんが、(バックドアが仕掛けられていたり、ポートフォワーディングでもしていない限り)http://127.0.0.1:50021 にOriginを渡さずリクエストを飛ばせるのはユーザー(人間とは限らない)が実行したプログラムだけです。

敗者復活戦

ブラウザの拡張機能としてWEBページの読み上げをする場合、どのようなOriginを渡すか分かりません。(未定義という意味ではなく、私が知らないということです。)なんとなくhttps:// かchrome-extension:// など独自のスキームを返す気がします。無論、そんなスキームは発見次第即ブラックリスト行きです。しかし、利用者の明示的な許可があれば通しても良いでしょう。例えばVOICEVOXエンジンで固有の(予測困難な)トークンを発行し、そのトークンを利用者がコピペできるようにします。そして、ブラックリスト入りしたOriginたちはトークンを渡さないとリクエストを拒否されます。(あるいは無視されます。)したがって、ブラックリスト入りしたOriginを渡すアプリは利用者にVOICEVOXトークンを求める必要があり、利用者はトークンをコピペするかどうかで明示的な許可を与えることができます。

@Hiroshiba
Copy link
Member

Hiroshiba commented May 6, 2022

詳細にありがとうございます!
Origin: nullなど、全く知らない概念があって目からウロコでした。

方針としては、まずレスポンスを返さないようにするのを優先的に直すことを考えると良いのかなと思いました。(辞書などに含まれるかもしれない個人情報を守るのが一番大事だと考えています。)
こちらは、VOICEVOXエディタのみを通すAccess-Control-Allow-Origin: app://.を用意して解決する方法で進めるのが適切かなと感じました。

ちなみに、VOICEVOXエディタがappプロトコルなのはここのコードによるものだと思います。
voiecvoxプロトコルなどに変えられるとおしゃれかもです。よくわかってませんが、こっちも変えないといけないかも・・・?
https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/configuration.html#changing-the-file-loading-protocol

この影響は @ts-klassen さんの仰る通り、ブラウザを介するサードパーティーアプリが動かなくなるかもしれません。
たしかゆかりねっとはブラウザを介する仕組みで、かつユーザーも多いので一番影響があるかもしれません。
とりあえず簡単に迂回できるよう、エンジン側にCORSを無効にするオプションを用意しつつ、しっかりアナウンスするのが良さそうに思います。

CORSを設定できたら、リクエストを弾く(処理をキャンセルする)のを進めるのが良いのかなと思いました。
こちらは、VOICEVOXエディタからのリクエスト・http[s]://localhost:*http[s]://127.0.0.1:*・Origin無しだけを通すホワイトリスト形式を考えています。

予測できる影響は、先ほど同様ブラウザを介するサードパーティーアプリだけですが、予想外の影響があるかもしれないので、こちらの方法はCORS設定ができたあと慎重に行いたいです。

@Hiroshiba
Copy link
Member

@ts-klassen さん、もしよかったらCORS設定を導入するプルリクエスト作成に挑戦してみませんか・・・?👀
このあたり知識がないと難しいのと、エンジン周りのことが詳しい方におまかせできると心強く、お伺いしてみた次第です。

もしわからないこと等がありましたらなんでも聞いて頂けますと幸いです!
どうでしょう・・・?

@ts-klassen
Copy link
Contributor Author

挑戦してみます。

@Hiroshiba
Copy link
Member

おー!!ぜひ…!!
python等でわからない点などあればなんでも聞いていただければ!

@Hiroshiba
Copy link
Member

Hiroshiba commented Oct 23, 2022

いったんこちらのタスクをまとめてみます。

  • app://以外のCORSを無効にする(Access-Control-Allow-Origin: app://.
  • Access-Control-Allow-Originの値を変えられるオプションを用意する
  • 無関係なリクエストを弾くor 実行をキャンセルする(VOICEVOXエディタからのリクエスト・http[s]://localhost:*http[s]://127.0.0.1:*・Origin無しだけを通す)
  • ユーザーに案内する

@ts-klassen
Copy link
Contributor Author

すみません、放置してました。
取り掛かります。編集範囲はrun.pyだけだと思います。

@masinc
Copy link
Contributor

masinc commented Oct 24, 2022

@Hiroshiba さん
TwitterのDMにて、ご連絡させていただきました。

無関係なリクエストを弾くor 実行をキャンセルする(VOICEVOXエディタからのリクエスト・http[s]://localhost:・http[s]://127.0.0.1:・Origin無しだけを通す)

localhostや127.0.0.1ですが、もし本番用途で不要あれば、開発環境限定の環境変数などで許可対応するのがよいかと思いましたがいかがでしょうか。

PRの件ですが、エンジン側についてはともかくエディタ側(特にVue, CSS)の知見が少ないため、全ての対応は難しいと考えています。協力できる範囲であればご相談ください!

@Hiroshiba
Copy link
Member

@ts-klassen さん
おお、ありがとうございます!! 不明な点あればなんでもお聞きください!!

@Hiroshiba
Copy link
Member

@masinc さん
丁寧にDMで教えてくださってありがとうございました!

localhostや127.0.0.1ですが、もし本番用途で不要あれば、開発環境限定の環境変数などで許可対応するのがよいかと思いましたがいかがでしょうか。

VOICEVOXエディタにとってはlocalhostなどは不要なのですが、エンジンだけ使いたいサードパーティアプリではそうでもないのかなーと思っています。
なので本番用途(ビルド済みエンジン)でも環境変数あるいは実行引数で指定して許可できると良いのかなと思っています。

なんとなくですが、指定なしだった場合は「VOICEVOXエディタからのリクエスト・http[s]://localhost:*http[s]://127.0.0.1:*・Origin無し」を通し、指定した場合は指定したものだけ通す、とかが良いのかなぁと想像しています。

PRの件ですが、エンジン側についてはともかくエディタ側(特にVue, CSS)の知見が少ないため、全ての対応は難しいと考えています。協力できる範囲であればご相談ください!

お言葉ありがたいです!
@ts-klassen さんからPR頂いたらレビューをお願いさせていただくかもしれません。その際はぜひ・・・!

@masinc
Copy link
Contributor

masinc commented Oct 24, 2022

@Hiroshiba さん

なので本番用途(ビルド済みエンジン)でも環境変数あるいは実行引数で指定して許可できると良いのかなと思っています。

はい、私もほぼ同様の実装を想定していました。

なんとなくですが、指定なしだった場合は「VOICEVOXエディタからのリクエスト・http[s]://localhost:・http[s]://127.0.0.1:・Origin無し」を通し、指定した場合は指定したものだけ通す、とかが良いのかなぁと想像しています。

なるほど。既存のサードパーティアプリがどのようなものが存在するのかわかっていないのですが、Webブラウザエンジン経由でHTTP通信するアプリ以外は基本的には問題無いと思われますので、オプションでの指定のみで十分対応できるのかと考えていました。
また、指定の有無に関わらず、VOICEVOXエディタとOrigin無しについては無条件許可で良いかと思っています。Webブラウザ上のアプリと違って、ローカルにて実行されているアプリなら任意のリクエストヘッダの設定ができますので。

@Hiroshiba
Copy link
Member

Hiroshiba commented Oct 25, 2022

返信ありがとうございます、助かります!

なるほど。既存のサードパーティアプリがどのようなものが存在するのかわかっていないのですが、Webブラウザエンジン経由でHTTP通信するアプリ以外は基本的には問題無いと思われますので、オプションでの指定のみで十分対応できるのかと考えていました。

たしかにブラウザ経由以外は全く問題なさそうに思いました。
ブラウザ利用するパターンは2つあって、1つがゆかりねっと(chromeで音声認識したテキストで合成エンジンAPIを叩く仕組み・・・なはず)、2つ目がブラウザの文字読み上げです。
前者はlocalhost由来な気がしますが、後者は任意のドメインから飛んできそうですね・・・。
まだちゃんと調べられてませんが、オプションでの迂回作を用意しておきたいところです。

また、指定の有無に関わらず、VOICEVOXエディタとOrigin無しについては無条件許可で良いかと思っています。

たしかにそうですね!localhostも無条件許可で良いかも・・・?

@Hiroshiba
Copy link
Member

@masinc さん
CORS設定まわりのプルリクエストについてレビュー頂けると心強いです・・・!

@Hiroshiba
Copy link
Member

こちらのissueですが、無関係なリクエストを弾くor 実行をキャンセルするまでの議論を分断したくないので、そこもissueに含む形にタイトルを修正させていただこうと思います 🙏

@Hiroshiba Hiroshiba changed the title CORSの範囲をどうするべきか CORSの範囲を制限し、無関係なリクエストを弾く Oct 31, 2022
@Hiroshiba Hiroshiba added 機能向上 and removed 要議論 実行する前に議論が必要そうなもの labels Oct 31, 2022
@Hiroshiba
Copy link
Member

もしよければ 無関係なリクエストを弾くor 実行をキャンセルするもPRお願いできると嬉しいです・・・!
(PR内容が被らないようにするために、このissueで宣言するか、空のDraft PRを作成していただければと!)

@ts-klassen
Copy link
Contributor Author

ts-klassen commented Oct 31, 2022

作業に入る前に、現状のおさらいです。

CORSを設定した時点で、情報をJavaScriptに抜き取られないようにするという目標は達成しました。

Originでリクエストを弾く処理をつけようという方針になった理由に、プリフライトリクエストを飛ばさないタイプのリクエストが脅威たりうると考えたからです。

まず、Originでリクエストを弾く処理でGETリクエストは防げません。(今気づいた。どうしよう…)
例)↓ここをクリック
http://localhost:50021/is_initialized_speaker?speaker=0&core_version=0.13.2

一方、POSTなどは対策できそうです。
現状、VOICEVOXを起動した状態で次のようなボタンを押すとCPUリソースが食われます。
https://voicevox.su-shiki.com/su-shikiapis/localtests/form_post.html

試しにVOICEVOXの代わりにnetcatを50021ポートで待機させると、

Bashコマンド「netcat -l 50021」の結果
netcat -l 50021
POST /initialize_speaker?speaker=0 HTTP/1.1
Host: localhost:50021
Connection: keep-alive
Content-Length: 0
Cache-Control: max-age=0
sec-ch-ua: "Google Chrome";v="105", "Not)A;Brand";v="8", "Chromium";v="105"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Linux"
Upgrade-Insecure-Requests: 1
Origin: null
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 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: cross-site
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9

のようにOrigin: nullを返しながらも有効なPOSTリクエストを出していることがわかります。
(面白いことに、https://voicevox.su-shiki.comに設置したトラップなのにOriginはnullです。)

本質的にはクロスサイトリクエストフォージェリ(CSRF)対策みたいなものだと思いますが、トークンを発行するような破壊的変更は避けたいです。
そこで、許可されていないOriginを渡すリクエストは弾くという方針を考えています。

もっと良い方法があれば是非!

@Hiroshiba
Copy link
Member

まとめありがとうございます!
仰る通り、許可されていないOriginを渡すリクエストは弾くというのが筋が良いのかなと感じました!

ちょっと認識が食い違っているのかもなのですが、この場合でもGETリクエストは防げない(弾けない)感じでしょうか 👀

@masinc
Copy link
Contributor

masinc commented Nov 1, 2022

まず、Originでリクエストを弾く処理でGETリクエストは防げません。(今気づいた。どうしよう…)

API実装気を付けないとDoSできちゃいますね。。

本質的にはクロスサイトリクエストフォージェリ(CSRF)対策みたいなものだと思いますが、トークンを発行するような破壊的変更は避けたいです。
そこで、許可されていないOriginを渡すリクエストは弾くという方針を考えています。

私もこの方針には同意見ですが、トークンを利用しないとミドルウェアなどでの一元的なCSRF対策は厳しそうと思いました。
あまり自信ないですが、GETメソッドのCSRF緩和策を取るとしたらこんな感じでしょうか。

  • 初回を除きレスポンスをキャッシュした結果を返す。(リクエスト毎にCPU/メモリ/IO等のリソースをほぼ利用しない)
  • ↑が難しい場合、連続リクエストに対しての何かしらの対策をする。両方できるとより良い。

ちょっと認識が食い違っているのかもなのですが、この場合でもGETリクエストは防げない(弾けない)感じでしょうか 👀

ブラウザの GET リクエストは Originヘッダを送ってこない様々なパターンがあり、それを正確に対応するのは厳しそうです。この記事がわかりやすいかもです。
CORSの仕様はなぜ複雑なのか - CORSが使われないとき

@Hiroshiba
Copy link
Member

@masinc さんありがとうございます!!
なるほど、ブラウザがGETリクエストはいろんなのがあるんですね・・・・・。存じませんでした。

キャッシュと連続リクエスト対策、ありだと感じました。
実装コストもあると思うので必須ではなく、とりあえずOrigin制限しつつ、余力があるなら別のPRで対応とかもありなのかなと思いました!

@Hiroshiba
Copy link
Member

CORS設定を取り込んだリリースを作ってみました。7zを展開したらexeが出てきます。ちゃんと弾かれるはず・・・?
https://github.com/VOICEVOX/voicevox_engine/releases/tag/0.14.0-preview.1

@ts-klassen
Copy link
Contributor Author

取り掛かります。

@Hiroshiba
Copy link
Member

おお!!よろしくお願いします・・・!

@Hiroshiba
Copy link
Member

PR&レビューありがとうございます!!

現在頂いたPRをマージしたmasterブランチでプレビュー版をビルド中です。
おそらくこのissueの大きな課題であった2つの問題は解決したのかなと思っています!
プレビュー版で問題なく動作することを確認でき次第、こちらのissueを閉じたいと思います!
おふたりともありがとうございました!!

@Hiroshiba
Copy link
Member

今回の仕様変更の影響をユーザーの方に案内をしたいのですが、ちょっと複雑な段取りを取ろうと思います。

先に自分 @hiho_karuta のツイッターで該当ツールが無いか聞く
↓
該当ツール開発者の方に事前に連絡する
(chrome拡張が2つ見つかったのでその方には連絡したい)
(あとゆかりねっと)
↓
「セキュリティアプデしたためサードパーティツールに影響があります。開発者の方はこちらをご確認ください。」
&影響があるサードパーティツールなど一覧の画像添付
で公式ツイート

というのも、これらの要件を満たしたいためです。

・愉快犯を防ぎたい(ので詳細を伏せる)
・ブラウザを介するサードパーティツールに影響があると気づける
・大多数のユーザーには影響がない
・WEB版VOICEVOXは影響ないことがわかる(ユーザー数が多く不安になる方がいらっしゃりそう)

また、普段機能追加PRをマージしたときはツイッターでPRリンク付きツイートをさせていただいている()のですが、もしツイートすると愉快犯が現れてしまうかもと考えています。
そのため、PRを出していただいた @ts-klassen さんには申し訳ないのですが、今回はPRリンク付きツイートをしない方針を取らせていただきたいと考えています。
申し訳ないですが、ご理解いただけますと幸いです 🙇🙇

@Hiroshiba
Copy link
Member

サードパーティアプリ開発者の方向けに案内する予定のissueを作成しました。もし語弊などあればご指摘頂けると嬉しいです・・・!

@ts-klassen
Copy link
Contributor Author

例えば「VoiceSissy for VOICEVOX」というChrome拡張機能では、
Origin: chrome-extension://mjfpekkpgmdihmfemmiodkhiebgbjnhm
というヘッダを飛ばしていました。

これについて、拡張機能の開発者が対応できることはなく、利用者自身が上記のOriginを許可する必要があります。

VOICEVOXエディタに許可するOriginを入力できる機能をつける必要があるのではないでしょうか。

@Hiroshiba
Copy link
Member

情報ありがとうございます!!
迂回策はいくつか思いつくので案内issue側に列挙してみたいと思います。
その辺りどうしていくのが良いか含めて開発者の方に聞いてみたいと思います。

@Hiroshiba
Copy link
Member

エンジン側に設定機能を持たせる方法がありました。

例えばStreamlitでwebページを作成して、localhost:50021/gui/homeとかにアクセスして設定を行ってもらうなどの方法が思いつきます。

エンジンだけで音声合成チェックする需要もあるはずですし、エンジン側にGUIをもたせるのはいろいろ便利そうです。

@aoirint
Copy link
Member

aoirint commented Dec 4, 2022

#392 (comment)

ブラウザ拡張についてですが、Google Chrome 101+(>=の意。Chrome 101は2022-04リリース。2022-12-04時点の最新の安定版はChrome 108)およびManifest V3で、declarativeNetRequestという機能によって、Originヘッダを削除する形で対応できるかもしれません(拡張機能の開発者による対応)。

declarativeNetRequestを使用するには、manifest.jsonpermissionsdeclarativeNetRequestWithHostAccessという項目(Chrome 96+)を追加する必要があります。ちなみにFirefoxで現行のManifest V2では、webRequest.onBeforeSendHeadersという機能で同じことができると思います(ただしChromeは、2022年1月からManifest V2の非推奨化プロセスに入っており、2024年1月に廃止する予定です)。

またCORSについては、manifest.jsonhost_permissionshttp://127.0.0.1:50021/*を含めることで、ブラウザ拡張機能の特権としてCORSを回避できます。CORSを無視するため、VOICEVOX ENGINEの起動引数--cors_policy_modeは影響しません。

XMLHttpRequest and fetch access to those origins without cross-origin restrictions (though not for requests from content scripts, as was the case in Manifest V2).

具体的な実装例を置いておきます。

manifest.jsonservice_workerに指定したJavaScriptファイルで、以下の処理を実行すると、実行した拡張機能によるVOICEVOX ENGINE http://127.0.0.1:50021宛のHTTPリクエストから、Originヘッダを削除できます。initiatorDomains(Chrome 101+)は、書き換えるHTTPリクエストの送信元を拡張機能自身のみに絞ることを意図しています(拡張機能自身以外が送信したHTTPリクエストのOriginを削除しないようにするため)。

// license: CC0
chrome.declarativeNetRequest.updateDynamicRules({
  removeRuleIds: [1],
  addRules: [
    {
      id: 1,
      priority: 1,
      action: {
        type: 'modifyHeaders',
        requestHeaders: [
          { 'header': 'Origin', 'operation': 'remove' },
        ],
      },
      condition: {
        urlFilter: '127.0.0.1:50021/*',
        initiatorDomains: [chrome.runtime.id], // chrome-extension://{chrome.runtime.id}
      },
    },
  ],
})

@Hiroshiba
Copy link
Member

なるほど!!chrome拡張側のコード変更でCORS回避できるんですね!!盲点でした。

拡張機能開発者の目線だと、ユーザーにCORSの設定をしてもらわなくて済むのでこちらの方が好まれそうですね。
「CORSの設定方法」みたいなドキュメントを用意して、そこで↑のコメントをちょっと言及してあげると良いかもですね。

いろんな需要があると思う(普通にwebページからリクエスト飛ばしたいとか)ので、CORSを設定する方法も提供できると嬉しいのかなと思いました!

@Hiroshiba
Copy link
Member

が解決されたので案内を進めていきたいと思います!

discordで聞いた感じだと、chrome系アプリの他には影響ない感じでした。
たぶんゆかりねっと周りも大丈夫です。

他に影響ありそうなシステムがないかを僕の個人アカウントからツイートして公募中です。
https://twitter.com/hiho_karuta/status/1610074942703558656

ちょっと時間たってからVOICEVOX公式から案内したいと思います!

@Hiroshiba
Copy link
Member

こちら確か案内も済ませて、特に問題なく完了した記憶があります。
のでクローズします。実装やディスカッションとても心強かったです、ありがとうございます!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants