対象読者
- cookieはwebサーバーがログインとかを認識するためによく使われるもの。
- cookieはブラウザに保存されていて、webサーバーに送られる。
- webサイトの構築をしたことがある。
↑こんな感じの雰囲気がわかっている前提で話を進めます。
cookieの基本動作
(1) ウェブブラウザで サイトA にアクセスする
ウェブブラウザで、例えば example.com
というドメインにアクセスします。
(2) サーバーは、要求されたリソース(ページ)を返す
サーバーは、要求されたリソース(ページ)を返しますが、このレスポンスにおけるヘッダ部にcookie(と呼ばれるデータ)をセットして返すことができます。
例えば、ログインした後に、ログインしたことを示すデータをcookieにセットして返したりします。
webサイトを閲覧したら必ずcookieが返ってくるわけではないです。
Set-Cookie
というフィールド名の右側に、「名前=値;」というフォーマットで各種情報がセットされます。ウェブブラウザはこの情報を保存することになります。
(3) ウェブブラウザで、再度 サイトA にアクセスする
ウェブブラウザで、再度 サイトA にアクセスすると、先程受け取ったcookieの情報がリクエストヘッダ部に自動的にセットされて送られます。
このときに送る内容は、サイトAで受け取ったcookieのみです。
他のサイトで受け取ったcookieは送信されません。
ただし、Aサイトを経由して、Bサイトへのリクエストをする際は、cookieが送信されます。
chromeを使っているなら、ブラウザで以下のリンクを見てみてください。
ご自身の閲覧したwebサイトに関連するcookie情報が見れます。
chrome://settings/siteData
cookieの値が盗まれたら、自分のアカウントが乗っ取られることもある
先程、他のサイトで受け取ったcookieは送信されないと言いましたが、
ウェブブラウザを見ている人が意図しない方法でcookieを他のサイトへ送信させ、悪用することができます。
このwebの脆弱性または攻撃手法をCSRF(クロスサイトリクエストフォージェリ)と言います。
悪用の内容としては、cookieに含まれるログイン情報を使って、そのユーザーになりすますことができます。
つまり、ログインしている状態に偽装することができるんです。
なので、そのサイト内のアカウントの情報を変更したり、投稿した内容を変更したりができるようになります。
これはセッションジャックと言われます。
cookieの盗み方
さまざまあるかと思いますが、有名な2つを取り上げます。
セキュリティに問題のあるネットワークを使用している場合
セキュリティに問題のあるネットワークを通過するcookieは盗聴可能です。無線LANは、まさにそのようなネットワークの一例です。特に暗号化されていない無線LANでは、接続されているクライアントのすべてのトラフィックを簡単に傍聴できてしまいます。
Webアプリケーションの開発者は、SSLによって安全な接続の提供をする必要があります。
ネットワークを傍受して内容を閲覧できても、内容自体をSSLによって暗号化してしまえば、内容を知ることは難しくなります。
何らかのコードをWebアプリケーションに注入する
攻撃者が何らかのコードをターゲットのWebアプリケーションに注入し、cookieを盗むだけでなく、
標的ユーザーを偽のWebサイトに誘い込む、攻撃者の利益になるような広告を表示する、Webサイトの要素を書き換えてユーザー情報を盗み出す、あるいはWebブラウザのセキュリティホールを経由して邪悪なソフトウェアをインストールすることもできます。
これをXSS(クロスサイトスクリプティング)攻撃と言います。
具体的な方法を2点あげます。
- HTML内にjavascriptを埋め込んで、直接ブラウザのcookie情報を抜き出す。
- 訪問サイトから別のドメインにリクエストを送るときに、cookieが送られる仕組みを利用し、偽サイトへ情報を流す。
この件に関しては、利用者側では対策することはできません。怪しいサイトは使うなということだけです。
webアプリ開発側が対策をする内容になります。対策方法は、事項に譲ります。
詳細については、Ruby on Rails公式の記事に詳しく書かれております。
こちらのブログもわかりやすいです。
cookieを盗まれない方法
有名なものを挙げておきます。
いずれもwebアプリ開発者がアプリに対して設定するものです。
SameSite属性を追加する
SameSite属性は、今開いているページのドメインから、別のドメインにリクエストを送る際に、クッキーをセットするかどうかを制御することができます。
Strict
:例えば、Aサイトでログイン中だった場合に、Bサイト上に用意されたAサイトへのリンクをクリックした場合、Aサイトにクッキーが送られませんので、Aサイトに対して未ログイン状態の扱いでページの遷移が行われます。当然、この動作は不便な面もあります。
Lax
:POSTメソッドを使ったフォーム、imgタグ、iframe、XMLHttpRequests などによる他ドメインへのリクエストにはクッキーはセットされません。
None
:従来どおりの動作(クッキーを送る)
ブラウザの仕様で、特に指定がなければLax
にするような仕様になっていることもあります。
HttpOnly属性を追加する
Cookie属性としてこれを付与するとJavaScriptからアクセスできなくなります。
XSS攻撃によるjavascriptでcookieを盗むことを防ぐことができます。
Secure属性を追加する
Secure属性を設定しない場合、Cookieは接続が https なのか http なのかには関係なく送信します。
http通信であれば通信経路上でにいる第三者からも簡単に盗聴できます。
フォームにCSRFトークンを設置する
webアプリケーションのフォームにトークンを設置し、CSRF攻撃を防ぐというものです。
様々なフレームワークで実装されているので、気づかずに対策ができていることもあると思います。
主な流れとしては、以下です。
- webアプリケーションがワンタイムトークンを発行し、ユーザーのセッションに保存。
- HTMLフォームなどにhiddenで埋め込む。(フレームワークが自動で設置する場合もある)
- リクエスト先では、セッションに保存したトークンと送信されたトークンが一致するか確認を行い、
一致しない場合は不正なリクエストとして扱う。
HTMLフォームへのトークン設置は、コードで表現すると以下のようになります。
こちらは、NextjsというフレームワークにNext-Authという認証用のライブラリを利用した一例です。
サーバーサイドで生成した一回限り有効なトークンを、hiddenタグに設置し、ページ表示しています。
※defalutValueは、HTMLでいうところのvalue
タグと置き換えて考えてください。
import { getCsrfToken } from "next-auth/react"
export default function SignIn({ csrfToken }) {
return (
<form method="post" action="/api/auth/callback/credentials">
// ②defaultValueに動的に生成されたCSRFトークンを設置している
<input name="csrfToken" type="hidden" defaultValue={csrfToken} />
<label>
Username
<input name="username" type="text" />
</label>
<label>
Password
<input name="password" type="password" />
</label>
<button type="submit">Sign in</button>
</form>
)
}
export async function getServerSideProps(context) {
return {
props: {
// ①サーバーサイドでCSRFトークンを取得している
csrfToken: await getCsrfToken(context),
},
}
}
CSRFトークンは、webサーバーに保存されているものではありません。
フォーム送信時に、cookie内のCSRFトークンと、フォームで送信されたCSRFトークンが一致するか見ているだけです。
参考
https://laboradian.com/same-site-cookies/
https://yamory.io/blog/about-csrf/