TT Lab
はじめる
学ぶ 学習パス コース

顧客データを扱う

権限があるからといって見てよいわけではない

TT Labで続きを見る

一言でいうと

FDEは、顧客の実際のデータに手が届く立場です。技術より先に学ぶべきなのは、最小限だけ見て、最小限だけ移し、痕跡を残す習慣です。

なぜ必要なのか

調査していると、自然にこんなコマンドを打つことになります。

SELECT * FROM orders WHERE status = 'FAILED' LIMIT 100;

ここには注文番号だけがあるわけではありません。名前、電話番号、住所、ときには決済情報も一緒に出てきます。そして、その結果が、ターミナルのスクロールバックに、キャプチャ画像に、Slackのメッセージに、ノートPCの一時ファイルに残ります。

事故は悪意よりも利便性から起こります。「早く見ようとしてCSVで抜き出してメールで送った」が、実際の事故報告書に最も多く登場する文です。

4つの原則

1. 必要な列だけを取得します SELECT *ではなく、調査に必要な列だけを指定します。原因分析に名前と住所が必要な場合は、ほとんどありません。

-- 나쁨
SELECT * FROM orders WHERE status='FAILED';
-- 좋음
SELECT id, created_at, status, error_code FROM orders WHERE status='FAILED';

2. 識別子は目的別に仮名化します 同一人物の連結がどうしても必要なら、正規化した識別子に、目的別のシークレットキーを使ったHMAC-SHA-256を適用し、十分な長さの出力を保ちます。

uid = HMAC_SHA256(secret_from_kms, normalize(email))

この値は匿名化された値ではなく、仮名化された識別子です。シークレットキーや周辺データが漏れると再び結び付けられるので、原本と同じレベルでアクセスを管理します。システム間の連結が承認された場合にだけ同じ目的のキーを共有し、互いに連結されてはいけない分析には、目的別に違うキーを使います。

3. 外へ持ち出しません データは、もとの場所で扱います。ローカルにダウンロードしたりメッセンジャーで送ったりした瞬間、管理の外です。やむを得ない場合は、集計した形でだけ移します。原本100行の代わりに、「失敗理由別の件数」の表1枚です。

4. 残します いつ何を照会したかを記録します。あとで「誰がこれを見たのか」という質問が来たときに、自分を守る唯一の手段です。ほとんどのシステムに監査ログがあり、なければ、作業ノートがその役割を果たします。

元に戻せるかどうかで分けるマスキング

方式 元に戻せるか いつ使うか
削除 不可 調査に不要な列
目的別のキーを使った十分に長いHMAC キーなしでは困難 承認された範囲で同一人物をまとめる必要があるとき
部分マスキング(010-****-1234) 不可だが推測は可能 目視確認が必要なとき
トークン化(マッピングを保管) 可能 あとで原本が必要なとき
暗号化 可能 移動・保管が避けられないとき

一般のハッシュや公開ソルトだけでは安全ではありません。電話番号とメールアドレスは、候補を作って総当たりしやすく、8桁のように出力を切ると、衝突の危険も大きくなります。レコードごとに違うソルトは、同じ人を同じ値にまとめられないので、繰り返し可能な連結が必要なときは、アクセス管理された目的別のシークレットキーと、十分に長いHMACを使います。

テストデータを作るとき

本番データをコピーしてテストに使うのは、最もよくあって最も危険な慣行です。どうしても必要なら、構造は保ち、値は変える方法で作ります。名前は辞書から、メールアドレスはドメインをexample.comにし、金額は分布を保ったままノイズを加えます。こうすれば、再現に必要な性質(長さの分布、重複、欠損)は残り、個人情報は消えます。

現場での姿

ログとエラー報告が最もよくある流出経路

個人情報の事故は、データベースが盗まれたときだけに起こるのではありません。正常に動作しているシステムが真面目に残した記録から、より頻繁に漏れます。

リクエスト全体を記録する習慣が最も危険です。デバッグのために入れた1行が本番に残ると、住民登録番号とカード番号がログの保存先に流れ込みます。ログはたいてい暗号化されておらず、保存期間が長く、閲覧権限が広いです。

# 이렇게 두면 어느 날 반드시 샌다
log.info("요청: %s", request.json())

# 필요한 것만, 식별자는 해시로
log.info("결제 요청 user=%s amount=%s", hash_id(user.id), amount)

エラー追跡ツールはローカル変数も一緒に送ります。例外が起きたフレームの変数を含めて送るのがそのツールの長所ですが、そのフレームにパスワードやトークンがあれば、そのまま付いていきます。送る前にフィルタリングする場所を、必ず置きます。

def before_send(event, hint):
    for k in ("password", "token", "authorization", "ssn", "card"):
        _scrub(event, k)
    return event

URLに個人情報を入れません。クエリ文字列は、アクセスログ、プロキシログ、ブラウザーの履歴、Refererヘッダーに残ります。同じ値でも本文に入れれば、この4か所には残りません。

3つ目の流出経路は人です。障害を調査していて、本番データをノートPCにダウンロードし、社内メッセンジャーに表を貼り付け、スクリーンショットを文書に入れます。技術的な管理では防ぎにくいので、そもそもダウンロードする理由をなくすほうが実効性があります。読み取り専用の照会画面、マスキングされた管理画面、クエリ結果を自動的に消す分析環境です。

残した記録は、いつか提出を求められます。どのログに何が入るかを一覧で管理し、保存期間を決めておきます。削除の要請が来たとき、データベースだけを消してログとバックアップに残っているなら、消したことにはなりません。最初から残さないほうが、消すよりいつも安く済みます。

続くラボですること

個人情報が混ざった注文200行を受け取り、外へ出す1枚を作るところまで進みます。4つの原則をそれぞれファイルとして残し、最後に持ち出し前の検査ツールを自分で作ります。

採点ツールが、その検査ツールを、流出するファイルときれいなファイルのそれぞれに対して実行します。すべてを止める検査ツールも、すべてを通す検査ツールも不合格です。誤検知が出れば誰も使わず、見逃せばあってもないのと同じだからです。