ログは「エンジニアの宝の山」か、それとも「攻撃者の招待状」か
現場のエンジニア諸君、日々お疲れ様。ログの設計について深く考えたことはあるか?
「とりあえずデバッグ用に全部出しとけ」と、リクエストボディやレスポンスの全容をログに吐き出しているなら、君たちは自ら攻撃者に宝の地図を渡しているようなものだ。運用現場でインシデント対応をしていると、決まって遭遇するのが「ログからの機密情報流出」だ。
今回は、ログ出力におけるPII(個人識別情報)のマスキングと、機密情報の除外について、現場の泥臭い教訓を交えて解説する。
—
なぜ「ログ」が狙われるのか?(攻撃者の視点)
攻撃者が侵入した際、真っ先に狙うのはデータベースではない。まずは「アプリケーションのログファイル」だ。
理由は単純。ログには、開発者が「デバッグ用」として放置した以下の情報が平文で転がっていることが多いからだ。
- セッションID・認証トークン(JWT等): これを奪えば、パスワードなしでユーザーになりすませる。
- リクエストボディ内のPII: クレジットカード番号、メールアドレス、電話番号。
- スタックトレース: サーバーのパス構造やライブラリのバージョンが露呈し、次の攻撃対象が明確になる。
実例(PoC的思考):
もし君のアプリがログにリクエスト全体を出力していて、それがELKスタックやS3バケットに保存されているとする。そのS3バケットの権限設定が緩ければ、攻撃者はバックエンドのAPIを叩かなくても、ログを眺めるだけでアクティブなセッションをハイジャックできる。いわゆる「サイドチャネル」からの情報収集だ。
—
現場で即効性のある「マスキング」実装
ログ出力のルールは「後から修正する」のではなく、「フレームワークのレイヤーで強制する」のが鉄則だ。各言語で、ログ出力時にフィルタリングを挟む実装例を紹介する。
1. Python (Loggingモジュールでのフィルタリング)
`logging.Filter` を使うのが最もクリーンだ。正規表現でPIIを置換するクラスを作成し、ハンドラーに登録する。
import logging
import re
class SensitiveDataFilter(logging.Filter):
# マスキング対象のパターン(クレジットカード、メールアドレスなど)
MASK_PATTERNS = {
‘card’: r’\b\d{4}-\d{4}-\d{4}-\d{4}\b’,
‘email’: r'[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+’
}
def filter(self, record):
msg = str(record.msg)
for key, pattern in self.MASK_PATTERNS.items():
msg = re.sub(pattern, ‘–––‘, msg)
record.msg = msg
return True
ロガーの設定
logger = logging.getLogger(‘app_logger’)
logger.addFilter(SensitiveDataFilter())
logger.info(“ユーザー登録成功: email@example.com, card: 1234-5678-1234-5678”)
出力結果: ユーザー登録成功: –––, card: –––
2. Node.js (Winston + Middlewareでの対応)
Expressでリクエストをログ出力する際、特定のキーを除外する設計だ。
const winston = require(‘winston’);
const logger = winston.createLogger({
format: winston.format.combine(
winston.format((info) => {
// ログからパスワードやトークンを削除
const sensitiveKeys = [‘password’, ‘token’, ‘credit_card’];
sensitiveKeys.forEach(key => delete info[key]);
return info;
})(),
winston.format.json()
),
transports: [new winston.transports.Console()]
});
—
ログの「完全性」と「隠蔽」:インフラの守り方
ログをマスキングしただけでは不十分だ。ログファイルそのものが改ざんされたら、攻撃の痕跡は消されてしまう。
1. クラウドストレージ(S3)の保護
ログを保存するバケットには、必ず以下の設定を施せ。
- Object Lock: 一定期間、ログの削除や改ざんを物理的に不可能な状態にする(WORM: Write Once, Read Many)。
- 最小権限の原則: ログを書き込むIAMロールと、読み取る(監査する)IAMロールを厳格に分離せよ。
2. Nginx等での不要情報除外
そもそもWebサーバーのログに機密情報が載らないよう、ヘッダーを制御する。
nginx.conf
Authorizationヘッダーをログに記録しない設定
log_format main ‘$remote_addr – $remote_user [$time_local] “$request” ‘
‘$status $body_bytes_sent “$http_referer” ‘
‘”$http_user_agent” -‘; # Authorizationは敢えて含めない
—
まとめ:セキュリティの「防波堤」はログにある
諸君、ログは「君たちの活動の記録」であると同時に、「攻撃者の最大のヒント」でもある。
- 「全部出す」は怠慢: 必要な情報だけを構造化して出す。
- 「マスキング」は自動化せよ: 人間に頼るな。ライブラリやミドルウェアに委ねろ。
- 「ログの保存先」を要塞化せよ: ログが漏洩した時点で、システムの防御は全敗だ。
今日からログ出力のコードを見直してほしい。もし「パスワード」や「トークン」の文字列がログで見つかったら、それは既にインシデントが起きているのと同じことだ。速やかに修正し、ログの信頼性を担保する仕組みを構築してくれ。
現場からは以上だ。何かあればいつでも相談してくれ。

コメント