Files
hbr-crawler/security-fixes.md
DonaldFang 方士碩 f524713cb6 Initial commit: HBR 文章爬蟲專案
- Scrapy 爬蟲框架,爬取 HBR 繁體中文文章
- Flask Web 應用程式,提供文章查詢介面
- SQL Server 資料庫整合
- 自動化排程與郵件通知功能

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 17:19:56 +08:00

25 KiB
Raw Blame History

HBR Taiwan 爬蟲系統 - 安全審計報告

審計日期: 2024-12-22 審計員: Claude (AI Security Consultant) 報告版本: 1.0 風險評級: 🔴 高風險 - 發現災難級漏洞,需立即修復


基本專案資訊

項目 內容
專案名稱 HBR Taiwan Web Scraper
專案描述 自動化爬取 Harvard Business Review Taiwan 文章的系統
目標用戶 內部/個人使用
處理的資料類型 文章元資料標題、URL、作者、日期、分類、標籤、文章內容
是否處理 PII
是否處理付款資訊
是否有 UGC
技術棧 Python 3.11+, Scrapy, MySQL/MariaDB, Gmail SMTP
部署環境 GitHub Actions (私有倉庫), 本地 Crontab
外部依賴 scrapy>=2.11.0, itemadapter>=0.7.0, pymysql>=1.1.0, python-dateutil>=2.8.2
資料庫位置 mysql.theaken.com:33306 (公網可訪問)
未來計畫 將新增 Web 介面/API

執行摘要

本次審計發現 3 個高風險4 個中風險3 個低風險 問題。最嚴重的問題是 資料庫密碼硬編碼在多個原始碼檔案中,且該資料庫可從公網訪問。這是一個典型的「新手災難級錯誤」,必須立即修復。

風險統計

風險等級 數量 狀態
🔴 高風險 (High) 3 需立即修復
🟠 中風險 (Medium) 4 建議盡快修復
🟢 低風險 (Low) 3 建議改善

第一部分:災難級新手錯誤檢查

🔴 HIGH-001: 資料庫密碼硬編碼在原始碼中

  • 風險等級: High - 災難級
  • 威脅描述: 資料庫連線密碼 Aa123456 直接硬編碼在多個 Python 檔案中,包括 settings.pydatabase.pytest_db_connection.py。由於資料庫伺服器可從公網訪問,任何能夠看到這些檔案的人都可以直接連線到您的資料庫。
  • 受影響組件:
    • hbr_crawler/hbr_crawler/settings.py (第 64-68 行)
    • hbr_crawler/hbr_crawler/database.py (第 220, 227 行)
    • test_db_connection.py (第 26-32 行)

駭客劇本 (Hacker's Playbook):

「我是一個好奇的人,偶然間在某個地方看到了這個專案的原始碼(可能是開發者不小心把私有倉庫設成公開、在論壇分享了程式碼片段、或者我拿到了開發者的電腦存取權限)。

我打開 settings.py,哇!第 67 行寫著 DB_PASSWORD = 'Aa123456'。再往上看,主機是 mysql.theaken.com,埠號是 33306,用戶名是 A101,資料庫名是 HBR_scraper

我打開終端機,輸入:

mysql -h mysql.theaken.com -P 33306 -u A101 -pAa123456

成功連線!現在我可以:

  1. 讀取所有資料 - SELECT * FROM articles;
  2. 刪除所有資料 - DROP TABLE articles;(如果用戶權限允許)
  3. 植入惡意資料 - 在文章內容中注入惡意腳本
  4. 竊取其他資料庫 - 如果這個用戶有權限訪問其他資料庫
  5. 作為跳板 - 使用這個資料庫伺服器攻擊同一網路中的其他系統

更糟糕的是,密碼 Aa123456 是一個極弱的密碼,即使我沒看到原始碼,使用暴力破解工具幾分鐘內就能猜出來。」


修復原理 (Principle of the Fix):

為什麼不能把密碼寫在程式碼裡?想像一下,你的程式碼是一本「食譜書」,你會把食譜印成書賣給很多人看。你會在食譜裡寫「我家保險箱的密碼是 123456」嗎當然不會

正確的做法是把密碼放在「環境變數」裡。環境變數就像是寫在便條紙上、只有你自己能看到的秘密。程式執行時會去讀這張便條紙,但便條紙不會被複製到食譜書裡。

在不同環境(開發、測試、生產),你可以用不同的便條紙(不同的環境變數),這樣也更安全、更靈活。


  • 修復建議與程式碼範例:

步驟 1: 建立 .env 檔案(此檔案絕對不能提交到版本控制)

# .env (放在專案根目錄)
DB_HOST=mysql.theaken.com
DB_PORT=33306
DB_USER=A101
DB_PASSWORD=您的新強密碼
DB_NAME=HBR_scraper

步驟 2: 建立 .gitignore 檔案(如果未來要使用 Git

# .gitignore
.env
*.env
.env.*
__pycache__/
*.pyc
.venv/
venv/
hbr_articles.csv
*.log

步驟 3: 安裝 python-dotenv

pip install python-dotenv

並更新 requirements.txt:

scrapy>=2.11.0
itemadapter>=0.7.0
pymysql>=1.1.0
python-dateutil>=2.8.2
python-dotenv>=1.0.0

步驟 4: 修改 settings.py

# 修改前 (危險!)
DB_HOST = 'mysql.theaken.com'
DB_PORT = 33306
DB_USER = 'A101'
DB_PASSWORD = 'Aa123456'  # 🔴 密碼暴露!
DB_NAME = 'HBR_scraper'

# 修改後 (安全)
import os
from dotenv import load_dotenv

# 載入 .env 檔案
load_dotenv()

DB_HOST = os.environ.get('DB_HOST', 'localhost')
DB_PORT = int(os.environ.get('DB_PORT', 3306))
DB_USER = os.environ.get('DB_USER')
DB_PASSWORD = os.environ.get('DB_PASSWORD')
DB_NAME = os.environ.get('DB_NAME')

# 檢查必要的環境變數
if not all([DB_USER, DB_PASSWORD, DB_NAME]):
    raise ValueError("Missing required database environment variables: DB_USER, DB_PASSWORD, DB_NAME")

步驟 5: 修改 database.pyget_database_manager() 函數

# 修改前 (危險!)
def get_database_manager() -> DatabaseManager:
    import os
    try:
        from scrapy.utils.project import get_project_settings
        settings = get_project_settings()
        host = settings.get('DB_HOST', os.environ.get('DB_HOST', 'mysql.theaken.com'))
        # ... 預設值包含真實密碼 ...
        password = settings.get('DB_PASSWORD', os.environ.get('DB_PASSWORD', 'Aa123456'))  # 🔴
    except:
        password = os.environ.get('DB_PASSWORD', 'Aa123456')  # 🔴

# 修改後 (安全)
def get_database_manager() -> DatabaseManager:
    import os
    from dotenv import load_dotenv
    load_dotenv()

    try:
        from scrapy.utils.project import get_project_settings
        settings = get_project_settings()
        host = settings.get('DB_HOST') or os.environ.get('DB_HOST')
        port = settings.getint('DB_PORT', int(os.environ.get('DB_PORT', 3306)))
        user = settings.get('DB_USER') or os.environ.get('DB_USER')
        password = settings.get('DB_PASSWORD') or os.environ.get('DB_PASSWORD')
        database = settings.get('DB_NAME') or os.environ.get('DB_NAME')
    except:
        host = os.environ.get('DB_HOST')
        port = int(os.environ.get('DB_PORT', 3306))
        user = os.environ.get('DB_USER')
        password = os.environ.get('DB_PASSWORD')
        database = os.environ.get('DB_NAME')

    if not all([host, user, password]):
        raise ValueError("Database configuration missing. Please set environment variables.")

    return DatabaseManager(host, port, user, password, database)

步驟 6: 修改 test_db_connection.py

# 修改前 (危險!)
DB_CONFIG = {
    'host': 'mysql.theaken.com',
    'port': 33306,
    'user': 'A101',
    'password': 'Aa123456',  # 🔴
    'database': 'HBR_scraper'
}

# 修改後 (安全)
import os
from dotenv import load_dotenv
load_dotenv()

DB_CONFIG = {
    'host': os.environ.get('DB_HOST'),
    'port': int(os.environ.get('DB_PORT', 3306)),
    'user': os.environ.get('DB_USER'),
    'password': os.environ.get('DB_PASSWORD'),
    'database': os.environ.get('DB_NAME')
}

# 驗證配置
if not all([DB_CONFIG['host'], DB_CONFIG['user'], DB_CONFIG['password']]):
    print("錯誤: 請設定環境變數 DB_HOST, DB_USER, DB_PASSWORD")
    sys.exit(1)

步驟 7: 立即更換資料庫密碼

由於密碼已經暴露在原始碼中,即使您現在修復了程式碼,原來的密碼可能已經被他人知道。請:

  1. 登入 MySQL 伺服器
  2. 更換 A101 用戶的密碼為強密碼(至少 16 字元,包含大小寫字母、數字、特殊符號)
  3. 更新 .env 檔案使用新密碼
  4. 更新 GitHub Actions secrets
-- 在 MySQL 中更換密碼
ALTER USER 'A101'@'%' IDENTIFIED BY '新的強密碼';
FLUSH PRIVILEGES;

🔴 HIGH-002: 弱密碼 - 資料庫密碼強度不足

  • 風險等級: High
  • 威脅描述: 目前使用的密碼 Aa123456 是一個極其常見的弱密碼,在大多數密碼字典攻擊中會在前 100 個嘗試內被破解。
  • 受影響組件: MySQL 用戶 A101

駭客劇本 (Hacker's Playbook):

「即使我沒有看到原始碼,我知道 mysql.theaken.com:33306 是一個公開的 MySQL 伺服器。我啟動 Hydra一個密碼暴力破解工具使用常見密碼字典

hydra -l A101 -P /usr/share/wordlists/rockyou.txt mysql://mysql.theaken.com:33306

不到一分鐘,工具告訴我密碼是 Aa123456。這個密碼太常見了,幾乎每個密碼字典都有它。」


  • 修復建議:
  1. 使用密碼管理器生成強密碼(建議 20+ 字元)
  2. 密碼應包含:
    • 大寫字母 (A-Z)
    • 小寫字母 (a-z)
    • 數字 (0-9)
    • 特殊符號 (!@#$%^&*)
  3. 避免使用:
    • 字典單詞
    • 常見模式123456, password, qwerty
    • 個人資訊(生日、名字)

強密碼範例 (請勿直接使用,僅作示例):

Hbr$Cr@wl3r#2024!Secure_DB

🔴 HIGH-003: 資料庫伺服器公網暴露且缺乏防護

  • 風險等級: High
  • 威脅描述: MySQL 伺服器 mysql.theaken.com:33306 可從公網直接訪問,沒有 IP 白名單限制,增加了被暴力破解和未授權訪問的風險。
  • 受影響組件: MySQL 伺服器配置

駭客劇本 (Hacker's Playbook):

「我使用 Shodan物聯網搜尋引擎搜尋開放的 MySQL 伺服器:

port:33306 product:MySQL

發現 mysql.theaken.com 出現在列表中。這個伺服器對全世界開放,任何人都可以嘗試連線。我可以:

  1. 使用暴力破解工具嘗試常見用戶名/密碼組合
  2. 嘗試已知的 MySQL 漏洞
  3. 進行 DoS 攻擊使服務不可用」

  • 修復建議:

方案 A: 設定防火牆白名單(推薦)

# 在伺服器上設定 iptables只允許特定 IP 訪問
# 假設您的 IP 是 203.0.113.100GitHub Actions IP 範圍需另外查詢

# 先封鎖所有對 33306 埠的訪問
iptables -A INPUT -p tcp --dport 33306 -j DROP

# 允許特定 IP
iptables -I INPUT -p tcp -s 203.0.113.100 --dport 33306 -j ACCEPT

# 允許 GitHub Actions IP 範圍(需定期更新)
# 可從 https://api.github.com/meta 獲取

方案 B: 使用 SSH 隧道

# 不直接暴露 MySQL而是通過 SSH 隧道連線
ssh -L 3306:localhost:3306 user@mysql.theaken.com

# 然後本地連線
mysql -h 127.0.0.1 -P 3306 -u A101 -p

方案 C: 使用 VPN

設定 VPN 伺服器,只有連接到 VPN 後才能訪問資料庫。

方案 D: MySQL 用戶 IP 限制

-- 限制 A101 用戶只能從特定 IP 連線
-- 先刪除現有用戶
DROP USER 'A101'@'%';

-- 創建只允許從特定 IP 連線的用戶
CREATE USER 'A101'@'203.0.113.100' IDENTIFIED BY '新的強密碼';
GRANT ALL PRIVILEGES ON HBR_scraper.* TO 'A101'@'203.0.113.100';

-- 如果需要 GitHub Actions需要為其 IP 範圍創建用戶
-- 注意GitHub Actions IP 範圍很廣,此方案可能不實際

第二部分:標準應用程式安全審計

🟠 MEDIUM-001: 缺少 .gitignore 檔案

  • 風險等級: Medium

  • 威脅描述: 專案沒有 .gitignore 檔案。如果未來將專案加入版本控制,可能會意外提交敏感檔案(如 .envhbr_articles.csv__pycache__)。

  • 受影響組件: 整個專案

  • 修復建議:

創建 .gitignore 檔案:

# 環境變數(最重要!)
.env
.env.*
*.env

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# 虛擬環境
.venv/
venv/
ENV/

# IDE
.idea/
.vscode/
*.swp
*.swo

# 專案特定
hbr_articles.csv
*.csv
*.log
logs/

# Scrapy
.scrapy/

# 資料庫備份
*.sql.bak
*.sql.backup

🟠 MEDIUM-002: SQL 注入風險 - create_database 函數

  • 風險等級: Medium
  • 威脅描述: database.py 中的 create_database 函數使用字串格式化構建 SQL 語句,而非參數化查詢,存在 SQL 注入風險。
  • 受影響組件: hbr_crawler/hbr_crawler/database.py (第 107 行)
# 危險的程式碼
cursor.execute(f"CREATE DATABASE IF NOT EXISTS `{database_name}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")

雖然使用了反引號包裹,但如果 database_name 包含特殊字元(如反引號本身),仍可能導致 SQL 注入。

  • 修復建議:
# 修改後 - 驗證資料庫名稱
import re

def create_database(self, database_name: str) -> bool:
    # 驗證資料庫名稱只包含安全字元
    if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', database_name):
        logger.error(f"無效的資料庫名稱: {database_name}")
        return False

    try:
        with self.get_connection(None) as conn:
            with conn.cursor() as cursor:
                # 由於已驗證名稱,現在可以安全使用
                cursor.execute(f"CREATE DATABASE IF NOT EXISTS `{database_name}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")
                conn.commit()
                logger.info(f"資料庫 {database_name} 建立成功(或已存在)")
                return True
    except Exception as e:
        logger.error(f"建立資料庫失敗: {e}")
        return False

🟠 MEDIUM-003: 資料庫連線未使用 SSL/TLS 加密

  • 風險等級: Medium

  • 威脅描述: 雖然 SDD 文件提到應使用 SSL/TLS 加密連線,但實際程式碼中並未實作。資料在傳輸過程中以明文形式傳送,可能被中間人攻擊竊取。

  • 受影響組件: hbr_crawler/hbr_crawler/database.py (第 50-59 行)

  • 修復建議:

# 修改前
connection = pymysql.connect(
    host=self.host,
    port=self.port,
    user=self.user,
    password=self.password,
    database=db_name,
    charset=self.charset,
    cursorclass=pymysql.cursors.DictCursor,
    autocommit=False
)

# 修改後 - 加入 SSL 設定
import ssl

def __init__(self, host: str, port: int, user: str, password: str,
             database: str = None, charset: str = 'utf8mb4', use_ssl: bool = True):
    # ... 原有程式碼 ...
    self.use_ssl = use_ssl

@contextmanager
def get_connection(self, database: Optional[str] = None):
    db_name = database or self.database

    # SSL 設定
    ssl_config = None
    if self.use_ssl:
        ssl_config = {
            'ssl': {
                'ssl_disabled': False,
                'check_hostname': True,
                'verify_mode': ssl.CERT_REQUIRED,
                # 如果有 CA 證書,可以指定
                # 'ca': '/path/to/ca-cert.pem'
            }
        }

    try:
        connect_args = {
            'host': self.host,
            'port': self.port,
            'user': self.user,
            'password': self.password,
            'database': db_name,
            'charset': self.charset,
            'cursorclass': pymysql.cursors.DictCursor,
            'autocommit': False
        }

        if ssl_config:
            connect_args.update(ssl_config)

        connection = pymysql.connect(**connect_args)
        yield connection
        connection.commit()
    # ... 原有錯誤處理 ...

🟠 MEDIUM-004: 錯誤處理可能洩漏敏感資訊

  • 風險等級: Medium

  • 威脅描述: 多處錯誤處理直接記錄完整的例外訊息,這些訊息可能包含敏感資訊(如資料庫連線字串、檔案路徑等)。當未來新增 Web 介面時,這些錯誤訊息可能會暴露給最終用戶。

  • 受影響組件:

    • database.py (多處 logger.error)
    • pipelines.py (多處 logger.error)
  • 修復建議:

# 修改前 - 可能洩漏敏感資訊
logger.error(f"資料庫連線錯誤: {e}")

# 修改後 - 安全的錯誤記錄
import traceback

# 開發環境可以記錄詳細錯誤
if os.environ.get('DEBUG', 'false').lower() == 'true':
    logger.error(f"資料庫連線錯誤: {e}")
    logger.debug(traceback.format_exc())
else:
    # 生產環境只記錄錯誤類型,不記錄詳細訊息
    logger.error(f"資料庫連線錯誤: {type(e).__name__}")

# 當未來有 Web 介面時,返回給用戶的錯誤訊息
def get_user_friendly_error(e: Exception) -> str:
    """返回對用戶友善的錯誤訊息,不洩漏內部細節"""
    error_map = {
        'OperationalError': '資料庫暫時無法連線,請稍後再試',
        'IntegrityError': '資料處理錯誤,請聯繫管理員',
        'ProgrammingError': '系統錯誤,請聯繫管理員',
    }
    return error_map.get(type(e).__name__, '發生未知錯誤,請稍後再試')

🟢 LOW-001: 未實作資料庫連線池

  • 風險等級: Low

  • 威脅描述: 每次資料庫操作都會建立新連線,效能較差且可能耗盡資料庫連線數。

  • 受影響組件: hbr_crawler/hbr_crawler/database.py

  • 修復建議:

# 使用連線池
from dbutils.pooled_db import PooledDB

class DatabaseManager:
    _pool = None

    @classmethod
    def get_pool(cls, host, port, user, password, database, charset='utf8mb4'):
        if cls._pool is None:
            cls._pool = PooledDB(
                creator=pymysql,
                maxconnections=10,
                mincached=2,
                maxcached=5,
                blocking=True,
                host=host,
                port=port,
                user=user,
                password=password,
                database=database,
                charset=charset,
                cursorclass=pymysql.cursors.DictCursor
            )
        return cls._pool

    @contextmanager
    def get_connection(self, database: Optional[str] = None):
        conn = self.get_pool(...).connection()
        try:
            yield conn
            conn.commit()
        except:
            conn.rollback()
            raise
        finally:
            conn.close()  # 歸還到連線池,非真正關閉

需要安裝 DBUtils

pip install DBUtils

🟢 LOW-002: GitHub Actions 工作流程安全性可加強

  • 風險等級: Low

  • 威脅描述: GitHub Actions 工作流程沒有明確限制權限,使用預設權限可能過於寬鬆。

  • 受影響組件: .github/workflows/weekly.yml

  • 修復建議:

name: weekly-crawl
on:
  schedule:
    - cron: "0 0 * * 1"
  workflow_dispatch: {}

# 明確限制權限
permissions:
  contents: read

jobs:
  crawl-and-mail:
    runs-on: ubuntu-latest
    # 限制可訪問的 secrets
    environment: production
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      # 使用 pip cache 加速
      - name: Cache pip packages
        uses: actions/cache@v3
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}

      - name: Install dependencies
        run: pip install -r requirements.txt

      - name: Run crawler
        run: |
          cd hbr_crawler
          scrapy crawl hbr

      - name: Send mail with CSV
        env:
          GMAIL_USERNAME: ${{ secrets.GMAIL_USERNAME }}
          GMAIL_APP_PASSWORD: ${{ secrets.GMAIL_APP_PASSWORD }}
          MAIL_TO: ${{ secrets.MAIL_TO }}
          # 加入資料庫環境變數
          DB_HOST: ${{ secrets.DB_HOST }}
          DB_PORT: ${{ secrets.DB_PORT }}
          DB_USER: ${{ secrets.DB_USER }}
          DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
          DB_NAME: ${{ secrets.DB_NAME }}
        run: python send_mail.py hbr_articles.csv

      - name: Upload CSV as artifact
        uses: actions/upload-artifact@v4
        with:
          name: hbr_articles_csv
          path: hbr_articles.csv
          retention-days: 7  # 限制保留天數

🟢 LOW-003: 缺少輸入驗證

  • 風險等級: Low

  • 威脅描述: 爬蟲提取的資料沒有經過驗證就直接儲存,可能導致 XSS 攻擊(當未來有 Web 介面時)或資料完整性問題。

  • 受影響組件: hbr_crawler/hbr_crawler/spiders/hbr.py, pipelines.py

  • 修復建議:

# 在 pipelines.py 中加入資料清理
import html
import re

def sanitize_html(text: str) -> str:
    """移除或轉義 HTML 標籤"""
    if not text:
        return ''
    # 移除 script 標籤
    text = re.sub(r'<script[^>]*>.*?</script>', '', text, flags=re.DOTALL | re.IGNORECASE)
    # 轉義 HTML 特殊字元
    text = html.escape(text)
    return text.strip()

def validate_url(url: str) -> bool:
    """驗證 URL 格式"""
    pattern = r'^https?://[a-zA-Z0-9\-._~:/?#\[\]@!$&\'()*+,;=%]+$'
    return bool(re.match(pattern, url))

class DatabasePipeline:
    def process_item(self, item, spider):
        adapter = ItemAdapter(item)

        # 驗證 URL
        url = adapter.get('url', '')
        if not validate_url(url):
            logger.warning(f"無效的 URL: {url}")
            return item

        # 清理文字欄位
        for field in ['title', 'summary', 'content', 'author']:
            if adapter.get(field):
                adapter[field] = sanitize_html(adapter[field])

        # ... 原有邏輯 ...

第三部分OWASP Top 10 (2021) 檢查清單

編號 漏洞類型 狀態 說明
A01 Broken Access Control ⚠️ 需注意 未來 Web 介面需實作適當的存取控制
A02 Cryptographic Failures 🔴 問題 密碼硬編碼、弱密碼、未使用 SSL
A03 Injection ⚠️ 部分問題 create_database 有潛在 SQL 注入風險,其他查詢已使用參數化
A04 Insecure Design 尚可 基本設計合理
A05 Security Misconfiguration 🔴 問題 資料庫公網暴露、缺少 .gitignore
A06 Vulnerable Components ⚠️ 需檢查 需定期更新依賴套件
A07 Auth Failures ⚠️ 需注意 未來 Web 介面需實作適當的身分驗證
A08 Software/Data Integrity 良好 使用 GitHub Actions 官方 actions
A09 Logging Failures ⚠️ 需改善 日誌可能洩漏敏感資訊
A10 SSRF 不適用 爬蟲目標是固定的,無 SSRF 風險

第四部分:依賴套件安全性分析

目前依賴 (requirements.txt)

套件 版本要求 已知漏洞 建議
scrapy >=2.11.0 無重大漏洞 保持更新
itemadapter >=0.7.0 無已知漏洞 保持更新
pymysql >=1.1.0 無重大漏洞 保持更新
python-dateutil >=2.8.2 無已知漏洞 保持更新

建議新增的安全相關套件

# requirements.txt 建議更新
scrapy>=2.11.0
itemadapter>=0.7.0
pymysql>=1.1.0
python-dateutil>=2.8.2
python-dotenv>=1.0.0      # 環境變數管理
DBUtils>=3.0.0            # 資料庫連線池(可選)

建議:設定依賴漏洞掃描

在 GitHub 倉庫啟用 Dependabot

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "pip"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 5

第五部分:未來 Web 介面安全建議

由於您計畫新增 Web 介面,以下是預防性安全建議:

1. 身分驗證與授權

  • 使用成熟的身分驗證框架(如 Flask-Login, FastAPI-Users
  • 實作 CSRF 保護
  • 使用 JWT 或 Session-based 認證
  • 實作速率限制防止暴力破解

2. API 安全

  • 所有 API 端點使用 HTTPS
  • 實作 CORS 限制
  • 驗證所有輸入參數
  • 使用 API Key 或 OAuth 進行認證

3. XSS 防護

  • 對所有輸出進行 HTML 轉義
  • 使用 Content-Security-Policy 標頭
  • 驗證和清理所有用戶輸入

4. 資料庫查詢

  • 繼續使用參數化查詢
  • 實作查詢結果分頁(防止大量資料洩漏)
  • 限制返回欄位(不返回敏感欄位)

修復優先順序

🔴 立即修復 (今天就要做)

  1. HIGH-001: 從所有原始碼中移除硬編碼的密碼,改用環境變數
  2. HIGH-002: 更換資料庫密碼為強密碼
  3. HIGH-003: 設定資料庫存取限制IP 白名單或 VPN

🟠 本週修復

  1. MEDIUM-001: 建立 .gitignore 檔案
  2. MEDIUM-002: 修復 create_database SQL 注入風險
  3. MEDIUM-003: 啟用資料庫 SSL/TLS 連線
  4. MEDIUM-004: 改善錯誤處理,避免資訊洩漏

🟢 可排程修復

  1. LOW-001: 實作資料庫連線池
  2. LOW-002: 加強 GitHub Actions 安全設定
  3. LOW-003: 加入輸入驗證和資料清理

結語

本次審計發現了幾個需要立即修復的嚴重安全問題,最關鍵的是 資料庫密碼硬編碼弱密碼 問題。由於資料庫伺服器可從公網訪問,這些問題的風險被大幅放大。

好消息是,您的程式碼在其他方面相對安全:

  • 使用參數化查詢(大部分情況)
  • 遵守 robots.txt
  • 使用 Gmail App Password 而非一般密碼
  • 合理的程式碼結構

請按照優先順序儘快修復這些問題,特別是在將專案部署到生產環境之前。


報告結束

如有任何問題,請隨時詢問。