前言
在前幾篇文章中,我們學習了網路協定和前端技術。現在,讓我們深入到網站的核心——後端。
如果說前端是餐廳的門面和服務生,那後端就是廚房和廚師。使用者在前端點餐(發送請求),後端負責處理訂單、從冰箱(資料庫)取出食材、烹飪(業務邏輯)、最後把料理送回給客人(回應)。
對資安人員來說,理解後端運作方式至關重要。大多數嚴重的安全漏洞——SQL Injection、Command Injection、認證繞過、權限提升——都發生在後端。你必須了解後端程式如何處理資料、如何與資料庫互動、如何驗證使用者身份,才能有效地發現和利用這些漏洞。
這篇文章將帶你認識後端開發的基礎知識,涵蓋程式語言概念、Web 框架、資料庫互動,以及常見的安全問題。
為什麼資安人員需要懂後端?
理解漏洞根本原因
當你看到一個 SQL Injection 漏洞時:
# 有漏洞的程式碼
query = "SELECT * FROM users WHERE username = '" + username + "'"
如果你不懂程式,你只能機械式地測試 ' OR '1'='1。但如果你理解後端:
– 你知道這是字串拼接造成的問題
– 你知道為什麼 Prepared Statement 可以防禦
– 你可以構造更複雜的 payload
– 你可以向開發者解釋如何修復
原始碼審計
許多資安工作需要審計原始碼:
– 白箱滲透測試
– 安全程式碼審查
– 漏洞研究
– 惡意程式分析
沒有程式基礎,這些工作根本無法進行。
開發安全工具
資安人員常常需要自己寫工具:
– 自動化掃描腳本
– 漏洞利用程式(Exploit)
– 資料處理腳本
– 客製化的測試工具
與開發團隊溝通
當你發現漏洞需要向開發者報告時,如果你能說:
「在
UserController.php第 47 行,$_GET['id']直接拼接到 SQL 查詢中,建議使用 PDO Prepared Statement」
比起只說「這裡有 SQL Injection」要專業得多,也更容易被採納。
後端概述
什麼是後端?
後端(Backend)是指在伺服器上執行的程式,負責:
┌─────────────────────────────────────────────────────────────────┐
│ 後端職責 │
├─────────────────────────────────────────────────────────────────┤
│ 1. 接收請求 ← 處理來自前端的 HTTP 請求 │
│ 2. 身份驗證 ← 確認使用者身份(登入、Token 驗證) │
│ 3. 權限控制 ← 確認使用者有權執行該操作 │
│ 4. 業務邏輯 ← 處理核心功能(計算、轉換、驗證) │
│ 5. 資料存取 ← 與資料庫互動(CRUD 操作) │
│ 6. 外部服務 ← 呼叫第三方 API(支付、郵件、簡訊) │
│ 7. 回傳結果 ← 將處理結果回傳給前端 │
└─────────────────────────────────────────────────────────────────┘
後端架構示意圖
┌─────────────────────────────────────────┐
│ Internet │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Web Server │
│ (Nginx / Apache) │
│ 處理靜態檔案、反向代理、負載均衡 │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Application Server │
│ (Python/Flask, Node.js, PHP, Java) │
│ 執行後端程式邏輯 │
├─────────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 路由處理 │ │ 中介軟體 │ │ 控制器 │ │
│ │ (Router) │ │ (Middleware) │ │ (Controller) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 模型層 │ │ 服務層 │ │ 工具函數 │ │
│ │ (Model) │ │ (Service) │ │ (Utils) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Database │
│ (MySQL, PostgreSQL, MongoDB) │
│ 儲存資料 │
└─────────────────────────────────────────────────────────────────┘
常見後端程式語言
| 語言 | 特點 | 常用框架 | 資安領域應用 |
|---|---|---|---|
| Python | 簡潔易學、生態豐富 | Django, Flask, FastAPI | 滲透測試腳本、自動化工具、資料分析 |
| PHP | 專為 Web 設計、部署簡單 | Laravel, Symfony | 很多網站仍使用 PHP,審計常見 |
| JavaScript (Node.js) | 前後端統一、非同步優勢 | Express, NestJS | 全端開發、API 測試 |
| Java | 企業級、強型別、穩定 | Spring Boot | 企業應用審計、Android 安全 |
| Go | 高效能、並發優秀 | Gin, Echo | 安全工具開發(如 nuclei) |
| Ruby | 優雅語法、開發快速 | Ruby on Rails | Metasploit 是用 Ruby 寫的 |
| C# | 微軟生態、企業應用 | ASP.NET Core | Windows 環境、企業應用 |
本文將以 Python 為主要範例語言,因為:
1. 語法簡潔,適合初學
2. 資安領域廣泛使用
3. 大量資安工具用 Python 開發
4. 快速驗證概念和撰寫 POC
程式語言基礎概念
無論使用哪種語言,以下概念都是通用的。
變數與資料型態
變數是儲存資料的容器。
# Python 範例
# 字串 (String)
name = "Alice"
message = 'Hello, World!'
multiline = """這是
多行字串"""
# 數字
age = 25 # 整數 (Integer)
price = 19.99 # 浮點數 (Float)
count = 1_000_000 # 可用底線增加可讀性
# 布林值 (Boolean)
is_admin = True
is_active = False
# 空值
nothing = None
# 列表 (List) - 可變的有序集合
fruits = ["apple", "banana", "cherry"]
fruits.append("orange") # 加入元素
fruits[0] # "apple"(索引從 0 開始)
# 元組 (Tuple) - 不可變的有序集合
coordinates = (10, 20)
# 字典 (Dictionary) - 鍵值對
user = {
"name": "Alice",
"age": 25,
"is_admin": False
}
user["name"] # "Alice"
user.get("email", "N/A") # 取得值,不存在則回傳預設值
# 集合 (Set) - 不重複的無序集合
unique_numbers = {1, 2, 3, 3, 3} # {1, 2, 3}
# 型態檢查
type(name) # <class 'str'>
type(age) # <class 'int'>
type(fruits) # <class 'list'>
# 型態轉換
str(25) # "25"
int("42") # 42
float("3.14") # 3.14
list("abc") # ['a', 'b', 'c']
// JavaScript (Node.js) 對照
let name = "Alice"; // 字串
const age = 25; // 數字(不分整數浮點數)
let isAdmin = true; // 布林值
let nothing = null; // 空值
let notDefined = undefined; // 未定義
let fruits = ["apple", "banana"]; // 陣列
let user = { name: "Alice", age: 25 }; // 物件
// PHP 對照
name = "Alice"; // 字串age = 25; // 整數
price = 19.99; // 浮點數isAdmin = true; // 布林值
nothing = null; // 空值fruits = ["apple", "banana"]; // 陣列
$user = [
"name" => "Alice",
"age" => 25
]; // 關聯陣列
運算子
# 算術運算子
a + b # 加
a - b # 減
a * b # 乘
a / b # 除(回傳浮點數)
a // b # 整數除法
a % b # 取餘數(模運算)
a ** b # 次方
# 比較運算子
a == b # 等於
a != b # 不等於
a > b # 大於
a < b # 小於
a >= b # 大於等於
a <= b # 小於等於
# 邏輯運算子
a and b # 且
a or b # 或
not a # 非
# 成員運算子
"a" in "abc" # True
"x" not in "abc" # True
1 in [1, 2, 3] # True
# 身份運算子
a is b # 是否為同一物件
a is not b # 是否不為同一物件
# 字串運算
"Hello" + " " + "World" # "Hello World"
"Ha" * 3 # "HaHaHa"
條件判斷
# if-elif-else
age = 18
if age < 13:
print("兒童")
elif age < 20:
print("青少年")
else:
print("成人")
# 三元運算子
status = "成年" if age >= 18 else "未成年"
# 多條件判斷
if age >= 18 and has_id:
print("可以購買")
if is_admin or is_moderator:
print("有管理權限")
# 真值判斷(Truthy/Falsy)
# 以下值視為 False:
# - False
# - None
# - 0, 0.0
# - "", [], {}, set()
if username: # 如果 username 不是空字串
print(f"歡迎,{username}")
// JavaScript 對照
if (age < 13) {
console.log("兒童");
} else if (age < 20) {
console.log("青少年");
} else {
console.log("成人");
}
// 三元運算子
let status = age >= 18 ? "成年" : "未成年";
// JavaScript 的 Falsy 值:
// false, null, undefined, 0, NaN, ""
// PHP 對照
if (age<13) {
echo "兒童";
} elseif (age < 20) {
echo "青少年";
} else {
echo "成人";
}
// 三元運算子
status =age >= 18 ? "成年" : "未成年";
// Null 合併運算子
username =_GET['username'] ?? 'Guest';
迴圈
# for 迴圈
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
# 帶索引的迴圈
for index, fruit in enumerate(fruits):
print(f"{index}: {fruit}")
# range 迴圈
for i in range(5): # 0, 1, 2, 3, 4
print(i)
for i in range(1, 6): # 1, 2, 3, 4, 5
print(i)
for i in range(0, 10, 2): # 0, 2, 4, 6, 8(步長 2)
print(i)
# 遍歷字典
user = {"name": "Alice", "age": 25}
for key in user:
print(f"{key}: {user[key]}")
for key, value in user.items():
print(f"{key}: {value}")
# while 迴圈
count = 0
while count < 5:
print(count)
count += 1
# 迴圈控制
for i in range(10):
if i == 3:
continue # 跳過此次迭代
if i == 7:
break # 終止迴圈
print(i)
# 列表推導式(List Comprehension)
squares = [x ** 2 for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
evens = [x for x in range(20) if x % 2 == 0]
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
函數
# 基本函數定義
def greet(name):
return f"Hello, {name}!"
result = greet("Alice") # "Hello, Alice!"
# 預設參數
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
greet("Alice") # "Hello, Alice!"
greet("Alice", "Hi") # "Hi, Alice!"
# 關鍵字參數
greet(greeting="Hey", name="Bob") # "Hey, Bob!"
# 可變參數
def sum_all(*numbers):
return sum(numbers)
sum_all(1, 2, 3, 4, 5) # 15
# 關鍵字可變參數
def print_info(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Alice", age=25, city="Taipei")
# Lambda 函數(匿名函數)
square = lambda x: x ** 2
square(5) # 25
# 搭配高階函數使用
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, numbers))
evens = list(filter(lambda x: x % 2 == 0, numbers))
# 函數作為參數
def apply_operation(numbers, operation):
return [operation(n) for n in numbers]
apply_operation([1, 2, 3], lambda x: x * 2) # [2, 4, 6]
類別與物件(Object-Oriented Programming)
# 類別定義
class User:
# 類別屬性(所有實例共享)
user_count = 0
# 建構函數
def __init__(self, username, email):
# 實例屬性
self.username = username
self.email = email
self.is_active = True
User.user_count += 1
# 實例方法
def greet(self):
return f"Hello, I'm {self.username}"
def deactivate(self):
self.is_active = False
# 類別方法
@classmethod
def get_user_count(cls):
return cls.user_count
# 靜態方法
@staticmethod
def validate_email(email):
return "@" in email
# 特殊方法
def __str__(self):
return f"User({self.username})"
def __repr__(self):
return f"User(username='{self.username}', email='{self.email}')"
# 建立實例
user1 = User("alice", "[email protected]")
user2 = User("bob", "[email protected]")
# 使用方法
print(user1.greet()) # "Hello, I'm alice"
print(User.get_user_count()) # 2
print(User.validate_email("[email protected]")) # True
# 繼承
class AdminUser(User):
def __init__(self, username, email, admin_level):
super().__init__(username, email) # 呼叫父類別建構函數
self.admin_level = admin_level
self.permissions = []
def add_permission(self, permission):
self.permissions.append(permission)
# 覆寫父類別方法
def greet(self):
return f"Hello, I'm admin {self.username}"
admin = AdminUser("admin", "[email protected]", 5)
print(admin.greet()) # "Hello, I'm admin admin"
例外處理
# 基本例外處理
try:
result = 10 / 0
except ZeroDivisionError:
print("除以零錯誤!")
# 捕捉多種例外
try:
value = int(input("輸入數字:"))
result = 10 / value
except ValueError:
print("輸入的不是數字!")
except ZeroDivisionError:
print("不能除以零!")
except Exception as e:
print(f"發生錯誤:{e}")
# else 和 finally
try:
file = open("data.txt", "r")
content = file.read()
except FileNotFoundError:
print("檔案不存在")
else:
print("讀取成功") # 沒有例外時執行
finally:
print("清理資源") # 無論如何都會執行
if 'file' in locals():
file.close()
# 使用 with 語句(Context Manager)
try:
with open("data.txt", "r") as file:
content = file.read()
except FileNotFoundError:
print("檔案不存在")
# 拋出例外
def validate_age(age):
if age < 0:
raise ValueError("年齡不能為負數")
if age > 150:
raise ValueError("年齡不合理")
return True
# 自訂例外
class AuthenticationError(Exception):
pass
class PermissionDeniedError(Exception):
def __init__(self, user, action):
self.user = user
self.action = action
super().__init__(f"User {user} cannot perform {action}")
# 使用自訂例外
def check_permission(user, action):
if not user.is_admin:
raise PermissionDeniedError(user.username, action)
模組與套件
# 匯入模組
import os
import sys
import json
# 匯入特定函數
from datetime import datetime, timedelta
from urllib.parse import urlparse, urlencode
# 匯入並重命名
import numpy as np
import pandas as pd
# 匯入所有(不建議)
from math import *
# 常用標準函式庫
import os # 作業系統互動
import sys # 系統相關
import json # JSON 處理
import re # 正規表達式
import hashlib # 雜湊函數
import base64 # Base64 編解碼
import urllib # URL 處理
import http # HTTP 相關
import socket # 網路 Socket
import subprocess # 執行外部命令
import threading # 多執行緒
import logging # 日誌記錄
# 使用範例
import os
print(os.getcwd()) # 當前目錄
print(os.listdir('.')) # 列出檔案
os.environ.get('PATH') # 環境變數
import json
data = {"name": "Alice", "age": 25}
json_str = json.dumps(data) # 轉成 JSON 字串
parsed = json.loads(json_str) # 解析 JSON
import re
pattern = r'\d+' # 匹配數字
matches = re.findall(pattern, "abc123def456") # ['123', '456']
import hashlib
md5_hash = hashlib.md5(b"password").hexdigest()
sha256_hash = hashlib.sha256(b"password").hexdigest()
import base64
encoded = base64.b64encode(b"Hello").decode() # 'SGVsbG8='
decoded = base64.b64decode(encoded) # b'Hello'
Web 後端開發
現在讓我們看看如何用程式語言建立 Web 後端。
HTTP 請求處理流程
┌──────────────────────────────────────────────────────────────────┐
│ HTTP 請求處理流程 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ 1. 接收請求 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ POST /api/users HTTP/1.1 │ │
│ │ Host: example.com │ │
│ │ Content-Type: application/json │ │
│ │ Authorization: Bearer xxx │ │
│ │ │ │
│ │ {"username": "alice", "email": "[email protected]"} │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 2. 路由匹配 │
│ POST /api/users → UserController.create() │
│ │ │
│ ▼ │
│ 3. 中介軟體處理 │
│ - 日誌記錄 │
│ - 身份驗證(檢查 Token) │
│ - 權限檢查 │
│ - 請求驗證 │
│ │ │
│ ▼ │
│ 4. 控制器處理 │
│ - 解析請求資料 │
│ - 驗證輸入 │
│ - 呼叫服務層 │
│ │ │
│ ▼ │
│ 5. 服務層/業務邏輯 │
│ - 執行業務邏輯 │
│ - 呼叫資料存取層 │
│ │ │
│ ▼ │
│ 6. 資料存取層 │
│ - 與資料庫互動 │
│ - CRUD 操作 │
│ │ │
│ ▼ │
│ 7. 回傳回應 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ HTTP/1.1 201 Created │ │
│ │ Content-Type: application/json │ │
│ │ │ │
│ │ {"id": 1, "username": "alice", "created_at": "..."} │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
Python Flask 範例
Flask 是 Python 最流行的輕量級 Web 框架。
from flask import Flask, request, jsonify
from functools import wraps
app = Flask(__name__)
# 模擬資料庫
users_db = {}
# ===== 中介軟體範例 =====
def require_auth(f):
"""身份驗證裝飾器"""
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers.get('Authorization')
if not token or not token.startswith('Bearer '):
return jsonify({"error": "Missing or invalid token"}), 401
# 實際應用中會驗證 Token
return f(*args, **kwargs)
return decorated
# ===== 路由與控制器 =====
@app.route('/')
def home():
"""首頁"""
return jsonify({"message": "Welcome to the API"})
@app.route('/api/users', methods=['GET'])
def get_users():
"""取得所有使用者"""
return jsonify(list(users_db.values()))
@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
"""取得單一使用者"""
user = users_db.get(user_id)
if not user:
return jsonify({"error": "User not found"}), 404
return jsonify(user)
@app.route('/api/users', methods=['POST'])
def create_user():
"""建立使用者"""
data = request.get_json()
# 輸入驗證
if not data:
return jsonify({"error": "No data provided"}), 400
username = data.get('username')
email = data.get('email')
if not username or not email:
return jsonify({"error": "Username and email are required"}), 400
# 建立使用者
user_id = len(users_db) + 1
user = {
"id": user_id,
"username": username,
"email": email
}
users_db[user_id] = user
return jsonify(user), 201
@app.route('/api/users/<int:user_id>', methods=['PUT'])
@require_auth
def update_user(user_id):
"""更新使用者"""
if user_id not in users_db:
return jsonify({"error": "User not found"}), 404
data = request.get_json()
users_db[user_id].update(data)
return jsonify(users_db[user_id])
@app.route('/api/users/<int:user_id>', methods=['DELETE'])
@require_auth
def delete_user(user_id):
"""刪除使用者"""
if user_id not in users_db:
return jsonify({"error": "User not found"}), 404
del users_db[user_id]
return '', 204
# ===== 錯誤處理 =====
@app.errorhandler(404)
def not_found(error):
return jsonify({"error": "Not found"}), 404
@app.errorhandler(500)
def internal_error(error):
return jsonify({"error": "Internal server error"}), 500
# ===== 啟動應用 =====
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
Node.js Express 範例
const express = require('express');
const app = express();
// 中介軟體
app.use(express.json()); // 解析 JSON
// 模擬資料庫
let users = [];
// 身份驗證中介軟體
const requireAuth = (req, res, next) => {
const token = req.headers.authorization;
if (!token || !token.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
};
// 路由
app.get('/', (req, res) => {
res.json({ message: 'Welcome to the API' });
});
app.get('/api/users', (req, res) => {
res.json(users);
});
app.get('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
app.post('/api/users', (req, res) => {
const { username, email } = req.body;
if (!username || !email) {
return res.status(400).json({ error: 'Username and email required' });
}
const user = {
id: users.length + 1,
username,
email
};
users.push(user);
res.status(201).json(user);
});
app.delete('/api/users/:id', requireAuth, (req, res) => {
const index = users.findIndex(u => u.id === parseInt(req.params.id));
if (index === -1) {
return res.status(404).json({ error: 'User not found' });
}
users.splice(index, 1);
res.status(204).send();
});
// 啟動伺服器
app.listen(3000, () => {
console.log('Server running on port 3000');
});
PHP 範例
<?php
// 簡單的 PHP API 範例
header('Content-Type: application/json');
// 模擬資料庫
users = [];
// 取得請求方法和路徑method = _SERVER['REQUEST_METHOD'];path = parse_url(_SERVER['REQUEST_URI'], PHP_URL_PATH);
// 路由處理
switch (path) {
case '/':
echo json_encode(['message' => 'Welcome to the API']);
break;
case '/api/users':
if (method === 'GET') {
echo json_encode(users);
} elseif (method === 'POST') {data = json_decode(file_get_contents('php://input'), true);
if (empty(data['username']) || empty(data['email'])) {
http_response_code(400);
echo json_encode(['error' => 'Username and email required']);
exit;
}
user = [
'id' => count(users) + 1,
'username' => data['username'],
'email' =>data['email']
];
users[] =user;
http_response_code(201);
echo json_encode($user);
}
break;
default:
http_response_code(404);
echo json_encode(['error' => 'Not found']);
}
?>
資料庫互動
後端程式通常需要與資料庫互動來儲存和讀取資料。
SQL 基礎
-- 建立資料表
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
is_admin BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 新增資料 (INSERT)
INSERT INTO users (username, email, password_hash)
VALUES ('alice', '[email protected]', 'hashed_password');
-- 查詢資料 (SELECT)
SELECT * FROM users;
SELECT id, username, email FROM users WHERE is_admin = TRUE;
SELECT * FROM users WHERE username = 'alice';
SELECT * FROM users WHERE created_at > '2025-01-01' ORDER BY created_at DESC;
SELECT COUNT(*) FROM users;
-- 更新資料 (UPDATE)
UPDATE users SET email = '[email protected]' WHERE id = 1;
UPDATE users SET is_admin = TRUE WHERE username = 'alice';
-- 刪除資料 (DELETE)
DELETE FROM users WHERE id = 1;
DELETE FROM users WHERE is_admin = FALSE;
-- 聯結查詢 (JOIN)
SELECT users.username, orders.total
FROM users
JOIN orders ON users.id = orders.user_id
WHERE orders.total > 100;
Python 資料庫操作
import sqlite3
import mysql.connector
from contextlib import contextmanager
# ===== SQLite 範例 =====
# 連接資料庫
conn = sqlite3.connect('database.db')
cursor = conn.cursor()
# 建立資料表
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
email TEXT NOT NULL,
password_hash TEXT NOT NULL
)
''')
# ❌ 不安全:SQL Injection 漏洞!
username = "alice' OR '1'='1"
cursor.execute(f"SELECT * FROM users WHERE username = '{username}'")
# ✓ 安全:使用參數化查詢
cursor.execute("SELECT * FROM users WHERE username = ?", (username,))
# 新增資料
cursor.execute(
"INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)",
("alice", "[email protected]", "hashed_password")
)
conn.commit()
# 查詢資料
cursor.execute("SELECT * FROM users WHERE id = ?", (1,))
user = cursor.fetchone() # 取得一筆
# 或
users = cursor.fetchall() # 取得所有
# 關閉連接
cursor.close()
conn.close()
# ===== MySQL 範例 =====
# 連接設定
config = {
'host': 'localhost',
'user': 'root',
'password': 'password',
'database': 'myapp'
}
conn = mysql.connector.connect(**config)
cursor = conn.cursor(dictionary=True) # 回傳字典格式
# 參數化查詢
cursor.execute(
"SELECT * FROM users WHERE username = %s AND is_active = %s",
(username, True)
)
user = cursor.fetchone()
# ===== 使用 ORM(SQLAlchemy)=====
from sqlalchemy import create_engine, Column, Integer, String, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String(50), unique=True, nullable=False)
email = Column(String(100), nullable=False)
password_hash = Column(String(255), nullable=False)
is_admin = Column(Boolean, default=False)
# 建立引擎和 Session
engine = create_engine('sqlite:///database.db')
Session = sessionmaker(bind=engine)
session = Session()
# 使用 ORM 查詢(自動防止 SQL Injection)
user = session.query(User).filter_by(username='alice').first()
admins = session.query(User).filter(User.is_admin == True).all()
# 新增資料
new_user = User(username='bob', email='[email protected]', password_hash='xxx')
session.add(new_user)
session.commit()
常見後端安全漏洞
這是資安人員最需要關注的部分。
SQL Injection
SQL Injection 是最經典也最危險的漏洞之一。
# ❌ 有漏洞的程式碼
def get_user(username):
query = f"SELECT * FROM users WHERE username = '{username}'"
cursor.execute(query)
return cursor.fetchone()
# 攻擊者輸入:alice' OR '1'='1
# 實際執行:SELECT * FROM users WHERE username = 'alice' OR '1'='1'
# 結果:回傳所有使用者!
# 攻擊者輸入:alice'; DROP TABLE users; --
# 實際執行:SELECT * FROM users WHERE username = 'alice'; DROP TABLE users; --'
# 結果:刪除整個資料表!
# 攻擊者輸入:' UNION SELECT username, password_hash, null FROM users --
# 結果:洩露所有密碼!
# ✓ 安全的做法:參數化查詢
def get_user_safe(username):
cursor.execute("SELECT * FROM users WHERE username = ?", (username,))
return cursor.fetchone()
<?php
// ❌ 有漏洞
username =_GET['username'];
query = "SELECT * FROM users WHERE username = 'username'";
result = mysqli_query(conn, query);
// ✓ 安全:使用 Prepared Statementstmt = conn->prepare("SELECT * FROM users WHERE username = ?");stmt->bind_param("s", username);stmt->execute();
?>
Command Injection
當程式執行系統命令並包含使用者輸入時,可能發生命令注入。
import os
import subprocess
# ❌ 有漏洞的程式碼
def ping_host(host):
os.system(f"ping -c 1 {host}")
# 攻擊者輸入:8.8.8.8; cat /etc/passwd
# 實際執行:ping -c 1 8.8.8.8; cat /etc/passwd
# 結果:洩露系統檔案!
# 攻擊者輸入:8.8.8.8; rm -rf /
# 後果不堪設想!
# ✓ 安全的做法
def ping_host_safe(host):
# 1. 輸入驗證
import re
if not re.match(r'^[\d.]+$', host): # 只允許數字和點
raise ValueError("Invalid host")
# 2. 使用 subprocess 並避免 shell=True
result = subprocess.run(
['ping', '-c', '1', host],
capture_output=True,
text=True,
timeout=10
)
return result.stdout
<?php
// ❌ 有漏洞
filename =_GET['filename'];
system("cat " . filename);
// 攻擊:?filename=;cat /etc/passwd
// ✓ 安全:使用 escapeshellargfilename = escapeshellarg(_GET['filename']);
system("cat " .filename);
// 更好:完全避免使用 shell
content = file_get_contents(validated_filename);
?>
Path Traversal(路徑穿越)
# ❌ 有漏洞的程式碼
def read_file(filename):
with open(f"/var/www/files/{filename}", 'r') as f:
return f.read()
# 攻擊者輸入:../../../etc/passwd
# 實際路徑:/var/www/files/../../../etc/passwd = /etc/passwd
# 結果:讀取到系統敏感檔案!
# ✓ 安全的做法
import os
def read_file_safe(filename):
base_dir = "/var/www/files"
# 解析絕對路徑
full_path = os.path.abspath(os.path.join(base_dir, filename))
# 確保路徑在允許的目錄內
if not full_path.startswith(base_dir):
raise ValueError("Invalid path")
# 確保檔案存在
if not os.path.isfile(full_path):
raise FileNotFoundError("File not found")
with open(full_path, 'r') as f:
return f.read()
Insecure Deserialization(不安全的反序列化)
import pickle
import yaml
# ❌ 危險:使用 pickle 反序列化不信任的資料
data = pickle.loads(user_input) # 可以執行任意程式碼!
# 攻擊者可以構造惡意 pickle 資料來執行系統命令
# 例如:os.system('rm -rf /')
# ❌ 危險:不安全的 YAML 載入
data = yaml.load(user_input) # 可以執行任意程式碼!
# ✓ 安全:使用安全的載入方式
data = yaml.safe_load(user_input)
# ✓ 安全:使用 JSON(不能執行程式碼)
import json
data = json.loads(user_input)
Server-Side Request Forgery (SSRF)
import requests
# ❌ 有漏洞的程式碼
def fetch_url(url):
response = requests.get(url)
return response.text
# 攻擊者輸入:http://localhost:6379/ (存取內部 Redis)
# 攻擊者輸入:http://169.254.169.254/ (AWS metadata,可能洩露憑證)
# 攻擊者輸入:file:///etc/passwd (讀取本地檔案)
# ✓ 安全的做法
from urllib.parse import urlparse
import ipaddress
def fetch_url_safe(url):
parsed = urlparse(url)
# 只允許 http 和 https
if parsed.scheme not in ['http', 'https']:
raise ValueError("Invalid scheme")
# 解析主機 IP
try:
ip = ipaddress.ip_address(parsed.hostname)
# 禁止內部 IP
if ip.is_private or ip.is_loopback or ip.is_reserved:
raise ValueError("Internal IP not allowed")
except ValueError:
# 如果不是 IP,可能是域名,需要進一步驗證
pass
# 白名單驗證
allowed_domains = ['example.com', 'api.example.com']
if parsed.hostname not in allowed_domains:
raise ValueError("Domain not allowed")
response = requests.get(url, timeout=10)
return response.text
Authentication Bypass(認證繞過)
# ❌ 有漏洞:不安全的密碼比較
def login(username, password):
user = get_user(username)
if user and user.password == password: # 明文比較!
return True
return False
# 問題:
# 1. 密碼應該儲存雜湊值,不是明文
# 2. 字串比較可能受到 Timing Attack
# ✓ 安全的做法
import hashlib
import hmac
def login_safe(username, password):
user = get_user(username)
if not user:
return False
# 使用安全的雜湊比較
password_hash = hashlib.pbkdf2_hmac(
'sha256',
password.encode(),
user.salt.encode(),
100000
)
# 使用常數時間比較,防止 Timing Attack
return hmac.compare_digest(password_hash, user.password_hash)
# ❌ 有漏洞:JWT 驗證不當
import jwt
def verify_token(token):
# 沒有驗證簽章!
payload = jwt.decode(token, options={"verify_signature": False})
return payload
# 攻擊者可以偽造 Token 內容
# ✓ 安全的做法
def verify_token_safe(token):
try:
payload = jwt.decode(
token,
SECRET_KEY,
algorithms=['HS256'] # 明確指定演算法
)
return payload
except jwt.InvalidTokenError:
return None
Mass Assignment(批量賦值)
# ❌ 有漏洞的程式碼
@app.route('/api/users', methods=['POST'])
def create_user():
data = request.get_json()
user = User(**data) # 直接使用所有傳入的資料
db.session.add(user)
db.session.commit()
return jsonify(user.to_dict())
# 攻擊者發送:{"username": "alice", "email": "...", "is_admin": true}
# 結果:普通使用者變成管理員!
# ✓ 安全的做法
@app.route('/api/users', methods=['POST'])
def create_user_safe():
data = request.get_json()
# 只接受允許的欄位
allowed_fields = ['username', 'email', 'password']
user_data = {k: v for k, v in data.items() if k in allowed_fields}
user = User(**user_data)
db.session.add(user)
db.session.commit()
return jsonify(user.to_dict())
安全編碼實踐
輸入驗證
import re
from email_validator import validate_email
def validate_user_input(data):
errors = []
# 驗證使用者名稱
username = data.get('username', '')
if not username:
errors.append("Username is required")
elif len(username) < 3 or len(username) > 20:
errors.append("Username must be 3-20 characters")
elif not re.match(r'^[a-zA-Z0-9_]+$', username):
errors.append("Username can only contain letters, numbers, and underscores")
# 驗證電子郵件
email = data.get('email', '')
try:
validate_email(email)
except:
errors.append("Invalid email address")
# 驗證密碼
password = data.get('password', '')
if len(password) < 8:
errors.append("Password must be at least 8 characters")
if not re.search(r'[A-Z]', password):
errors.append("Password must contain uppercase letter")
if not re.search(r'[a-z]', password):
errors.append("Password must contain lowercase letter")
if not re.search(r'\d', password):
errors.append("Password must contain digit")
return errors if errors else None
輸出編碼
from markupsafe import escape
import html
def render_user_content(content):
# HTML 編碼防止 XSS
safe_content = html.escape(content)
return f"<div>{safe_content}</div>"
# Flask 的 Jinja2 模板會自動編碼
# {{ user_input }} 會自動編碼
# {{ user_input|safe }} 不會編碼(危險!)
密碼處理
import bcrypt
import secrets
def hash_password(password):
"""安全地雜湊密碼"""
salt = bcrypt.gensalt()
return bcrypt.hashpw(password.encode(), salt)
def verify_password(password, hashed):
"""驗證密碼"""
return bcrypt.checkpw(password.encode(), hashed)
def generate_token():
"""產生安全的隨機 Token"""
return secrets.token_urlsafe(32)
def generate_reset_token():
"""產生密碼重設 Token"""
return secrets.token_hex(32)
日誌記錄
import logging
# 設定日誌
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('app.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
def login(username, password):
user = get_user(username)
if not user:
# 記錄失敗的登入嘗試
logger.warning(f"Failed login attempt for non-existent user: {username}")
return False
if not verify_password(password, user.password_hash):
logger.warning(f"Failed login attempt for user: {username}")
return False
logger.info(f"Successful login for user: {username}")
return True
# ❌ 不要記錄敏感資訊
logger.info(f"User {username} logged in with password {password}") # 危險!
# ✓ 安全的日誌
logger.info(f"User {username} logged in successfully")
實戰練習
搭建簡單的練習環境
# vulnerable_app.py - 有意設計漏洞的練習應用
from flask import Flask, request, render_template_string
import sqlite3
import os
app = Flask(__name__)
# 初始化資料庫
def init_db():
conn = sqlite3.connect('vulnerable.db')
c = conn.cursor()
c.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
username TEXT,
password TEXT,
email TEXT
)
''')
c.execute("INSERT OR IGNORE INTO users VALUES (1, 'admin', 'admin123', '[email protected]')")
c.execute("INSERT OR IGNORE INTO users VALUES (2, 'user', 'user123', '[email protected]')")
conn.commit()
conn.close()
init_db()
# SQL Injection 漏洞
@app.route('/search')
def search():
username = request.args.get('username', '')
conn = sqlite3.connect('vulnerable.db')
c = conn.cursor()
# 有漏洞的查詢
query = f"SELECT * FROM users WHERE username = '{username}'"
c.execute(query)
results = c.fetchall()
conn.close()
return str(results)
# XSS 漏洞
@app.route('/hello')
def hello():
name = request.args.get('name', 'Guest')
# 有漏洞的模板
template = f"<h1>Hello, {name}!</h1>"
return render_template_string(template)
# Command Injection 漏洞
@app.route('/ping')
def ping():
host = request.args.get('host', '127.0.0.1')
# 有漏洞的命令執行
output = os.popen(f"ping -c 1 {host}").read()
return f"<pre>{output}</pre>"
# Path Traversal 漏洞
@app.route('/read')
def read_file():
filename = request.args.get('file', 'readme.txt')
try:
# 有漏洞的檔案讀取
with open(f"./files/{filename}", 'r') as f:
content = f.read()
return f"<pre>{content}</pre>"
except:
return "File not found"
if __name__ == '__main__':
app.run(debug=True, port=5000)
練習任務
- SQL Injection
- 訪問
/search?username=admin - 嘗試:
/search?username=' OR '1'='1 - 嘗試提取所有密碼
- 訪問
- XSS
- 訪問
/hello?name=Alice - 嘗試:
/hello?name=<script>alert('XSS')</script>
- 訪問
- Command Injection
- 訪問
/ping?host=127.0.0.1 - 嘗試:
/ping?host=127.0.0.1;whoami
- 訪問
- Path Traversal
- 訪問
/read?file=readme.txt - 嘗試:
/read?file=../../../etc/passwd
- 訪問
安全工具推薦
靜態分析工具:
– Bandit(Python):pip install bandit && bandit -r your_code/
– ESLint(JavaScript):安全規則檢查
– SonarQube:多語言支援
動態測試工具:
– OWASP ZAP:Web 應用掃描
– Burp Suite:滲透測試必備
– SQLMap:自動化 SQL Injection
延伸學習
練習平台
- OWASP WebGoat:學習 Web 安全的教學平台
- DVWA(Damn Vulnerable Web Application):有意設計漏洞的 PHP 應用
- HackTheBox:真實環境滲透測試
- PortSwigger Web Security Academy:免費的 Web 安全課程
- PentesterLab:滲透測試練習
推薦資源
書籍:
– 《The Web Application Hacker’s Handbook》
– 《Black Hat Python》
– 《Violent Python》
線上資源:
– OWASP Testing Guide
– OWASP Cheat Sheet Series
– Python 官方文件
– Flask/Django 安全指南
常用框架安全文件
- Flask Security:https://flask.palletsprojects.com/en/2.0.x/security/
- Django Security:https://docs.djangoproject.com/en/4.0/topics/security/
- Express Security:https://expressjs.com/en/advanced/best-practice-security.html
- Laravel Security:https://laravel.com/docs/security
總結
這篇文章涵蓋了後端程式語言的基礎知識:
程式基礎
– 變數與資料型態
– 控制流程(條件、迴圈)
– 函數與類別
– 例外處理
– 模組與套件
Web 後端開發
– HTTP 請求處理流程
– Web 框架使用(Flask、Express、PHP)
– 路由與中介軟體
– RESTful API 設計
資料庫互動
– SQL 基礎語法
– 參數化查詢
– ORM 使用
安全漏洞
– SQL Injection
– Command Injection
– Path Traversal
– SSRF
– 認證繞過
– Mass Assignment
安全實踐
– 輸入驗證
– 輸出編碼
– 密碼處理
– 日誌記錄
對資安人員來說,理解後端程式是進行安全測試的基礎。無論是:
– 白箱滲透測試(程式碼審計)
– 黑箱測試(理解後端行為)
– 漏洞研究(分析根本原因)
– 撰寫 Exploit(利用漏洞)
– 與開發團隊溝通(提出修復建議)
都需要扎實的後端程式知識。
下一步,你可以:
– 搭建練習環境,實際測試各種漏洞
– 學習使用 Burp Suite 進行 Web 測試
– 嘗試 OWASP WebGoat 或 DVWA
– 深入學習一個框架(如 Flask 或 Django)
– 練習程式碼審計


