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

870 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.py``database.py``test_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`。
>
> 我打開終端機,輸入:
> ```bash
> 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` 檔案(此檔案絕對不能提交到版本控制)
```bash
# .env (放在專案根目錄)
DB_HOST=mysql.theaken.com
DB_PORT=33306
DB_USER=A101
DB_PASSWORD=您的新強密碼
DB_NAME=HBR_scraper
```
**步驟 2**: 建立 `.gitignore` 檔案(如果未來要使用 Git
```gitignore
# .gitignore
.env
*.env
.env.*
__pycache__/
*.pyc
.venv/
venv/
hbr_articles.csv
*.log
```
**步驟 3**: 安裝 python-dotenv
```bash
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`
```python
# 修改前 (危險!)
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.py``get_database_manager()` 函數
```python
# 修改前 (危險!)
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`
```python
# 修改前 (危險!)
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
```sql
-- 在 MySQL 中更換密碼
ALTER USER 'A101'@'%' IDENTIFIED BY '新的強密碼';
FLUSH PRIVILEGES;
```
---
### 🔴 HIGH-002: 弱密碼 - 資料庫密碼強度不足
* **風險等級**: `High`
* **威脅描述**: 目前使用的密碼 `Aa123456` 是一個極其常見的弱密碼,在大多數密碼字典攻擊中會在前 100 個嘗試內被破解。
* **受影響組件**: MySQL 用戶 `A101`
---
**駭客劇本 (Hacker's Playbook)**:
> 「即使我沒有看到原始碼,我知道 `mysql.theaken.com:33306` 是一個公開的 MySQL 伺服器。我啟動 Hydra一個密碼暴力破解工具使用常見密碼字典
>
> ```bash
> 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**: 設定防火牆白名單(推薦)
```bash
# 在伺服器上設定 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 隧道
```bash
# 不直接暴露 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 限制
```sql
-- 限制 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` 檔案。如果未來將專案加入版本控制,可能會意外提交敏感檔案(如 `.env``hbr_articles.csv``__pycache__`)。
* **受影響組件**: 整個專案
* **修復建議**:
創建 `.gitignore` 檔案:
```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 行)
```python
# 危險的程式碼
cursor.execute(f"CREATE DATABASE IF NOT EXISTS `{database_name}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")
```
雖然使用了反引號包裹,但如果 `database_name` 包含特殊字元(如反引號本身),仍可能導致 SQL 注入。
* **修復建議**:
```python
# 修改後 - 驗證資料庫名稱
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 行)
* **修復建議**:
```python
# 修改前
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)
* **修復建議**:
```python
# 修改前 - 可能洩漏敏感資訊
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`
* **修復建議**:
```python
# 使用連線池
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
```bash
pip install DBUtils
```
---
### 🟢 LOW-002: GitHub Actions 工作流程安全性可加強
* **風險等級**: `Low`
* **威脅描述**: GitHub Actions 工作流程沒有明確限制權限,使用預設權限可能過於寬鬆。
* **受影響組件**: `.github/workflows/weekly.yml`
* **修復建議**:
```yaml
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`
* **修復建議**:
```python
# 在 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
```yaml
# .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
### 🟠 本週修復
4. **MEDIUM-001**: 建立 .gitignore 檔案
5. **MEDIUM-002**: 修復 `create_database` SQL 注入風險
6. **MEDIUM-003**: 啟用資料庫 SSL/TLS 連線
7. **MEDIUM-004**: 改善錯誤處理,避免資訊洩漏
### 🟢 可排程修復
8. **LOW-001**: 實作資料庫連線池
9. **LOW-002**: 加強 GitHub Actions 安全設定
10. **LOW-003**: 加入輸入驗證和資料清理
---
## 結語
本次審計發現了幾個需要立即修復的嚴重安全問題,最關鍵的是 **資料庫密碼硬編碼****弱密碼** 問題。由於資料庫伺服器可從公網訪問,這些問題的風險被大幅放大。
好消息是,您的程式碼在其他方面相對安全:
- ✅ 使用參數化查詢(大部分情況)
- ✅ 遵守 robots.txt
- ✅ 使用 Gmail App Password 而非一般密碼
- ✅ 合理的程式碼結構
請按照優先順序儘快修復這些問題,特別是在將專案部署到生產環境之前。
---
**報告結束**
如有任何問題,請隨時詢問。