1. 项目概述:为什么我们需要重新审视“常见漏洞”
在安全圈里待久了,你可能会发现一个有趣的现象:每年都有层出不穷的新漏洞、新攻击手法被曝光,安全会议上的议题也越来越“高大上”,从供应链攻击到AI模型投毒,仿佛不聊点前沿的就不够专业。但当你真正去复盘那些导致实际数据泄露、服务中断甚至勒索事件的根因时,往往会发现,绝大多数问题都出在那些我们耳熟能详、甚至觉得“老掉牙”的“常见漏洞”上。
我处理过不少应急响应,从初创公司到大型企业,很多事故的源头,不是什么0day,而是SQL注入、弱口令、不安全的直接对象引用这些在OWASP Top 10榜单上挂了十几年的“老朋友”。大家不是不知道它们,而是要么觉得“我的框架有防护,不会出问题”,要么在复杂的业务迭代和紧张的工期压力下,对这些基础安全的细节逐渐麻木和忽视。
所以,今天我想抛开那些炫技的、前沿的漏洞概念,沉下心来,和你一起重新拆解这“十大常见漏洞”。目的不是简单地罗列定义,而是结合我这些年踩过的坑、挖过的洞和修过的系统,从攻击者的视角看它们为什么危险,从防御者的视角看我们日常开发运维中到底在哪里“埋了雷”,以及最关键的——如何用可落地、可持续的方式真正防住它们。无论你是刚入行的安全工程师,还是身兼数职的研发负责人,这些内容都值得你花时间重新思考一遍。
2. 漏洞核心原理与攻击者视角拆解
理解漏洞,首先要理解攻击者怎么想、怎么做。防御不能只靠黑名单和WAF规则,必须知己知彼。
2.1 注入类漏洞:攻击者的“万能钥匙”
一提到注入,大家第一反应是SQL注入。但注入的本质是: 程序将不可信的用户数据,作为命令或查询的一部分去执行 。攻击者通过精心构造的输入,改变了程序原本的执行逻辑。
SQL注入 是最经典的例子。假设一个登录查询语句是:
SELECT * FROM users WHERE username = '$user' AND password = '$pass'
如果攻击者在用户名字段输入
admin'--
,语句就变成了:
SELECT * FROM users WHERE username = 'admin'--' AND password = '$pass'
--
在多数数据库中是注释符,这意味着密码检查被完全绕过了。这还只是最简单的“永真式”绕过。联合查询注入可以窃取其他表数据,报错注入可以利用数据库错误信息回显敏感数据,盲注则可以在没有明显回显的情况下,通过程序行为的差异(如响应时间、页面内容细微变化)一点点“盲猜”出数据。我见过最顽固的SQL注入漏洞,存在于一个陈旧的报表生成模块,开发人员为了灵活性,直接拼接用户输入的列名和排序字段,即便用了参数化查询处理值,表名和列名部分依然被注入。
命令注入 则更危险。比如一个服务器功能是让用户输入IP地址进行Ping测试:
ping -c 4 $user_input_ip
如果用户输入
8.8.8.8; cat /etc/passwd
,那么分号后的命令也会被执行。在Linux/Unix环境下,反引号
、$()、管道符 |、&&、||
都可能成为命令注入的跳板。攻击者一旦成功,获取的就是服务器操作系统层面的权限。
攻击者视角 :注入漏洞是攻击者的最爱,因为成功往往意味着直接获取核心数据或系统控制权。他们会用自动化工具(如sqlmap)进行大规模探测,也会针对特定参数进行手工精细化的绕过尝试,比如用各种编码、注释技巧来绕过简单的过滤。
2.2 失效的身份认证与权限管理:通往核心区域的后门
这类漏洞常被低估,但破坏力极强。它分为两个层面: 认证 和 授权 。
认证失效
的例子比比皆是:登录页面无验证码或验证码可被绕过,导致暴力破解;修改密码功能不验证旧密码;密码重置令牌的熵值过低或有效期过长,可被预测或重放;会话ID生成算法不安全,导致可被猜测或固定。我曾审计过一个系统,其“记住我”功能生成的令牌,就是简单的
用户名+时间戳
的MD5哈希,且未加盐,极易被伪造。
授权失效
则更隐蔽。
水平越权
是最常见的:用户A通过修改URL中的ID参数(如
/api/order/123
改为
/api/order/124
),就能访问到用户B的订单数据。后端如果没有对每次数据访问都做严格的“当前用户是否拥有该数据所有权”的校验,这个漏洞就存在。
垂直越权
更危险:普通用户通过某种方式(比如修改请求中的角色参数,或访问一个本该只有管理员才能访问的隐藏API端点)获取了管理员权限。
不安全的直接对象引用
本质上也属于授权问题。当程序使用用户提供的输入(如文件名、数据库主键)直接访问底层对象时,如果没有访问控制检查,就会出问题。例如,
/download?file=../../etc/passwd
可能引发路径遍历,下载到系统敏感文件。
攻击者视角 :攻击者会系统地测试每一个需要认证的端点。对于登录,他们会尝试弱口令字典、爆破常见用户名。对于会话,他们会尝试窃取、猜测或固定会话令牌。对于授权,他们会系统性地遍历所有对象ID(如从1到10000),或使用Burp Suite等工具爬取所有功能链接,尝试以低权限用户访问高权限接口。
2.3 敏感数据泄露与安全配置错误:摆在明处的“宝藏”
这类漏洞往往源于疏忽和对默认配置的盲目信任。
敏感数据泄露 不单指数据库里的密码明文存储。它还包括:将API密钥、数据库连接字符串等硬编码在客户端代码(如JavaScript)或配置文件中,并上传到公开的代码仓库;应用程序的错误信息过于详细,将堆栈跟踪、数据库错误(包含表结构、字段名)直接返回给用户;敏感数据(如身份证号、手机号)在传输时未加密,或在前端展示时未做脱敏处理。
安全配置错误 是一个大类。比如:服务器(如Nginx, Apache)使用默认的配置和欢迎页面,暴露了软件类型和版本号,为攻击者提供了信息;不必要的HTTP方法(如PUT, DELETE, TRACE)未被禁用;安全头部(如Content-Security-Policy, X-Frame-Options, HSTS)没有正确配置,导致点击劫持、XSS等风险增加;云存储桶(如AWS S3、阿里云OSS)权限配置为公开可读可写,导致大量企业数据泄露的案例屡见不鲜。
攻击者视角 :信息收集是攻击的第一步。他们会用
nikto、dirb等工具扫描网站目录和文件,寻找备份文件(.bak, .old)、配置文件(.git, .env)、管理后台等。他们会检查HTTP响应头,寻找缺失的安全策略。他们会尝试访问常见的默认路径,如/phpinfo.php,/admin,/console。这些看似简单的信息,能拼凑出系统的脆弱点地图。
3. 防御实战:从原理到可落地的解决方案
知道漏洞怎么产生的,我们就要在设计和开发阶段筑起防线。光有意识不够,必须有具体、可执行的方法。
3.1 根治注入:参数化查询与最小权限原则
对于 SQL注入 ,唯一正确、彻底的解决方案是使用 参数化查询 (预编译语句)。它的原理是将SQL语句的结构(命令)与数据(参数)分开发送给数据库。数据库先编译语句结构,再将参数作为纯数据处理,从根本上杜绝了参数改变语句逻辑的可能。
-
正确做法(以Python的psycopg2为例) :
# 错误做法:字符串拼接 # cursor.execute(f"SELECT * FROM users WHERE name = '{username}'") # 正确做法:参数化查询 cursor.execute("SELECT * FROM users WHERE name = %s", (username,))无论
username输入什么' OR '1'='1,它都只会被当作查找的 值 ,而不会成为SQL 语法 的一部分。 -
ORM框架使用注意事项 :使用Django ORM、SQLAlchemy等框架能很大程度上避免手写SQL导致的注入,但并非绝对安全。如果使用了这些框架提供的“原始SQL查询”功能(如
MyModel.objects.raw()或session.execute(text(...))),仍需对传入的参数使用参数化方式。绝对不要用字符串格式化(f-string或%)来拼接用户输入到原始SQL字符串中。
对于 命令注入 ,防御的核心是 “白名单”校验 和 避免直接调用shell 。
- 如果用户输入必须是IP地址,就用正则表达式严格匹配IP格式。
- 如果是要选择有限的选项(如“开始”、“停止”服务),就用固定的映射,而不是直接传递字符串。
-
如果需要执行系统命令,尽量使用具体的API(如Python的
subprocess.run并传递参数列表['ping', '-c', '4', user_input_ip]),而不是启动一个shell(如shell=True),因为shell会解析各种特殊字符。 - 遵循 最小权限原则 :运行应用程序的进程或容器,不应该拥有root或管理员权限。这样即使命令注入成功,能造成的破坏也有限。
3.2 构建坚固的认证与授权体系
认证层面 :
-
强密码策略
:不仅要求长度和复杂度,更要使用
加盐哈希
(如bcrypt, Argon2)存储密码。MD5、SHA1甚至不加盐的哈希,在现在的算力下等同于明文。
bcrypt等算法设计上就很慢,能有效对抗彩虹表攻击。 - 多因素认证 :为关键操作和高权限账户启用MFA。即使密码泄露,还有第二道防线。
-
安全的会话管理
:使用框架提供的、安全的会话机制。会话ID应足够长、随机,通过安全的Cookie传输(
HttpOnly,Secure,SameSite属性)。服务器端应设置合理的会话超时时间。 - 防范暴力破解 :对登录、密码重置等接口实施限流(如同一IP每分钟最多尝试5次),并引入有效的验证码(如行为验证码,避免简单的图片验证码被OCR识别)。
授权层面 :
- 默认拒绝 :所有资源的访问,默认都应该是拒绝的,只有显式授予权限后才能访问。
- 服务端强制检查 :权限检查必须在服务端进行。前端的菜单隐藏或按钮禁用只是用户体验,不能作为安全依据。每次数据访问请求,后端都必须验证“当前登录用户是否有权操作目标数据”。
- 使用成熟的权限框架 :如RBAC(基于角色的访问控制),定义清晰的角色和权限,避免在代码中硬编码权限逻辑。对于复杂的业务场景,可以考虑ABAC(基于属性的访问控制)。
- 防止不安全的直接对象引用 :不要使用连续的、可预测的ID(如自增主键)直接暴露给前端。可以使用UUID,或者在使用自增ID时,在后端增加一次“访问控制列表”检查。对于文件下载,应该通过一个安全的代理服务来读取文件,而不是让用户直接传递文件路径。
3.3 敏感数据全生命周期保护与安全加固
数据保护 :
- 传输中加密 :全站使用HTTPS(TLS 1.2+),并配置HSTS强制浏览器使用HTTPS。内部服务间通信也尽量使用TLS。
- 存储时加密 :数据库中的敏感信息(密码、身份证、银行卡号)必须加密或哈希存储。密钥管理是关键,应使用专业的密钥管理服务,而不是写在配置文件里。
-
展示时脱敏
:在前端展示时,对敏感信息进行部分隐藏,如
138****1234。 - 错误信息规范化 :生产环境应关闭详细的调试信息,向用户返回通用的错误提示(如“服务器内部错误”),而将详细日志记录到后端的安全日志系统中,供管理员排查。
安全配置 :
- 最小化安装 :移除所有不必要的软件、服务、组件和依赖。减少攻击面。
- 及时更新 :建立漏洞情报订阅和补丁管理流程,及时为操作系统、中间件、框架和库应用安全更新。
-
安全头部
:为Web应用配置关键的安全HTTP头部:
-
Content-Security-Policy:定义允许加载资源的来源,能有效缓解XSS。 -
X-Frame-Options: DENY:防止页面被嵌入iframe,避免点击劫持。 -
X-Content-Type-Options: nosniff:阻止浏览器MIME类型嗅探。 -
Strict-Transport-Security:强制使用HTTPS。
-
- 权限最小化 :数据库连接账户、服务器进程运行账户、云服务访问密钥,都只赋予完成其功能所必需的最小权限。定期审计和轮换密钥。
4. 融入SDLC:让安全成为开发流程的一部分
漏洞往往是在代码编写时被引入的。等到测试甚至上线后再来修复,成本会呈指数级增长。因此,必须将安全活动嵌入到软件开发生命周期的每一个环节。
4.1 左移安全:在编码阶段拦截问题
“安全左移”的核心思想是,在开发的最早期阶段就考虑和引入安全实践。
- 安全需求与设计 :在项目需求分析和架构设计阶段,安全工程师或具备安全意识的架构师就应该介入。进行威胁建模,识别出系统可能面临的威胁、存在的资产和信任边界,从而在设计上就规避风险。例如,在设计一个支付系统时,就要考虑如何隔离核心支付逻辑、如何安全存储密钥、如何设计审计日志。
-
安全编码规范与培训
:为开发团队制定明确的安全编码规范,并定期进行培训。规范应具体,例如:“所有数据库查询必须使用参数化接口”、“用户输入在输出到HTML前必须进行上下文相关的编码”、“禁止使用
eval()函数处理用户数据”等。将OWASP Top 10的防护要点转化为团队内部的开发纪律。 - 使用安全的组件和库 :使用软件成分分析工具,管理项目依赖。确保使用的第三方库没有已知的高危漏洞。优先选择活跃维护、有良好安全记录的库。
4.2 自动化安全测试与代码审计
人工检查效率低且易遗漏,必须借助自动化工具。
- 静态应用程序安全测试 :SAST工具(如SonarQube, Checkmarx, 以及开源工具如Semgrep)可以在不运行代码的情况下,通过分析源代码、字节码或二进制代码,来发现潜在的安全漏洞,如注入点、硬编码密码、不安全的加密算法等。它可以集成到CI/CD流水线中,在代码提交或合并时自动扫描,发现问题则阻断流程。
- 动态应用程序安全测试 :DAST工具(如OWASP ZAP, Burp Suite的自动化扫描)从外部模拟黑客攻击,对正在运行的应用进行测试。它能发现一些SAST发现不了的运行时问题,如配置错误、身份验证缺陷等。DAST适合在测试环境或预发布环境定期执行。
- 依赖项扫描 :使用工具(如OWASP Dependency-Check, Snyk, GitHub Dependabot)持续扫描项目依赖的第三方库,当发现新的公开漏洞时及时告警,并建议升级到安全版本。
- 人工代码审计 :自动化工具不能解决所有问题,尤其是业务逻辑漏洞。定期(如每季度或每个重大版本发布前)由经验丰富的安全人员或资深开发进行人工代码审计,聚焦在核心业务逻辑、权限控制、资金操作等关键模块,非常必要。
4.3 运行时防护与持续监控
即使经过了严格测试,上线的系统仍需要最后一层防线和持续的眼睛。
- Web应用防火墙 :WAF可以作为一道有效的边界防护,能够识别和阻断常见的攻击模式,如SQL注入、XSS、CC攻击等。但它是一种基于规则或行为的检测,不能替代代码本身的安全。要正确配置WAF规则,并定期更新。同时要明白,高级的、针对特定业务逻辑的攻击可能绕过WAF。
- 运行时应用程序自我保护 :RASP技术将保护代码像疫苗一样注入到应用程序中,使其能够从内部监控自身的行为。当检测到异常操作(如疑似SQL注入的字符串操作、疑似内存破坏的指令)时,可以实时阻断并告警。RASP能提供更精准的防护,但对性能有一定影响。
- 全面的日志记录与监控 :记录所有安全相关事件,如登录成功/失败、权限变更、敏感数据访问、管理操作等。确保日志被集中收集、妥善保存且不可篡改。建立实时监控和告警机制,例如:同一IP短时间内大量登录失败、非工作时间异常访问敏感API、用户从异常地理位置的登录等。这些异常行为可能是攻击正在进行中的信号。
5. 十大漏洞之外的思考:人的因素与安全文化
技术手段再完善,如果使用技术的人缺乏安全意识,防线依然脆弱。很多严重的安全事件,始于一次简单的社工钓鱼邮件,或是一个员工图方便设置的弱密码。
5.1 社会工程学与内部威胁
攻击者越来越倾向于“绕过技术,攻击人”。他们可能伪装成IT部门发送钓鱼邮件,诱骗员工点击链接输入公司账号密码;可能通过电话冒充高管,要求财务人员紧急转账。防御这类攻击,关键在于持续的安全意识教育。定期对全员进行钓鱼邮件演练,让大家对可疑邮件保持警惕;制定清晰的信息披露和外部请求验证流程,特别是涉及资金和敏感数据的操作。
内部威胁同样不容忽视。离职员工未及时回收权限、心怀不满的员工恶意破坏、员工无意中将代码上传到公开GitHub仓库……这些都需要通过管理手段来控制,如严格的权限生命周期管理、代码仓库的保密检查、员工行为审计等。
5.2 建立积极的安全文化
安全不应该只是安全团队的责任,也不应该被视为阻碍业务发展的“绊脚石”。理想的安全文化是“共建”文化。
- 领导层重视 :安全投入需要资源,这离不开管理层的支持和理解。安全团队需要用业务能听懂的语言(如风险可能造成的财务损失、品牌声誉损失)来沟通安全的价值。
- 正向激励 :与其一味惩罚出安全问题的团队,不如设立正向激励。例如,奖励那些主动报告安全漏洞的员工(建立内部SRC),表彰在安全编码实践中做得好的团队,将安全指标纳入绩效考核。
- 降低参与门槛 :为开发人员提供易于使用的安全工具、清晰的修复指南和及时的支持。当开发人员发现一个库有漏洞时,他能快速地从内部文档中找到升级哪个版本、如何测试,而不是感到无助和抵触。
说到底,应对这些常见漏洞,是一场持久战。没有一劳永逸的银弹,它需要我们将安全的理念、技术和管理手段,像织网一样,紧密地融入到系统设计、开发、部署、运营的每一个环节中。从一行代码的编写,到一个服务器的配置,再到一次全员的安全培训,每一步都算数。当你和你的团队开始习惯性地用攻击者的眼光审视自己的系统,当安全实践变得像写单元测试一样自然时,这些“常见”漏洞,才会真正变得不常见。
462



被折叠的 条评论
为什么被折叠?



