ApacheのHTTP基本認証機構を用いた短時間パスワードの試験実装です(ソースも公開)。
以下ご興味があればごらんください。
横浜工文社は問題解決方式の探索、実現可能性調査など研究開発試作を請け負います。
ご相談ください。
横浜工文社はデータベースホスティングサービスを構築中です(近々公開予定 → 公開しました → DBHosting)。 その過程でアカウント登録のため「ワンタイムパスワード(one-time password)」のようなものが必要になりました。 そしてふたつの「短時間パスワード(short-time password)」の試験実装を行いました。 SQLテーブルを用いたものと、アパッチのHTTP基本認証(HTTP BASIC Authentication)機構を用いたもののふたつです。 結局は前者のテーブル利用型を採用しました。 しかし後者のHTTP認証を利用する方式は簡単かつ有用であることがわかりました。 そこでHTTP基本認証を用いた短時間パスワードを実装したコード(短いBashシェルスクリプト)を公開します。
ソースコード: ticket.sh
この短時間パスワード実装は「チケット」と呼んでいます。 保護されたWebページやWebアプリへのアクセスを一定時間だけ可能とする許可証という意味です。
当初このチケット実装を「ワンタイムパスワード」と呼んでましたが正確には「短時間」です。パスワード入手者は一定時間内であれば保護ページに複数回アクセスできます。それで呼び方を変えました。
チケットの動作概要です。
これは筆者が「事前確認」とよぶ「メールボックス確認(mailbox confirmation)」の一種です。
もうひとつ「事後確認」と呼ぶものがあります。 ショッピングサイトなど新しいWebサービスに登録する際、まずメールアドレスや住所など個人情報をタイプします。 登録作業の最後にサービスサイトが送信する電子メールをチェックするよう促されます。 メールで送られたURLをクリックすると再びショッピングサイトに誘導されます。 サイト側はここで訪問者が電子メールアドレスの正当な所有者であることが確認できます。
このページで説明するチケットサービスはショッピングサイトの例とは異なります。 メールボックス確認をサービスにアクセスする前に行うからです。 このためチケットスタイルを事前確認、ショッピングサイトのスタイルを事後確認と呼んでいます。
筆者は同じ事前確認型のふたつのメールボックス確認方式を試しました。
最終的に筆者はテーブル利用のチケットに決めました。 ユーザ名やパスワード以外の訪問者情報も保持したかったからです。 限定利用のパスワードを保存する方法としてはデータベーステーブルを用いる方式がより一般的と思います。 しかし基本認証を用いた短時間パスワードは一定のシナリオでは有効であると思います。 利点は次のとおりです
以下、HTTP基本認証で短時間パスワードを実装したコードの主要部分と実行例を示します。
下記例ではダミーのドメイン名(訪問者はexample.com、サーバはexample.net)を使います。実際には当方のドメイン名でテストを実施。
最初に訪問者は希望のサービスや見たいページを提供するドメイン宛てに電子メールを送ります。 標題(Subject)にはアクセスしたいURLパス(ドメイン名を除く)を指定します。 メールの本文は何でもよく、空でかまいません。
From: guest@example.com
To: ticket@example.net
Subject: /admin/auth_account.mdm
(No body text)
受け側のドメインのメールサーバが返すメールは次のとおりです。
Hi, guest@example.com,
You have requested a ticket to visit /admin/auth_account.mdm on our site.
Click the following URL and type the username and password on a popup dialog:
https://example.net/admin/auth_account.mdm
Username: guestHexampleAcom
Password: ee6cac9a
For shortcut, you can just click the following URL:
https://geustHexampleAcom:ee6cac9a@example.net/admin/auth_account.mdm
If you are not expecting this email, please just ignore and discard it.
Thank you.
Ticket Office
この時点で、受け側ドメインで動作するアパッチWebサーバにはユーザ名とパスワードが登録されており、訪問者が対象ページにアクセスすれば認証が成功する状態となっています。
指定の電子メールアドレスに届くメッセージを受信し何らかのメッセージを自動的に返信するには、サーバ側でメールハンドラスクリプト(あるいはプログラム)を設定しておく必要があります。 筆者が使用中のメールサーバのPostfixの場合、次の行を/etc/aliasesに追加します。
[/etc/aliases]
ticket: "|/usr/local/maliases/ticket.sh"
この設定により'ticket@example.net'にメールが届くと'ticket.sh'スクリプトが実行されます。 メールの中身が標準入力を経由してスクリプトに渡されます。
実際には筆者は/etc/aliasesとは別のファイルを使用し、そのファイルを/etc/main.cfの'alias_maps'行に追加しました。そのファイルの所有者の権限でスクリプトを実行するためです。 さらに、デバッグ・ロギング目的でstdoutおよびstderrをファイルにリダイレクトするようにしました。
→ ticket: "|/usr/local/maliases/ticket.sh >> /var/log/maliases/ticket.log 2>&1"
受信したメールメッセージはメールハンドラの標準入力からmbox形式(From行にメッセージが続く)で渡されます。
From guest@example.com Thu Jul 09 12:55:09 2020
Return-Path: <guest@example.com>
X-Original-To: ticket@example.net
Delivered-To: ticket@example.net
Received: ...
To: Live Embedder Ticket <ticket@example.net>
From: "Guest" <guest@example.com>
Subject: /admin/auth_account.mdm
Message-ID: <2e5e2435-0c74-fd8b-723f-583ed02674be@...>
Date: Thu, 9 Jul 2020 12:55:07 +0900
User-Agent: ...
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8; format=flowed
Content-Transfer-Encoding: 7bit
Content-Language: en-US
(one blank line)
送信者のメールアドレスはSENDER環境変数でも渡ってきます。 メールから取り出したい情報は訪問者が訪れたいURLパスを含む標題行(Subject)だけで、それは下記のようにawkを用いて取得しました。
# get path info from Subject header
path_info=$(cat | awk '/^Subject:/{print $2;}');
echo "path_info: $path_info"
|
v
path_info: /admin/auth_account.mdm
実際のところチケットとして使う一時利用のユーザ名とパスワードはなんでもかまいません。 攻撃者が当該Webサーバのパスワードファイルに偽造したユーザ名とパスワードを書き込めないかぎり偽のチケットを作ることはできません。 筆者の実験では送信者のメールアドレスと/dev/urandomから取得した4バイトの乱数の16進表記を用いました。 URLへ埋め込めるようメールアドレスに含まれる記号を英大文字に変換しました。
# sanitize email address so that it can be embedded in URL
username=$(echo $SENDER | tr "[:upper:]" "[:lower:]" | tr "@_.-" "HUAD")
echo "username: $username"
|
v
username: guest2HexampleAcom
# get a password from random number generator
password=$(xxd -p -l 4 /dev/urandom)
echo "password: $password"
|
v
password: ee6cac9a
のちに、perlモジュールを使って上記の四つ以外の記号も変換するようにしましたが、わかりやすさを重視して、当初の短いシェルコードのまま掲載します。
アパッチのhtpasswdというコマンドでプレーンテキストのパスワードファイルが作成できます。 HTTP基本認証でこのファイルのパスワードが使われます。
基本認証で保護された領域にWebクライアントがアクセスすると、アパッチはユーザ名とパスワードを求め、管理下の認証データと比較します。 htpasswdが作成するパスワードファイルはそのようなデータのひとつです。
# register username and password pair to Apache password file
[ -e $HTPWFILE ] || option_c='-c'
htpasswd $option_c -b $HTPWFILE $username $password
if [ $? -ne 0 ]; then
>&2 echo "Failed to write to $HTPWFILE: $username";
exit 0
fi
HTPWFILEはアパッチが該当のURLにアクセスがあった場合にチェックするパスワードファイルのパスを含むシェル変数です。 '-c'オプションでhtpasswdコマンドにパスワードファイルがすでに存在するかどうかを伝えます。
上記スクリプトはエラーが起こっても終了コードはゼロを返します。 これはメールサーバが送信者にエラーメールを返すのを避けるためです。
コマンドの動作をコマンドラインで試してみます。
$ htpasswd -c -b /var/www/etc/htp_tickets guestHexampleAcom ee6cac9a
$ ls /var/www/etc/htp_tickets
-rw-rw-r-- 1 www-data www-data 53 Jul 9 12:55 htp_tickets
$ cat /var/www/etc/htp_tickets
guestHexampleAcom:$apr1$H8QE2KWc$99cegAYGAGohWy7O3aPMc/
ユーザ名とパスワードは下記のようにURLに埋め込むことができます。
full_url="https://$username:$password@example.net$path_info"
echo "full_url: $full_url"
|
v
full_url: https://guestHexampleAcom:ee6cac9a@example.net/admin/auth_account.mdm
postfixパッケージに含まれるsendmailコマンドを使って送信者にメールを返信します。 SENDMAIL変数にはsendmailコマンドへのパスが設定されています(筆者の環境の場合は/usr/sbin/sendmail)。 RECIPIENT変数はメールハンドラに割り当てられたメールアドレスにPostfixが設定してくれます。
$SENDMAIL -F "Ticket Office" -f "$RECIPIENT" $SENDER <<REPLY_CONTENT
Subject: Re: $path_info
Hi, $SENDER,
You have requested a ticket to visit $path_info on our site.
Click the following URL and type the username and password on a popup dialog:
$plain_url
Username: $username
Password: $password
For shortcut, you can just click the following URL:
$full_url
If you are not expecting this email, please just ignore and discard it.
Thank you.
Ticket Office
REPLY_CONTENT
(メール文例は掲載済み)
筆者は短時間パスワードの存続時間を1時間と決めました。 この要件を実現するのにatコマンドを使用してハンドラスクリプトの実行から1時間後にパスワードファイル中の該当パスワードエントリを削除するようスケジュールします。
# schedule deletion in one hour
at now + 1 hour <<AT_COMMAND
htpasswd -D $HTPWFILE $username
logger -s "[ticket.sh] ticket expired: path $path_info given to $SENDER"
AT_COMMAND
htpasswdコマンドの-Dオプションは指定のユーザ名を持つエントリを削除します。 loggerコマンドはsyslogサーバにログメッセージを送信するのに使用します。 loggerコマンドの'-s'オプションはそのメッセージを同時に標準エラーへも出力することを指定します。
下記はスクリプトのstdinとstdoutをファイルにキャプチャしたものです(ドメイン名は変更)。
[/var/log/maliases/ticket.log]
[ticket.sh] 20/07/09-12:55:09 SENDER = guest@example.com, RECIPIENT = ticket@example.net
path_info: /admin/auth_account.mdm
<13>Jul 9 12:55:09 www-data: [ticket.sh] ticket requested for /admin/auth_account.mdm to guest@example.com
username: guest2HexampleAcom
password: ee6cac9a
plain_url: https://example.net/admin/auth_account.mdm
full_url: https://guestHexampleAcom:ee6cac9a@example.net/admin/auth_account.mdm
Adding password for user guestHexampleAcom <- from htpasswd
warning: commands will be executed using /bin/sh <- from at command
job 19 at Thu Jul 9 13:55:00 2020
<13>Jul 9 12:55:09 www-data: [ticket.sh] ticket issued for /admin/auth_account.mdm to guest@examaple.com
次はsyslogへ出力された監査用ログです。
[/var/log/messages]
Jul 9 12:55:09 ... www-data: [ticket.sh] ticket requested for /admin/auth_a
ccount.mdm to guest@example.com
Jul 9 12:55:09 ... www-data: [ticket.sh] ticket issued for /admin/auth_account.mdm to guest@example.com
Jul 9 13:55:00 ... www-data: [ticket.sh] ticket expired: path /admin/auth_account.mdm given to guest@example.com
メールハンドラスクリプト実行の1時間後に削除スクリプトが実行されていることがわかります。
確認のためパスワードファイルの中身をみてみます。
$ ls -l /var/www/etc/htp_tickets
-rw-rw-r-- 1 www-data www-data 0 Jul 9 13:55
$ cat /var/www/etc/htp_tickets
$
パスワードファイルからパスワードエントリがなくなっていることがわかります。
以上、チケット(限定利用のパスワード)の試験実装の動作について説明しました。
アパッチの基本認証機構を用いて単純なメールハンドラひとつで短時間パスワードが実装可能であることがわかりました。 Webページ側でプログラミングが不要なため静的ページであっても短時間パスワードでの保護が可能です。
横浜工文社は問題解決方式の探索、実現可能性調査など研究開発試作を請け負います。 ご相談ください。
Presented by Kobu.Com
Written 2020-Jul-10
Updated 2020-Jul-31 fixed: $RECEIPIENT -> $RECIPIENT