やられアプリ BadTodo - 14 セッション管理の不備

前回:やられアプリ BadTodo - 13 クリックジャッキング - demandosigno

HTTPプロトコルはステートレスで、サーバー側では状態を保持しません。しかしアプリケーションでは状態を保持したい場合があります。典型例としてよく紹介されるのがECサイトの「ショッピングカート」です。ショッピングカートはカートに入れられた商品を覚えておく必要があります。
これらアプリケーションの状態を覚えておくことをセッション管理と呼びます。このセッション管理をHTTPで実現する方法にCookieがあります。
セッションIDが漏洩すると他の利用者に成りすましができるので、漏洩しないように対策する必要があります。

以下、ウェブ健康診断 仕様 p.18(K)セッション管理の不備 にある検出パターンを確認していきます。

1.ログインの前後でセッション ID が変化するか

ウェブアプリケーションによっては、ユーザがログインする前の段階(例えばサイトの閲覧を開始した時点)でセッション ID を発行してセッションを開始し、そのセッションをログイン後も継続して使用する実装のものがあります。
しかしながら、この実装はセッション ID の固定化攻撃に対して脆弱な場合があります。このような実装を避け、ログインが成功した時点から新しいセッションを開始する(新しいセッション ID でセッション管理をする)ようにします。
安全なウェブサイトの作り方 p.19:ログイン成功後に、新しくセッションを開始する

BadTodoも最初にサイトを訪れた後、どこかのページに移るなどする際にhttps://todo.example.jp/todolist.php?TODOSESSID=94f2d5a5bf752299b0444ebae2118bcfのような形でセッションID(TODOSESSID)を発行します。
この後ログインを行った後もセッションIDは変わらず継続して使用されます。「ログインの前後でセッション ID が変化しない」ためセッション管理の不備があると判断します。

このままだとhttps://todo.example.jp/login.php?TODOSESSID=hogehogeのようなURLからログインさせることで悪意のある第三者に現利用者のセッションIDを任意の値に指定されてしまう可能性があります。「セッション ID の固定化(Session Fixation)攻撃」といいます。Bad Todo - 11 HTTP ヘッダ・インジェクションの方法でも可能です。 対策として、ログインが成功した時点から新しいセッションを開始するようにします。PHPでこの処理を行うには、session_regenerate_id 関数が利用できます。
PHP: session_regenerate_id - Manual

2.言語・ミドルウェアの備えるセッション管理機構を使用せず手作りのセッション管理機構を使っていないか

セッション ID のパラメータ名等で、言語・ミドルウェアのセッション管理機構を使用しているかを判断します(PHPSESSID, JSESSIONID, ASPSESSIONIDxxxx など)。
BadTodoはPHPが使われていますので、session_start 関数が利用されているのなら PHPSESSID というセッションID名になるはずですが実際は TODOSESSID であるため、独自のセッション管理機構を使っていると考えられます。

実際には sesssion.phpは次のようになっていました。

<?php
~(前略)~
define('MAGICNUMBER', 4199870312793);
~(中略)~
function get_randomid() {
  $random = microtime(true) + MAGICNUMBER; //現在のUnixタイムスタンプをマイクロ秒で返します、それにMAGICNUMBERを加えています。
  return md5($random); //この値をmd5()関数でハッシュ化してセッションIDを生成しています。
}
~(中略)~
 private function set_sessionid() {
    $this->log .= "set_sessionid:";
    if (isset($_COOKIE[SESSIDNAME])) {
      $this->sessionid = $_COOKIE[SESSIDNAME];
      $this->cookie_avail = true;
    } elseif (isset($_GET[SESSIDNAME])) {
      $this->sessionid = $_GET[SESSIDNAME];
    } elseif (isset($_POST[SESSIDNAME])) {
      $this->sessionid = $_POST[SESSIDNAME];
    } else {
      $this->sessionid = get_randomid();
    }
  }
~(後略)~

md5() は現在ではセキュリティ上の問題があるハッシュ関数とされています。またマイクロ秒とはいえ日時を元にしているので予測可能と言えます。MAGICNUMBERを含めて総当たりなどで推測可能かを後で確かめたいと思います。
参照:CSRF対策のトークンをワンタイムにしたら意図に反して脆弱になった実装例 | 徳丸浩の日記

ログイン時 Set-Cookie: TODOSESSID=6a1b31cc82eb4ad957cabd2da04d5f19; path=/; samesite=none; secure となっており、Secure属性が付与されているため問題ありません。

ウェブサイトが発行する Cookie には、secure 属性という設定項目があり、これが設定された Cookieは HTTPS 通信のみで利用されます。Cookie に secure 属性がない場合、HTTPS 通信で発行したCookie は、経路が暗号化されていない HTTP 通信でも利用されるため、この HTTP 通信の盗聴によりCookie 情報を不正に取得されてしまう可能性があります。HTTPS 通信で利用する Cookie には secure属性を必ず加えてください。かつ、HTTP 通信で Cookie を利用する場合は、HTTPS で発行する Cookieとは別のものを発行してください。
安全なウェブサイトの作り方 p.19:HTTPS 通信で利用する Cookie には secure 属性を加える

デフォルトではCookieオン、セットされる。

Cookieオフ

ブラウザの[設定]から一旦キャッシュ削除

再度ログイン

セッションIDがURL埋め込みになりました。
この時、Todoリストに記載の外部リンクなどを踏んだとすると、

Refererヘッダ経由でセッションIDが漏洩します。

セッションIDをURLに付与する方式は古い携帯電話端末などCookieを使えない場面を想定しており、URL rewriting機能が使われます。不要であれば無効化することを検討してください。
(当初はApacheのmod_rewriteかと思っていましたが、BadTodoの場合はPHPの設定によるもののようです。/todo/.user.ini で session.use_only_cookies=Off, session.use_trans_sid=On とされています。)
PHP: セッションに関連する INI 設定をセキュアにする - Manual

ただ、最近のブラウザのデフォルトでは同じオリジンの場合にのみパスとクエリ文字列が記載されるようになっており安全性が高められています。(同一オリジン=プロトコル、ホスト、ポート番号が同じ)
一方、BadTodoの場合はレスポンスヘッダに記載の通り "Referrer-Policy: no-referrer-when-downgrade" とあり、わざと弱める設定が行われています。
Referrer-Policy - HTTP | MDN

なお、Cookie をブラウザに残す必要が無い場合は、有効期限の設定(expires=)を省略し、発行した Cookie をブラウザ終了後に破棄させる方法もあります。しかし、この方法は、利用者がブラウザを終了させずに使い続けた場合には Cookie は破棄されないため、期待する効果を得られない可能性があります。
安全なウェブサイトの作り方 p.20:セッション ID を Cookie にセットする場合、有効期限の設定に注意する

次回:やられアプリ BadTodo - 15 アクセス制御や認可制御の欠落 - demandosigno

/* -----codeの行番号----- */