Skip to main content

セッション管理のセキュリティ:ユーザーセッションを保護するベストプラクティス

著者
blog feature pypi spoof

2023年7月11日

0 分で読めます

セッション管理のセキュリティは、Webアプリケーション開発に欠かせない要素です。ユーザーセッションを保護し、不正アクセスを防ぎます。セッションを管理することで、機密性の高いユーザーデータの機密性、完全性、可用性を確保できます。また、ユーザーのプライバシー全般を保護します。これは、アプリケーションへの信頼を維持するうえで不可欠です。

セッションを安全に管理するには、ユーザーがログアウトしたときやセッションが終了したときに、セッショントークンを破棄するプロセスを確立します。この方法により、通信を盗聴している人や、ユーザーになりすまそうとする人による不正アクセスを防止できます。

この記事では、セキュアなセッション管理を実装する方法と、そのためのベストプラクティスを実践的に解説します。

セッション管理のセキュリティを理解する

ドメインレベルでのセッション管理の重要性を理解するために、基本的なログイン、ログアウト、セッション管理機能を備えたシンプルなNode.jsアプリケーションを作成します。

前提条件

このチュートリアルを進めるには、次のものが必要です。

このデモでは、Windowsオペレーティングシステムと関連するコマンドを使用します。

プロジェクト全体の概要については、完全なコードをご覧ください。

Node.jsでセッションを実装する

まず、コマンドプロンプト(CMD)を開き、以下のコードを使ってsessionsというフォルダーを作成し、そのフォルダーに移動してアプリケーションを初期化します。

mkdir session 
cd sessions
npm init -y

上記のコマンドを実行すると、プロジェクトにインストールされているすべての依存関係を記録したpackage.jsonファイルが自動生成されます。

次に、目的の機能を実現するため、いくつかのパッケージをインストールします。Webサーバーを作成するフレームワークのexpressと、セッションミドルウェアの作成に必要なサーバーサイドHTTPフレームワークのexpress-sessionです。以下のコードを使って、両方のパッケージをインストールします。

npm i express express-session

次に、Visual Studio Codeでプロジェクトを開きます。

アプリケーションのロジックを記述するため、index.jsファイルを作成します。CMDでtouch index.jsコマンドを実行してindex.jsファイルを作成し、以下のコードでindex.jsファイルを更新します。

const express = require('express'); 
const session = require('express-session');

const  app = express();
const port = 3001;

// Session Middlewares, and Login and Logout Routes go here
app.use(session({
  secret: '38sisjsk92',
  resave: false,
  saveUninitialized: true
}));

// URL-encoded middleware
app.use(express.urlencoded({ extended: true }));

app.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});

上記のコードスニペットは、Node.jsアプリケーションの初期コードです。プロジェクトの本体でExpressアプリのセッションミドルウェアを設定します。

URLエンコードミドルウェアは、Express.jsアプリケーションでURLエンコードされたフォームデータへの受信リクエストを処理します。

次に、index.jsファイルのミドルウェアの下に以下のコードを追加し、セッション中のユーザーのログインとログアウト機能を処理します。

// Routes
app.get('/', (req, res) => {
  if (req.session.username) {
	res.send(`Welcome to the page, ${req.session.username}! <a href="/logout">Logout</a>`);
  } else {
	res.send('Welcome, Please <a href="/login">login</a>.');
  }
  console.log(req.session)
});

app.get('/login', (req, res) => {
  if (req.session.username) {
	res.redirect('/');
  } else {
	res.send(`
  	  <form method="post" action="/login">
<input type="text" name="username" placeholder="Username" required>
    		<br /> <br />
    		<input type="text" name="password" placeholder="Password" required>
    		<br /> <br />
    		<button type="submit">Login</button>
  	  </form>
	`);
  }
});

// validate your username and password here
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  req.session.username = username;
  req.password = password;
  res.redirect('/');
});

app.get('/logout', (req, res) => {
  req.session.destroy();
  res.redirect('/');
});

先に進む前に、上記のコードで行われていることを簡単に確認しましょう。

  • 最初のルートapp.get('/')は、アプリケーションのルートURLへのリクエストを処理します。セッションにユーザー名が保存されているユーザーを歓迎し、ログアウト用のリンクを表示します。それ以外の場合は、ログインを促します。

  • 2番目のルートapp.get('/login')は、ログインURLへのリクエストを処理します。

  • 3番目のルートapp.post('/login')は、ログインフォームの送信を処理します。

  • 4番目のルートapp.get('/logout')は、ログアウトURLへのGETリクエストを処理します。ユーザーのセッションを破棄し、ルートURLにリダイレクトします。

変更をすべて保存し、ターミナルで次のコマンドを実行してアプリケーションを起動します。

node index.js

ポート3001でリッスンしているため、ブラウザーでhttp://localhost:3001/を開きます。このURLにアクセスすると、シンプルなログインリンクが表示されます。

「ようこそ!ログインしてください。」というメッセージが表示されたシンプルなWebページ

ログインリンクをクリックして名前を入力します。次のランディングページが表示されます。

「ようこそ、John Doeさん!」と「ログアウト」リンクが表示されたWebページ。

ターミナルを確認してみましょう。次のようなセッションオブジェクトが表示されます。

ポート3001でリッスンしているNode.jsサーバーと、cookieおよびユーザー名「John Doe」を含むセッションオブジェクトを表示したターミナル。

最初のプロパティはパスです。セッションCookieの有効なパスを表します。次に、セッションの有効期限、Cookieの有効期間、そしてクライアント側の攻撃(クロスサイトスクリプティング(XSS)など)を防ぐためにhttpOnly属性がtrueに設定されていることが確認できます。セッションオブジェクトには、現在のユーザー名も表示されます。

Webアプリケーションにおけるセッション管理とその役割

サンプルアプリケーションを用意できたところで、セッション管理の仕組みと、セッションを安全に管理することが非常に重要な理由を確認しましょう。

Webアプリケーションでは、ユーザーとアプリケーション間のやり取りに機密情報が含まれることがよくあります。ここではパスワードが該当します。セッション管理は、認証済みユーザーとのやり取りを安全にし、セッション中に提供される情報を常に保護します。これを怠ると、セッション管理の脆弱性につながるおそれがあります。

よくある脆弱性には、次のようなものがあります。

  • セッションハイジャック — 攻撃者がセッショントークンを盗んだり推測したりして侵害し、所有者になりすましてサーバーに不正アクセスする攻撃です。攻撃者がこれを実行する方法はいくつかあります。その1つがXSSの利用です。ユーザーがアクセスするWebアプリケーションでhttpOnly属性がfalseに設定されていると、クライアント側からのセッションハイジャックのリスクが非常に高くなり、XSS攻撃を受ける隙が生じます。

  • 中間者(MITM)攻撃 — これもセッションハイジャックの一種です。ユーザーとサーバー間の通信を傍受してセッションIDを取得することで、悪意のある攻撃者がセッションにアクセスできます。セッションIDが平文で送信されると、データ通信路で傍受した相手に簡単に読み取られてしまいます。

  • セッション固定化 — 攻撃者が有効なユーザーセッションを乗っ取り、既存のセッションIDを使ってサーバーにアクセスするようユーザーを誘導する攻撃です。セッションにアクセスするユーザーが、セッションIDを使って適切に検証されていない場合に主に発生します。また、攻撃者が脆弱なセッションIDをURLに含めて被害者に送信した場合にも発生する可能性があります。

安全でないセッション管理プロセスには、深刻なリスクが伴います。悪意のある攻撃者は、入手した機密データを使って詐欺や窃盗を行い、特権情報にアクセスする可能性があります。こうしたセキュリティ侵害はユーザーに甚大な被害をもたらし、アプリケーションへの信頼を損なうおそれがあります。

幸い、セッション管理プロセスを保護するための対策はいくつかあります。以下では、その方法をいくつか紹介し、実装方法を説明します。

Cookieを使うと、セッションを安全に管理できます。セッション管理では、Cookieを使ってユーザーとサーバー側のセッションオブジェクトを紐付けられます。一意の識別子があるため、Cookieにはユーザーが訪れた各インスタンスでの関連アクティビティが記録されます。

たとえば、ユーザーがアプリケーションにアクセスしてログインすると、セッションオブジェクトが作成され、セッションIDが割り当てられます。CookieにはセッションIDが保存され、レスポンスとしてブラウザーに送信されます。ユーザーがリクエストを送ると、ブラウザーはCookieを送信します。サーバーはそのCookieでセッションIDを確認し、対応するオブジェクトを取得します。これにより、サーバーはユーザーを検証し、異なるページやリクエスト間でセッションを維持できます。

そのため、システム開発者、アプリケーションアーキテクト、管理者は、Cookieを可能な限り安全に使用できるようにする必要があります。

Cookieを保護する方法の1つは、セキュアフラグを設定し、HTTPS経由のリクエストのみを許可することです。送信パケットは平文ではなく読み取れないため、ネットワーク上のユーザーを通信経路上のMITM攻撃から保護できます。

MITM攻撃に加え、Cookieはネットワーク経由でデータパケットを送信するため、XSS攻撃にも脆弱です。HttpOnlyフラグを使用するとJavaScriptによるセッションCookieの読み取りを防ぎ、こうした攻撃を防止できます。

このフラグを実装するには、セッションオブジェクトのHttpOnlyがブール値のtrueに設定されていることを確認する必要があります。

ターミナルで以下のコマンドを実行し、Cookieを保存するミドルウェアのcookie-parserと、脆弱なバージョンのパッケージbcryptをインストールします。

npm i cookie-parser bcrypt@v4.0.1

次に、index.jsファイルのコードを以下のように更新します。

const cookieParser = require('cookie-parser');

// Enable cookie parsing
app.use(cookieParser());

// Set up session middleware
app.use(session({
  secret: '38sisjsk92',
  resave: false,
  saveUninitialized: true,
  cookie: {
	httpOnly: true, 
	secure: false, // Sent only over HTTPS if true
  },   
}));

Visual Studio Codeでコード内の脆弱性を検出するには、アカウントでSnyk Codeが有効になっている必要があります。Visual Studio Code拡張機能またはSnykウェブサイトで有効にできます。Visual Studio CodeのターミナルにあるPROBLEMSタブを確認すると、コード内の複数の脆弱性がSnykによって検出されていることがわかります。

Expressアプリで、ハードコードされたシークレット、CSRF保護の無効化、Cookie設定など、5件のセキュリティ問題を一覧表示するVS Codeの「問題」パネル。

スキャンの結果、コードにハードコードされたシークレットがあるほか、クロスサイトリクエストフォージェリ(CSRF)対策とセキュアフラグが無効になっており、脆弱なパッケージが使われていることがわかります。

Snykは、表示される手順に沿ってこれらの問題を解決する方法を提案します。ここでは、いくつかの問題を修正してみましょう。

まず、Snykはハードコードを避けるため、環境変数の使用を推奨しています。この変更を実装するには、ターミナルで次のコマンドを実行します。

npm i dotenv
touch .env

最初のコマンドでは、設定された環境変数を読み取るdotenvパッケージをインストールし、2つ目のコマンドでは、機密情報を保存する環境ファイルを作成します。

次に、.envファイルに以下の設定を追加します。

SESSION_SECRET=38sisjsk92

次に、index.jsファイルで、以下のコードを使ってセッションミドルウェアを更新します。

require('dotenv').config(); // loads env variables 
app.use(session({
  secret: process.env.SESSION_SECRET,
}));

これらの更新により、環境変数からシークレットが読み込まれるようになります。

次に、セキュアフラグをtrueに設定します。

CSRF対策を有効にするとアプリケーションにかなりのコードを追加することになるため、このデモでは省略します。ただし、実際のアプリケーションではcsrfパッケージを使って必ず対処してください。

脆弱なパッケージの問題を解決するには、公式のnpmドキュメントを参照し、更新版、またはよりテストされていて安全なbcryptjsパッケージをインストールしてください。

これらの更新後にSnyk拡張機能でアプリケーションをスキャンすると、ほとんどのエラーが解消されていることがわかります。この時点でアプリケーションを実行すると、HTTPSを使用していない場合はlocalhostに接続できず、ルートURLへのリダイレクトが繰り返されます。

クライアントリクエストの保存にローカルストレージを使うほか、アプリケーションのセッションデータをデータベースに保存する方法もあります。データベースを使えば、サーバーを再起動しても一意のセッションIDに基づいてセッションデータを保持できます。そのため、ユーザーは毎回認証情報を入力せずにログインできます。また、Redisなどのメモリーキャッシュを使用する方法もあります。

セッションタイムアウトを実装する

セッションタイムアウトとは、ユーザーのセッションが期限切れになり、自動的にシステムからログアウトされることです。セッション攻撃が発生する可能性のある時間を短縮し、一定時間後にセッションが破棄されるようにします。

セッションタイムアウトの期間を設定する際は、次の点を考慮してください。

  • アプリケーションの種類 — アプリケーションが扱うデータに適したセッションタイムアウトの長さを決めます。

  • ユーザーの行動 — タイムアウトによってユーザーの操作が妨げられないようにします。

セッションタイムアウトは、セッションミドルウェアで実装します。以下のスニペットを使ってコードを更新し、セッションタイムアウトを1時間に設定します。

// Set up session middleware
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: true,
  cookie: {
	maxAge: 3600000, // Expires after 1 hour (in milliseconds)
	httpOnly: true, // Accessible only via HTTP(S)
	secure: false, // Sent only over HTTPS
  },   
}));

適切なログアウト機能を実装する

ログアウト機能により、ユーザーはアプリケーションの利用を終えたときに、明示的にセッションを終了できます。ログアウト機能を実装すると、ユーザー自身がCookieセッションを終了できるため、セキュリティを自分で管理しているという安心感につながります。

ただし、ログアウト機能には依然として開発者による対応が必要です。ユーザーがログアウトを開始すると、クライアント側のセッションは終了する場合があります。しかし、サーバー側ではセッションCookieが完全に無効化されていない可能性があります。攻撃者はこれらのセッショントークンを使って、あたかも正規のユーザーであるかのように操作を実行できます。

このセキュリティ侵害を防ぐには、サーバー側でユーザーのセッショントークンとセッションIDが確実に破棄されるようにする必要があります。以下のコードをindex.jsファイルの最後のルートとして追加し、この機能をアプリケーションに実装します。

app.get('/logout', (req, res) => {
  req.session.destroy(err => {
	if (err) {
  	  console.log(err)
	} else {
  	  res.clearCookie('connect.sid');
  	  res.redirect('/');
	}
  });
});

このコードでは、ユーザーがログアウトしたときにサーバー側のセッションを削除するため、コールバック関数とともにdestroy()を呼び出します。また、clearCookie()を呼び出し、ログイン中のユーザーに割り当てられたキーであるconnection.sidも削除します。

ブラウザーの検証機能から、ログイン中のユーザーのCookieを確認すると、以下のようにconnection.sidが表示されます。

ブラウザーの開発者ツールで、Cookiesパネル内のlocalhost connect.sidセッションCookieが選択されている様子。

上記の手順を実装することで、システムのユーザーはオンライン上のセキュリティを心配することなくログアウトできます。Snyk Code拡張機能を使うと、プロジェクトで実行したすべての脆弱性スキャンを確認でき、それぞれのステータスも表示されます。そのため、アプリケーションをユーザーに公開する前に、脆弱性がないことを確認できます。

この記事では、Snykを使ってプロジェクトをスキャンし、ハードコードされたシークレット、セッション関連の問題、脆弱なパッケージの使用など、脆弱性への対処方法を確認しました。Snykが検出した脆弱性を修正した結果、以下のレポートに示すとおり、システムに脆弱性は残っていません。

オープンソース、コード、構成、コード品質のスキャンで脆弱性や問題が検出されていないことを示す、ダークテーマのSnykダッシュボード。

セッションを安全に管理するためのその他のベストプラクティス

この記事で紹介した手順に加えて、セッションをより安全に管理するためのベストプラクティスをいくつかご紹介します。

  • 安全なセッション識別子を使用する — 安全なセッション識別子とは、ユーザーセッションの認証に使われる、長くて一意な文字列です。これらの識別子は自動生成できます。ランダムで予測しにくいため、統計的な分析手法を使う綿密な攻撃者でも推測は困難です。また、文字列が十分に長いため、総当たり攻撃にも耐えられます。

  • サーバー側でセッションを管理する — セッション識別子を一意に生成しても、クライアント側で取得すると脆弱性につながる可能性があります。サーバー側でセッションを管理し、セッション識別子を保存してアプリケーション内のユーザーと照合できます。この方式でセッションを保存すれば、機密性の高いユーザー認証情報をより安全に保護でき、ユーザーはなりすましを恐れずにシステムを利用できます。

  • ログアウトを適切に検証する — ユーザーがあるインスタンスにログインしてからログアウトした場合、訪問したすべてのインスタンスでログアウトが適切に無効化される必要があります。インスタンス間でセッションデータを同期せずにこれを実現するのは複雑な場合があります。それでも、各インスタンスが攻撃者にとってユーザー情報を盗む侵入口になり得るため、確実に対応することが重要です。

まとめ

この記事では、訪問者の安全性を高め、攻撃の数を減らすために、セッション管理のセキュリティを向上させるさまざまな方法を紹介しました。セッションを安全に管理することで、ユーザーが残す個人情報やデータ、アカウント情報を安全に保護できます。

また、ユーザーがウェブサイトやウェブアプリケーションを利用する際も保護できます。ここで紹介したベストプラクティスとセキュリティ戦略を実践すれば、ユーザーは認証情報や機密情報が安全に守られていると確信できます。

Capture the Flagを始めよう

オンデマンドのバーチャル入門ワークショップを見て、Capture the Flagの課題の解き方を学びましょう。

続きを読む

feature insights context
Blog

自律型攻撃はすでに始まっている。防御もそのスピードに追いつかなければならない。

自律型攻撃者によって、防御に使える時間は短くなっています。継続的な検出、修復、検証、予防で、セキュリティチームが攻撃に歩調を合わせる方法をご紹介します。

illustration hero ai
Blog

AIハリケーンが到来

AIはソフトウェア開発とサイバー攻撃の双方を加速させています。リーダーは、エージェントとコードを開発の初期段階から保護し、実行時に制御を徹底するとともに、防御策を独立して検証しなければなりません。

feature insights context
Blog

予防は、本質的に解決済みの問題なのでしょうか?

エージェントが生成するコードの予防策はアーキテクチャ上解決されていますが、開発を遅らせることなくセキュリティを守る制御を選ぶことが、依然として課題です。