OWASP Top 10 - 安全日誌與監控失效

OWASP Top 10 - Security Logging and Monitoring Failures

日誌監控失效概述

安全日誌與監控失效(Security Logging and Monitoring Failures)是 OWASP Top 10 中重要的安全風險之一。當系統缺乏適當的日誌記錄和監控機制時,攻擊者可以在不被發現的情況下持續入侵系統,導致資料外洩、系統被完全控制等嚴重後果。

根據統計,企業平均需要 200 天以上才能發現資安事件,這主要是因為:

  • 沒有記錄關鍵的安全事件
  • 日誌未被定期審查
  • 缺乏即時告警機制
  • 日誌儲存時間過短
  • 日誌完整性未受保護

應記錄的安全事件

完善的日誌系統應該記錄以下關鍵安全事件:

事件類型說明重要性
登入嘗試成功與失敗的登入記錄
權限變更使用者權限的新增、修改、刪除
敏感資料存取存取個資或機密資料的行為
系統設定變更系統組態的任何修改
API 呼叫外部 API 的請求與回應
錯誤與異常應用程式的錯誤訊息
安全事件WAF 阻擋、入侵偵測告警

Python 日誌記錄範例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import logging
from datetime import datetime
import json

class SecurityLogger:
    def __init__(self, app_name):
        self.logger = logging.getLogger(app_name)
        self.logger.setLevel(logging.INFO)

        # 設定日誌格式
        formatter = logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        )

        # 檔案處理器
        file_handler = logging.FileHandler('/var/log/app/security.log')
        file_handler.setFormatter(formatter)
        self.logger.addHandler(file_handler)

    def log_auth_event(self, user_id, event_type, success, ip_address, user_agent):
        """記錄身份驗證事件"""
        log_data = {
            'timestamp': datetime.utcnow().isoformat(),
            'event_type': event_type,
            'user_id': user_id,
            'success': success,
            'ip_address': ip_address,
            'user_agent': user_agent
        }

        if success:
            self.logger.info(f"AUTH_SUCCESS: {json.dumps(log_data)}")
        else:
            self.logger.warning(f"AUTH_FAILURE: {json.dumps(log_data)}")

    def log_access_event(self, user_id, resource, action, ip_address):
        """記錄資源存取事件"""
        log_data = {
            'timestamp': datetime.utcnow().isoformat(),
            'event_type': 'ACCESS',
            'user_id': user_id,
            'resource': resource,
            'action': action,
            'ip_address': ip_address
        }
        self.logger.info(f"ACCESS: {json.dumps(log_data)}")

日誌格式標準化

採用標準化的日誌格式有助於日誌的分析和關聯。建議使用 JSON 格式,並包含以下必要欄位:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{
    "timestamp": "2024-07-11T10:30:00.000Z",
    "level": "WARNING",
    "event_type": "AUTH_FAILURE",
    "source": "web-app-01",
    "user_id": "user123",
    "ip_address": "192.168.1.100",
    "user_agent": "Mozilla/5.0...",
    "session_id": "abc123def456",
    "request_id": "req-789xyz",
    "message": "Login failed: invalid password",
    "metadata": {
        "attempt_count": 3,
        "endpoint": "/api/login"
    }
}

Node.js 結構化日誌範例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const winston = require('winston');

const securityLogger = winston.createLogger({
    level: 'info',
    format: winston.format.combine(
        winston.format.timestamp(),
        winston.format.json()
    ),
    defaultMeta: { service: 'web-application' },
    transports: [
        new winston.transports.File({
            filename: '/var/log/app/security.log',
            level: 'info'
        }),
        new winston.transports.File({
            filename: '/var/log/app/error.log',
            level: 'error'
        })
    ]
});

function logSecurityEvent(eventType, details) {
    securityLogger.log({
        level: details.success ? 'info' : 'warn',
        event_type: eventType,
        ...details,
        timestamp: new Date().toISOString()
    });
}

日誌完整性保護

日誌的完整性至關重要,攻擊者通常會試圖刪除或修改日誌以掩蓋入侵痕跡。

保護措施:

  1. 日誌簽章:對日誌加上數位簽章確保未被竄改
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import hashlib
import hmac

class SecureLogger:
    def __init__(self, secret_key):
        self.secret_key = secret_key.encode()
        self.previous_hash = None

    def create_log_entry(self, message):
        """建立帶有完整性驗證的日誌條目"""
        timestamp = datetime.utcnow().isoformat()

        # 建立日誌內容
        log_content = f"{timestamp}|{message}"

        # 計算 HMAC(包含前一筆日誌的雜湊,形成鏈式結構)
        chain_data = f"{self.previous_hash or 'GENESIS'}|{log_content}"
        signature = hmac.new(
            self.secret_key,
            chain_data.encode(),
            hashlib.sha256
        ).hexdigest()

        self.previous_hash = signature

        return {
            'timestamp': timestamp,
            'message': message,
            'signature': signature,
            'previous_hash': self.previous_hash
        }
  1. 唯寫權限:應用程式只有寫入權限,無法刪除或修改現有日誌
  2. 遠端備份:即時將日誌同步到安全的遠端伺服器

集中式日誌管理

對於分散式系統,應採用集中式日誌管理解決方案,如 ELK Stack(Elasticsearch, Logstash, Kibana)或 Splunk。

Logstash 配置範例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# /etc/logstash/conf.d/security.conf
input {
    beats {
        port => 5044
        ssl => true
        ssl_certificate => "/etc/logstash/certs/logstash.crt"
        ssl_key => "/etc/logstash/certs/logstash.key"
    }
}

filter {
    json {
        source => "message"
    }

    if [event_type] == "AUTH_FAILURE" {
        mutate {
            add_tag => ["security_alert"]
        }
    }

    geoip {
        source => "ip_address"
        target => "geo"
    }
}

output {
    elasticsearch {
        hosts => ["https://elasticsearch:9200"]
        index => "security-logs-%{+YYYY.MM.dd}"
        ssl => true
        cacert => "/etc/logstash/certs/ca.crt"
    }
}

Filebeat 配置範例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# /etc/filebeat/filebeat.yml
filebeat.inputs:
  - type: log
    enabled: true
    paths:
      - /var/log/app/security.log
    json.keys_under_root: true
    json.add_error_key: true
    fields:
      log_type: security

output.logstash:
  hosts: ["logstash:5044"]
  ssl.certificate_authorities: ["/etc/filebeat/certs/ca.crt"]
  ssl.certificate: "/etc/filebeat/certs/filebeat.crt"
  ssl.key: "/etc/filebeat/certs/filebeat.key"

即時告警設定

建立即時告警機制可以在安全事件發生時立即通知相關人員。

常見告警規則:

告警條件嚴重程度回應時間
單一帳戶 5 分鐘內登入失敗 > 5 次1 小時
同一 IP 對多個帳戶登入失敗15 分鐘
管理員帳戶異常登入緊急立即
敏感資料大量存取30 分鐘
非上班時間的管理操作2 小時

Python 告警實作範例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
from collections import defaultdict
from datetime import datetime, timedelta
import requests

class AlertManager:
    def __init__(self):
        self.failed_logins = defaultdict(list)
        self.ip_attempts = defaultdict(set)

    def check_brute_force(self, user_id, ip_address):
        """檢查暴力破解攻擊"""
        current_time = datetime.now()
        cutoff = current_time - timedelta(minutes=5)

        # 清理過期記錄
        self.failed_logins[user_id] = [
            t for t in self.failed_logins[user_id] if t > cutoff
        ]

        # 記錄此次失敗
        self.failed_logins[user_id].append(current_time)
        self.ip_attempts[ip_address].add(user_id)

        # 檢查告警條件
        if len(self.failed_logins[user_id]) >= 5:
            self.send_alert(
                severity="MEDIUM",
                title="可能的暴力破解攻擊",
                message=f"帳戶 {user_id} 在 5 分鐘內登入失敗超過 5 次",
                ip_address=ip_address
            )

        if len(self.ip_attempts[ip_address]) >= 3:
            self.send_alert(
                severity="HIGH",
                title="憑證填充攻擊偵測",
                message=f"IP {ip_address} 嘗試登入多個帳戶",
                ip_address=ip_address
            )

    def send_alert(self, severity, title, message, **kwargs):
        """發送告警通知"""
        alert_data = {
            'severity': severity,
            'title': title,
            'message': message,
            'timestamp': datetime.now().isoformat(),
            **kwargs
        }

        # 發送到 Slack
        requests.post(
            'https://hooks.slack.com/services/YOUR/WEBHOOK/URL',
            json={'text': f"[{severity}] {title}: {message}"}
        )

事件關聯分析

透過關聯分析可以發現單一事件難以察覺的攻擊模式。

Elasticsearch 查詢範例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
    "query": {
        "bool": {
            "must": [
                {"match": {"event_type": "AUTH_FAILURE"}},
                {"range": {
                    "timestamp": {
                        "gte": "now-1h",
                        "lte": "now"
                    }
                }}
            ]
        }
    },
    "aggs": {
        "by_ip": {
            "terms": {
                "field": "ip_address.keyword",
                "min_doc_count": 10
            },
            "aggs": {
                "unique_users": {
                    "cardinality": {
                        "field": "user_id.keyword"
                    }
                }
            }
        }
    }
}

測試方法

1. 日誌記錄完整性測試

1
2
3
4
5
6
7
# 測試是否記錄登入失敗
curl -X POST http://target.com/api/login \
    -d '{"username":"test","password":"wrong"}' \
    -H "Content-Type: application/json"

# 檢查日誌是否有記錄
grep "AUTH_FAILURE" /var/log/app/security.log

2. 日誌注入測試

測試是否能透過輸入惡意內容污染日誌:

1
2
3
# 嘗試注入換行符號
curl -X POST http://target.com/api/login \
    -d '{"username":"admin\n2024-07-11 ADMIN_LOGIN success","password":"test"}'

3. 告警機制測試

1
2
3
4
5
6
7
8
9
# 模擬暴力破解測試告警
import requests

for i in range(10):
    requests.post(
        'http://target.com/api/login',
        json={'username': 'admin', 'password': f'wrong{i}'}
    )
# 檢查是否收到告警通知

測試清單

  • 驗證所有安全事件是否被記錄
  • 測試日誌是否包含必要欄位
  • 確認日誌不包含敏感資訊(密碼、信用卡號等)
  • 測試日誌注入防護
  • 驗證告警機制是否正常運作
  • 檢查日誌保留期限是否符合規範
  • 測試日誌存取權限控管

防護措施

1. 完善的日誌記錄策略

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 日誌記錄最佳實踐
class AuditLogger:
    SENSITIVE_FIELDS = ['password', 'credit_card', 'ssn', 'token']

    def sanitize_data(self, data):
        """移除敏感資訊"""
        if isinstance(data, dict):
            return {
                k: '***REDACTED***' if k in self.SENSITIVE_FIELDS else v
                for k, v in data.items()
            }
        return data

    def log_request(self, request, response, user_id):
        """記錄 API 請求"""
        log_entry = {
            'timestamp': datetime.utcnow().isoformat(),
            'user_id': user_id,
            'method': request.method,
            'path': request.path,
            'status_code': response.status_code,
            'ip_address': request.remote_addr,
            'request_body': self.sanitize_data(request.json),
            'response_time_ms': response.elapsed_ms
        }
        self.logger.info(json.dumps(log_entry))

2. 日誌保護機制

  • 設定適當的檔案權限(chmod 640)
  • 使用獨立的日誌伺服器
  • 實施日誌輪替和壓縮
  • 加密傳輸中的日誌資料

3. 監控儀表板建置

建立安全監控儀表板,即時顯示:

  • 登入成功/失敗趨勢
  • 異常存取模式
  • 地理位置分析
  • 高風險事件統計

參考資料

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy