引言
在当今互联网环境中,安全问题日益严峻。作为后端开发者,我们不仅要实现功能,更要确保应用的安全性。SQL注入、CSRF和SSRF是三种常见且危险的攻击方式,它们可能导致数据泄露、未授权操作甚至服务器被完全控制。本文将结合实际案例,详细介绍这三种攻击的原理、危害以及如何通过代码和配置进行有效防御。
一、SQL注入防御
1.1 什么是SQL注入
SQL注入是指攻击者通过在输入字段中插入恶意SQL代码,欺骗后端服务器执行非预期的SQL命令。例如,一个登录表单中,如果直接拼接用户输入,攻击者可以输入 ' OR '1'='1 来绕过密码验证。
1.2 常见攻击示例
假设我们有如下代码:
# 不安全的Python Flask示例
from flask import Flask, request
import sqlite3
app = Flask(__name__)
@app.route('/login')
def login():
username = request.args.get('username')
password = request.args.get('password')
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
cursor.execute(query)
user = cursor.fetchone()
if user:
return "登录成功"
else:
return "登录失败"
攻击者输入 username=' OR '1'='1' -- 和任意密码,查询变为 SELECT * FROM users WHERE username='' OR '1'='1' --' AND password='',从而绕过验证。
1.3 防御措施
核心原则:永远不要信任用户输入,使用参数化查询或预编译语句。
1.3.1 使用参数化查询(以Python为例)
# 安全的Python Flask示例
from flask import Flask, request
import sqlite3
app = Flask(__name__)
@app.route('/login')
def login():
username = request.args.get('username')
password = request.args.get('password')
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
# 使用参数化查询
query = "SELECT * FROM users WHERE username=? AND password=?"
cursor.execute(query, (username, password))
user = cursor.fetchone()
if user:
return "登录成功"
else:
return "登录失败"
1.3.2 使用ORM框架(如SQLAlchemy)
from sqlalchemy import create_engine, text
engine = create_engine('sqlite:///users.db')
with engine.connect() as conn:
result = conn.execute(text("SELECT * FROM users WHERE username=:username AND password=:password"), {'username': username, 'password': password})
1.3.3 其他辅助措施
- 输入验证:对输入进行白名单验证,比如用户名只允许字母数字。
- 最小权限原则:数据库账户只授予必要的权限,避免使用管理员账户连接应用。
- 错误处理:不要将数据库错误信息直接返回给用户。
二、CSRF防御
2.1 什么是CSRF
CSRF(跨站请求伪造)攻击者诱导用户访问恶意网站,在用户不知情的情况下,利用用户已登录的身份向目标网站发送请求,执行非用户本意的操作。例如,用户登录银行网站后,又访问了恶意网站,恶意网站中的图片或表单会向银行网站发起转账请求。
2.2 攻击示例
假设一个银行网站有一个转账接口:
<!-- 恶意网站中的图片标签 -->
<img src="http://bank.com/transfer?to=attacker&amount=1000" />
如果用户已登录银行网站,浏览器会自动携带Cookie,导致转账请求被执行。
2.3 防御措施
核心原则:确保请求是用户主动发起的,而不是被第三方伪造的。
2.3.1 使用CSRF Token
在每个表单中生成一个随机Token,存储在服务端Session中,并在表单提交时验证。
# Flask示例,使用Flask-WTF
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)
# 模板中:
<form method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<!-- 其他字段 -->
</form>
2.3.2 同源检测
检查请求的 Origin 和 Referer 头,如果与当前站点不同,则拒绝请求。但注意,有些浏览器可能不发送这些头,所以不能作为唯一防御。
2.3.3 自定义请求头
对于API请求,要求必须带有自定义请求头(如 X-Requested-With: XMLHttpRequest),因为跨域请求无法添加自定义头。
2.3.4 双重提交Cookie
在Cookie中设置一个随机值,并在请求参数中携带相同值,服务端验证两者是否一致。
三、SSRF防御
3.1 什么是SSRF
SSRF(服务端请求伪造)攻击者利用服务端的功能,让服务端向攻击者指定的URL发起请求,从而访问内部网络资源或进行端口扫描。例如,一个图片处理功能允许用户传入图片URL,服务端去请求该URL,攻击者可以传入 http://internal-server/secret 来获取内部数据。
3.2 攻击示例
# 不安全的Python示例
import requests
def fetch_image(url):
response = requests.get(url)
return response.content
攻击者传入 http://127.0.0.1:6379/ 尝试访问Redis,或传入 file:///etc/passwd 读取文件。
3.3 防御措施
核心原则:限制服务端发起请求的目标地址,禁止访问内网和敏感协议。
3.3.1 白名单校验
只允许请求特定域名或IP。
ALLOWED_DOMAINS = ['images.example.com']
def is_safe_url(url):
parsed = urlparse(url)
if parsed.hostname in ALLOWED_DOMAINS:
return True
return False
3.3.2 禁用危险协议
限制只允许HTTP和HTTPS,禁止file、ftp等协议。
from urllib.parse import urlparse
def safe_request(url):
parsed = urlparse(url)
if parsed.scheme not in ['http', 'https']:
raise Exception("不支持的协议")
# 进一步校验
3.3.3 解析IP并校验
将域名解析为IP,检查IP是否属于内网IP(如10.x.x.x、172.16-31.x.x、192.168.x.x)。
import ipaddress
import socket
def is_private_ip(hostname):
try:
ip = socket.gethostbyname(hostname)
return ipaddress.ip_address(ip).is_private
except:
return True
3.3.4 使用代理和防火墙
在服务端设置代理,强制请求通过代理,并在代理层面限制访问内网。
四、总结与最佳实践
本文详细介绍了SQL注入、CSRF和SSRF三种攻击的原理及防御方法。在实际开发中,我们应该:
- 始终使用参数化查询,避免拼接SQL。
- 启用CSRF防护,尤其是对状态改变请求。
- 严格限制服务端请求,避免SSRF。
- 遵循最小权限原则,为应用分配必要的权限。
- 定期进行安全审计,使用自动化工具扫描漏洞。
安全是一个持续的过程,希望本文能帮助你构建更安全的应用程序。
延伸阅读
- OWASP Top 10
- 各语言框架的安全文档
- 安全编码规范