LESSON 30分

ログ分析で攻撃を検知しよう

ストーリー

「ツールも大事だが、もう1つ重要なスキルがある」高橋さんが画面を切り替えた。

「アクセスログの分析だ。今回の不審なアクセスも、ログの監視から発覚した。 ログを読めるエンジニアは、攻撃の兆候を早期に察知できる」

画面にはずらりとアクセスログが並んでいる。

「これ全部見るんですか......?」

「全部見る必要はない。パターンを知っていれば、異常を素早く見つけられる。 今からそのパターンを教えよう」


アクセスログの基本

Webサーバーのログ形式

Nginx / Apache の一般的なアクセスログ形式(Combined Log Format):

192.168.1.100 - - [15/Jan/2025:14:30:05 +0900] "GET /api/users?id=1 HTTP/1.1" 200 1234 "https://example.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
各フィールドの意味:
192.168.1.100    → クライアントのIPアドレス
-                → 認証ユーザー名(未認証の場合は -)
[15/Jan/2025:...]→ アクセス日時
"GET /api/..."   → HTTPメソッドとパス
200              → HTTPステータスコード
1234             → レスポンスサイズ(バイト)
"https://..."    → リファラー(参照元URL)
"Mozilla/5.0..." → ユーザーエージェント

不審なパターンを見つける

パターン1: SQLインジェクションの試行

# 典型的なSQLi攻撃のログ
10.0.0.50 - - [15/Jan/2025:14:30:05 +0900] "GET /api/users?id=1%27%20OR%201%3D1%20-- HTTP/1.1" 200 45678
10.0.0.50 - - [15/Jan/2025:14:30:07 +0900] "GET /api/users?id=1%27%20UNION%20SELECT%20*%20FROM%20passwords%20-- HTTP/1.1" 500 234
10.0.0.50 - - [15/Jan/2025:14:30:09 +0900] "GET /api/users?id=1%27%3B%20DROP%20TABLE%20users%20-- HTTP/1.1" 500 234

URLデコードすると:

/api/users?id=1' OR 1=1 --
/api/users?id=1' UNION SELECT * FROM passwords --
/api/users?id=1'; DROP TABLE users --

注目ポイント:

  • 同一IPから短時間に連続したリクエスト
  • URLにSQLの予約語(UNION, SELECT, DROP, OR 1=1)が含まれている
  • '(シングルクオート)や --(コメント)の存在

パターン2: XSS攻撃の試行

10.0.0.50 - - [15/Jan/2025:14:31:00 +0900] "GET /search?q=%3Cscript%3Ealert(1)%3C/script%3E HTTP/1.1" 200 5678
10.0.0.50 - - [15/Jan/2025:14:31:02 +0900] "POST /comments HTTP/1.1" 200 123

デコード: /search?q=<script>alert(1)</script>

パターン3: ブルートフォース攻撃

# 同一IPから大量のログイン試行
10.0.0.50 - - [15/Jan/2025:14:30:00 +0900] "POST /api/login HTTP/1.1" 401 45
10.0.0.50 - - [15/Jan/2025:14:30:01 +0900] "POST /api/login HTTP/1.1" 401 45
10.0.0.50 - - [15/Jan/2025:14:30:02 +0900] "POST /api/login HTTP/1.1" 401 45
10.0.0.50 - - [15/Jan/2025:14:30:03 +0900] "POST /api/login HTTP/1.1" 401 45
10.0.0.50 - - [15/Jan/2025:14:30:04 +0900] "POST /api/login HTTP/1.1" 200 234

注目ポイント:

  • 同一エンドポイントへの高頻度リクエスト
  • 401(認証失敗)の連続後に200(成功)
  • 1秒間隔など機械的なタイミング

パターン4: ディレクトリトラバーサルの試行

10.0.0.50 - - [15/Jan/2025:14:32:00 +0900] "GET /files?path=../../etc/passwd HTTP/1.1" 403 45
10.0.0.50 - - [15/Jan/2025:14:32:02 +0900] "GET /files?path=..%2F..%2Fetc%2Fpasswd HTTP/1.1" 200 1234

パターン5: 異常なレスポンスサイズ

# 通常のレスポンスサイズは 500 バイト程度のエンドポイントで
# 突然大きなレスポンスが返されている
10.0.0.50 - - [15/Jan/2025:14:33:00 +0900] "GET /api/users?id=1 HTTP/1.1" 200 512
10.0.0.50 - - [15/Jan/2025:14:33:05 +0900] "GET /api/users?id=1' OR 1=1-- HTTP/1.1" 200 458902

レスポンスサイズが 512 バイトから 458,902 バイトに急増しています。SQLインジェクションにより全レコードが返された可能性があります。


ログ分析のコマンド例

基本的な分析

bash
# 特定のIPからのアクセスを抽出
grep "10.0.0.50" access.log

# SQLインジェクションのパターンを検索
grep -iE "(union|select|drop|insert|delete|update|or 1=1|' --)" access.log

# XSSパターンの検索
grep -iE "(<script|alert\(|onerror=|onload=)" access.log

# 401エラーの頻度を確認(ブルートフォース検出)
grep " 401 " access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10

# 特定の時間帯のアクセスを抽出
grep "15/Jan/2025:14:3" access.log

高度な分析

bash
# IPごとのリクエスト数を集計(異常に多いIPを発見)
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20

# 5xx エラーの多いエンドポイントを特定
grep " 5[0-9][0-9] " access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -10

# 1分間に100回以上アクセスしているIPを検出
awk '{print $1, $4}' access.log | \
  awk -F'[: ]' '{print $1, $2":"$3":"$4}' | \
  sort | uniq -c | sort -rn | \
  awk '$1 > 100 {print}'

レート制限の重要性

ログ分析で攻撃を検知するだけでなく、レート制限で攻撃を予防することも重要です。

typescript
// express-rate-limit を使用した例
import rateLimit from 'express-rate-limit';

// 一般的なAPI: 15分間に100リクエストまで
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  message: { error: 'リクエスト数の上限に達しました。しばらく待ってから再試行してください。' }
});

// ログインAPI: 15分間に5回まで(ブルートフォース対策)
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  message: { error: 'ログイン試行回数の上限に達しました。' }
});

app.use('/api/', apiLimiter);
app.use('/api/login', loginLimiter);

セキュリティログの記録

アクセスログだけでなく、アプリケーション内でセキュリティイベントを記録することも重要です。

typescript
// セキュリティイベントのログ記録
function logSecurityEvent(event: {
  type: string;
  severity: 'INFO' | 'WARN' | 'CRITICAL';
  ip: string;
  userId?: string;
  details: string;
}) {
  const logEntry = {
    timestamp: new Date().toISOString(),
    ...event
  };
  console.log(`[SECURITY] ${JSON.stringify(logEntry)}`);
}

// 使用例
app.post('/api/login', async (req, res) => {
  const { email, password } = req.body;
  const user = await authenticateUser(email, password);

  if (!user) {
    logSecurityEvent({
      type: 'LOGIN_FAILED',
      severity: 'WARN',
      ip: req.ip,
      details: `Failed login attempt for email: ${email}`
    });
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  logSecurityEvent({
    type: 'LOGIN_SUCCESS',
    severity: 'INFO',
    ip: req.ip,
    userId: user.id,
    details: `Successful login`
  });
});

まとめ

ポイント内容
ログ形式Combined Log Format の各フィールドを理解する
不審パターンSQLi、XSS、ブルートフォース、異常サイズを見分ける
分析コマンドgrep、awk、sort、uniq を組み合わせて分析
レート制限ログ分析だけでなく、予防措置も実装する

チェックリスト

  • アクセスログの各フィールドを理解した
  • 不審なアクセスパターンを5つ以上識別できる
  • grepとawkを使ったログ分析の基本を理解した
  • レート制限の必要性を理解した

次のステップへ

ログ分析の方法を学びました。 次のセクションでは、これまでの知識を活用して、実際に脆弱なコードを見つけて報告する演習に取り組みます。


推定読了時間: 30分