説明
信頼できない入力をそのままログに記録すると、ログインジェクションにつながる場合があります。攻撃者は改行(\n、\r)、タブ、ANSI エスケープ、HTML などを挿入し、レコードを分割したり別のイベントに見せたりできます。偽のメッセージが実際のイベントを隠し、運用担当者を誤解させるおそれがあります。ログを HTML として表示する場合は、ビューアーで XSS が起こる可能性もあります。
想定される影響
- 改行や制御文字でレコードを偽造し、元のログの意味を変えるおそれがあります。
- レコードの破損により、行単位の SIEM 解析やアラートが妨げられる場合があります。
- 偽のログが調査を難しくし、インシデント対応を遅らせる可能性があります。
- HTML を描画するログビューアーでスクリプトが実行されるおそれがあります。
- 監査証跡の破損によって、コンプライアンスやフォレンジック調査に支障が出る場合があります。
対処方法
- ログ形式に応じて、
\n、\rなどの制御文字を除去またはエスケープしてください。例はvalue.replace(/[\r\n]/g, '')です。 console.log('%s', value)のように書式文字列を固定してください。%s自体は改行や制御文字を除去しないため、別途処理してください。- Web 上のログはテキストとして表示するか、HTML のコンテキストに合ったエンコードを適用してください。
- ログの大量生成を抑えるため、入力を 256 文字などに制限してください。
sanitizeForLogなどの共通関数で正規化してください。- パスワードやトークンなどのシークレットを記録しないでください。
例
変更前
javascript
const express = require('express');
const app = express();
app.get('/audit', (req, res) => {
const agent = req.headers['user-agent']; // 信頼できない入力
const q = req.query.q; // 信頼できない入力
// BAD: 入力をそのままログのテンプレートに挿入
console.info(`[AUDIT] agent=${agent} query=${q}`);
res.send('ok');
});
app.listen(3001);
変更後
javascript
const express = require('express');
const app = express();
function sanitizeForLog(v) {
if (typeof v !== 'string') return v;
// CR/LF と Unicode の行区切り文字を除去し、長さを制限
const noCtl = v.replace(/[\r\n]/g, '').replace(/[\u2028\u2029]/g, '');
return noCtl.length > 256 ? noCtl.slice(0, 256) + '…' : noCtl;
}
app.get('/audit', (req, res) => {
const rawAgent = req.headers['user-agent'];
const rawQ = req.query.q;
const agent = sanitizeForLog(String(rawAgent || 'unknown'));
const q = sanitizeForLog(String(rawQ || ''));
// GOOD: 固定の書式と正規化済みの値を使用
console.info('[AUDIT] agent=%s query=%s', agent, q);
res.send('ok');
});
app.listen(3001);
説明:
- 変更前: 入力(
agent、q)を直接メッセージに挿入するため、改行、HTML、ANSI エスケープなどで記録や解析を妨害される可能性があります。 - 変更後:
sanitizeForLogは CR/LF と Unicode の行区切り文字を除去し、長さを制限します。すべての制御文字や HTML を除去するわけではありません。固定書式と併用して行単位の偽造を減らし、端末の ANSI 処理や Web ビューアーの出力エンコードは別途適用してください。