HTTPヘッダを雰囲気で読んでしまっている。実際にどんなヘッダが使われていて、どのように使われているか調べてみるため、twitterのHTTPヘッダをMDN Web docsと照らし合わせながら1つずつ見てみた。セキュリティ的に公開してはいけなさそうな部分はXXXXとしている。
ヘッダ全体
$ curl -I -XGET https://twitter.com/
HTTP/2 200
cache-control: no-cache, no-store, must-revalidate, pre-check=0, post-check=0
content-security-policy: connect-src 'self' blob: https://*.giphy.com https://*.pscp.tv https://*.video.pscp.tv 省略 ; default-src 'self'; form-action 'self' https://twitter.com 省略; font-src 'self' https://*.twimg.com; frame-src 'self' https://twitter.com 省略 ; img-src 'self' blob: data: https://*.cdn.twitter.com 省略; manifest-src 'self'; media-src 'self' blob: 省略; object-src 'none'; script-src 'self' 'unsafe-inline' https://*.twimg.com 省略 'nonce-MThhNzMwYjItNmI2ZS00YmNiLTgyNjctYzNhNDJmNjhmNTE0'; style-src 'self' 'unsafe-inline' https://*.twimg.com; worker-src 'self' blob:; report-uri https://twitter.com/i/csp_report?a=O5RXE%3D%3D%3D&ro=false
content-type: text/html; charset=utf-8
cross-origin-opener-policy: same-origin
date: Tue, 03 Nov 2020 13:37:45 GMT
expiry: Tue, 31 Mar 1981 05:00:00 GMT
last-modified: Tue, 03 Nov 2020 13:37:45 GMT
pragma: no-cache
server: tsa_m
set-cookie: personalization_id="XXXX"; Max-Age=63072000; Expires=Thu, 03 Nov 2022 13:37:45 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
set-cookie: guest_id=XXXX; Max-Age=63072000; Expires=Thu, 03 Nov 2022 13:37:45 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None
strict-transport-security: max-age=631138519
x-connection-hash: XXX
x-content-type-options: nosniff
x-frame-options: DENY
x-powered-by: Express
x-response-time: 155
x-xss-protection: 0
cache-control: no-cache, no-store, must-revalidate, pre-check=0, post-check=0
no-storeはキャッシュをしないことを意味する。twitterのような常に情報が流動的なサイトはno-storeにする。
良い例:
Cache-Control: no-store
no-store以外にもいろいろと付いているが、キャッシュをしない場合はこれだけで十分だとMDN Web docsには書かれていた。
pre-check=0, post-check=0はIEのキャッシュ制御用らしい。*1
見やすく適宜改行した。
content-security-policy:
connect-src 'self' blob: https://*.giphy.com https://*.pscp.tv 省略 ;
default-src 'self';
form-action 'self' https://twitter.com 省略;
font-src 'self' https://*.twimg.com;
frame-src 'self' https://twitter.com 省略 ;
img-src 'self' blob: data: https://*.cdn.twitter.com 省略;
manifest-src 'self';
media-src 'self' blob: 省略;
object-src 'none';
script-src 'self' 'unsafe-inline' https://*.twimg.com 省略 'nonce-MThhNzMwYjItNmI2ZS00YmNiLTgyNjctYzNhNDJmNjhmNTE0';
style-src 'self' 'unsafe-inline' https://*.twimg.com;
worker-src 'self' blob:;
report-uri https://twitter.com/i/csp_report?a=O5RXE%3D%3D%3D&ro=false
特定のサイトのリソースしか読み込ませないためのヘッダ。リソースの種類ごとに細かく指定できる。report-uriはCSP(Content-Security-Policy)違反をしたときにブラウザが報告するURLが示されるが、非推奨でreport-toへと段階的に変更していくようだ。
content-type: text/html; charset=utf-8
おなじみコンテンツタイプヘッダ。charsetについて調べてみた。
仕組みを理解しており、可能であるなら、どのようなコンテンツであれHTTPヘッダーでエンコーディングを指定しましょう。しかし必ず同時に文書内での指定も行うべきです。
HTMLで文字エンコーディングを指定する
指定しないとどうなるかは実装次第のようで、必ず指定したほうがよいようだ。また、<meta>タグにもcharsetが指定できるが、優先度はContent-Typeのcharsetのほうが高い。
cross-origin-opener-policy: same-origin
twitterから別のサイトを開いたときに、別のサイトがwindow.openerからtwitterのプロパティを読み込めてしまうのはセキュリティ上よろしくないため、それを許可しないという意味。詳しくは以下のサイトを参照。
asnokaze.hatenablog.com
date: Tue, 03 Nov 2020 13:37:45 GMT
日付。禁止ヘッダーと言われ、ユーザーエージェントが自動挿入するヘッダで、アプリケーション側で特定の日付を指定したりはできない。
expiry???
expiry: Tue, 31 Mar 1981 05:00:00 GMT
有効期限を示すExpiresならあるが、expiryは謎。twitterが独自に利用しているヘッダ?expiresのtypoだとしても、Cache-Controlでno-storeが指定されているため、コンテンツがキャッシュされることはない。
last-modified: Tue, 03 Nov 2020 13:37:45 GMT
オリジンがそのファイルを最後に更新したとする日時が入る。If-Modified-Sinceなどで利用される。
pragma: no-cache
現在HTTP/1.0との広報互換性のために利用されるヘッダで、ここではCache-Control: no-storeと同じことを表している。おそらく未だHTTP/1.1対応すらしていないブラウザが一定数あるのだろう。
server: tsa_m
レスポンスを返したサーバで動くソフトウェアを表している。詳細に書くと攻撃に利用される可能性があるため、あまり詳細に記述しないほうがよい。tsa_mが何のソフトウェアを表しているかはよくわからなかった。
見やすいように適宜改行した。
set-cookie:
personalization_id="XXXX";
Max-Age=63072000;
Expires=Thu, 03 Nov 2022 13:37:45 GMT;
Path=/;
Domain=.twitter.com;
Secure;
SameSite=None
Cookieをセットしている。
- personalization_idだけがCookie本体で、それ以外はCookieに関する制御をしている。
- DomainとPathでtwitter.comとそのサブドメインの場合のみCookieを送信できるようにしている。
- 殆どの場合Path=/。
- /が少し分かりづらいが、/*という意味。
- SecureによってHTTPSでないとCookieを送信できない。
- SameSite=Noneはオリジンをまたいでクッキーを送信して良いこと表している。
- Noneを指定する場合、Secureが必須。
- CSRF攻撃の予防のためにSameSiteがあるわけだが、Noneでも大丈夫なのだろうか。
strict-transport-security: max-age=631138519
HTTPではなくHTTPSを利用するように促すヘッダ。リダイレクトとは違い、ブラウザが改めてHTTPSとして接続し直す。
利用例はMDN Web docsで示されている例がわかりやすい。HTTPでリクエストが来たとき、HTTPSのリダイレクトが記述されたHTTPレスポンスを返すのは、暗号化されていないため悪意のあるサイトへリダイレクトするよう書き換えられる可能性がある(中間者攻撃)。だからHTTPではなく新しくHTTPSで接続し直してとだけ返信するStrict-Transport-Securityが有用。
x-connection-hash
x-connection-hash HTTP response header
twitterが独自に定義しているヘッダだと思われる。
x-content-type-options: nosniff
Content-Typeで指定したMIMEをブラウザが書き換えてはいけないと指定している。MIME Confusion Attackという攻撃を防げる。詳しくは以下のサイトで。
mozsec-jp.hatenablog.jp
x-frame-options: DENY
ページが<frame>や<iframe>、<embed>、<object>の中に表示することを禁止している。クリックジャッキング攻撃を防げる。
x-powered-by: Express
一部のソフトウェアで送信されるヘッダーのようで、送信したソフトウェアがExpressであることを表す。
X-Response-Time
x-response-time: 155
以下のサイトによると、リクエストがミドルウェアに来てから、ヘッダが書かれるまでの時間を記録している。twitter以外でも使われていた。
X-Response-Time HTTP response header
x-xss-protection: 0
XSSを検出しても何もしないことを意味している。CSPで代用でき、レガシーブラウザをサポートしない限りはなくてもよいヘッダーのようだった。
twitterにあったHTTPヘッダーの中で以下のものはCORS セーフリストレスポンスヘッダーと呼ばれる。
- Cache-Control
- Content-Type
- Expires
- Last-Modified
- Pragma
CORS(オリジン間リソース共有)によってオリジンをまたいでHTTPクエリがとんでも、これらのヘッダーは含ませてもよいとされる。逆に、SetCookieなどは含ませてはいけない。
最後に
調べてみて、どこまでが実用上必要で、どこまでがレガシーをサポートするためなのかがわかって勉強になった。別のサイトについても調べてみたい。
参考
Cross-Origin-Opener-Policyについて - ASnoKaze blog
キャッシュについて整理 - Qiita
CookieのDomainとPath属性の仕様について調べた話 - emahiro/b.log
HTMLで文字エンコーディングを指定する
【翻訳】Firefox における MIME Confusion Attack の防止 - Mozilla Security Blog 日本語版
HttpFoundationで画像の条件付きGETを実装してみる (Symfony Advent Calendar JP 2012 - Day 22) - k-holyのPHPとか諸々メモ