做了几年安全测试和漏洞修复,我最大的感受是:大多数安全问题翻来覆去就那么几类,原理不复杂,但总有人前赴后继地踩坑。很多公司花钱买了扫描器,却连最基本的注入和越权都没堵住,原因不是工具不好用,而是团队对漏洞的认知停留在“听说过”层面,一说修复就只知道加WAF,完全不理解漏洞为什么会产生。
这篇文章我想把这几年和漏洞打交道的过程做个沉淀,把最常见的漏洞类型、攻击链路、修复思路整理成一份能直接用的清单。不管是正在补课的新人,还是要给团队做安全培训的负责人,希望你看完能对“漏洞到底是怎么回事”有一个整体的判断力——知道什么场景容易出什么问题,出了问题该怎么排优先级,而不是盲目跟风买设备、上产品。
1. 注入类漏洞:拼接字符串是最危险的习惯
注入类的核心痛点就一句话:该当数据处理的东西,被当成代码执行了。这类漏洞在各类榜单上常年霸榜,不是因为它高级,而是因为开发习惯太普遍——只要有人喜欢拼字符串,就有人能利用这条缝隙。
1.1 SQL注入:一条引号引发的数据泄露
SQL注入是最经典、也是最好理解的一种。当后端代码把用户输入直接拼进SQL语句时,输入里的引号和语句片段就被数据库当成指令执行了。
最常见的问题写法长这样:
$sql = "SELECT * FROM users WHERE id = " . $_GET['id']; $result = mysqli_query($conn, $sql);攻击者在浏览器里输入1 OR 1=1,这条SQL就变成SELECT * FROM users WHERE id = 1 OR 1=1,直接把整张表查出来。更狠一点,配合UNION SELECT把密码字段带出来,或者用INTO OUTFILE写一句话木马。
我见过不止一次这样的案例:某后台管理系统的导出功能,日期参数是拼进去的,攻击者通过时间盲注把管理员的密码哈希一点点拖了出来,然后离线跑字典破解,最后登录后台删数据。整个过程没触发任何报警,因为日志里看起来就是正常的几次导出请求。
修复这事儿其实不难:查询一律参数化,让数据库把输入永远当值处理而不是当代码拼进执行计划。以PHP为例:
$stmt = $conn->prepare("SELECT * FROM users WHERE id = ?"); $stmt->bind_param("i", $_GET['id']); $stmt->execute();Java、Python、Go都有对应的PreparedStatement、参数绑定机制,思路完全相同。参数化之后,用户再输入什么花活,都只是数据,SQL结构纹丝不动。
提示:不要以为加了ORM框架就万事大吉。ORM的链式查询API也有不少场景存在字符串拼接,比如动态排序字段、复杂原生查询语义,一旦有字段名由前端传入,理论上是存在同类注入路径的,只不过利用难度高一些。
1.2 命令注入与模板注入:从参数到RCE的距离
比起SQL注入,命令注入和模板注入的危害更直接——前者可能导致服务器被人接管,后者往往直接通向远程代码执行。
命令注入常见于系统管理类功能:某个接口要ping一个IP、要解析一个域名、要执行一个外部程序。开发图省事,直接把参数拼进系统命令:
ip = request.args.get('ip') result = subprocess.run('ping ' + ip, shell=True)此时攻击者传入127.0.0.1; cat /etc/passwd,分号后面的内容就会被shell当成新命令执行。修复方式也很明确:避免shell=True,用参数列表方式传参,并且对输入做严格白名单校验——判断是否为合法的IP格式,不是就直接拒绝。
模板注入则更隐蔽。一些内容管理系统允许用户自定义邮件模板、报表模板,如果模板引擎直接渲染了用户可控的字符串,攻击者通过构造特定语法,就能调用底层函数执行代码。以Jinja2为例,精心构造的payload可以读取环境变量、执行系统命令。这类漏洞的修复核心是:不要让普通用户直接操作模板源文件,模板渲染走固定页面,数据内容走变量注入,并严格隔离模板解析环境。
1.3 注入漏洞的统一修法:永远把数据与代码分开
注入漏洞虽然形态各异,但修复思路高度统一:输入校验白名单化、参数绑定或参数化调用、最小权限运行。
第一层是校验:凡是进入业务逻辑的参数,必须先验证类型、格式、长度、取值范围。手机号必须有手机号的正则格式,IP必须是合法的IPv4/IPv6格式,ID必须是整数。校验通过才能往下走。
第二层是绑定:数据库查询用参数化,系统命令调用用参数数组方式,模板渲染和代码生成绝不直接拼接用户输入。
第三层是最小权限:数据库账号别给DROP、FILE权限,别用root跑Web服务,容器里运行命令的账户不给sudo权限。三层都做了,即便某条链路出现绕过,损失也能控制住。
这三层做法不是安全团队逼出来的教条,而是每一条都有对应着真实攻击路径。我有次排查一个老系统,发现管理员账号的权限是DBA级别,注入点虽然被修了,但如果哪次的修复漏了一处,攻击者就能直接遍历所有数据库。把权限收窄后,同样的漏洞就算再出现,利用成本也翻了十倍不止。
2. 前端防线的三连坑:XSS、CSRF与点击劫持
如果说注入类漏洞是后端代码的习惯问题,那前端的坑就更偏“默认信任”了。很多开发者觉得输出到页面的内容没啥风险,结果XSS、CSRF这些漏洞往往就在这种放松中爆发。
2.1 XSS三种形态与存储型危害
XSS(跨站脚本攻击)的本质是:攻击者提供的恶意脚本,被目标网站当成了可执行内容输出。按照触发位置分成三种:
- 反射型:恶意脚本藏在URL参数里,每次都要欺骗用户点击构造好的链接才能触发。
- 存储型:恶意脚本保存在服务器上(比如评论区、个人简介、商品标题),所有访问这个页面的用户都会被打中,危害最大。
- DOM型:不经过后端,纯粹在前端JavaScript里通过DOM操作把恶意代码插入页面执行。
存储型XSS我印象最深。某内容社区允许用户提交昵称,昵称在后端没有做过滤,前端渲染时直接插进了HTML。攻击者把昵称设置成一段JavaScript,指向一个钓鱼页面,管理员在后台一打开用户列表,浏览器就带着管理员的登录态请求了那个钓鱼页面,结果会话凭证被转走。管理员账号说丢就丢,前后不到十分钟。
修复XSS的核心是输出编码:什么时候都不把用户输入当成HTML去渲染。具体来说:
- 在HTML标签内输出,转义
<、>、&、"、'; - 在JavaScript字符串中输出,转义引号和反斜杠;
- 在URL属性中输出,做URL编码;
- 富文本场景用白名单标签过滤,而不是黑名单禁用。
同时配合响应头Content-Security-Policy限制脚本来源,即使某处漏了,浏览器也会拦下外联的恶意脚本。这是一层非常重要的兜底,很多团队完全忽略了它。
2.2 CSRF与点击劫持的利用差异
CSRF(跨站请求伪造)和XSS容易混淆,实际上完全不同。XSS是利用合法用户在页面里执行脚本,CSRF是让合法用户在不知情的情况下发起攻击者指定的请求。
举个例子:某后台有修改绑定手机的接口,请求方式是GET,参数是newPhone和token。攻击者在论坛上放一张图片,图片地址就是后台接口的完整URL。管理员只要在登录态下点开这个帖子,浏览器就会自动请求那个接口,把绑定手机改成攻击者的号码。整个过程管理员毫无感知,攻击者拿到了关键的账号恢复入口。
CSRF的修复三板斧:
- 同步Token校验:每个请求带上随机的CSRF Token,服务端校验存在且未过期;
- 同源检查:校验请求的
Origin和Referer是否来自本站; - 敏感操作二次验证:修改手机、绑定邮箱、转账这些操作,要求输入原密码或短信验证码。
点击劫持则更加“视觉化”。攻击者构造一个透明的页面叠放在正常按钮上方,用户看到一个诱导按钮,实际点击的是被隐藏的目标按钮。因为整个过程用户都是主动操作的,浏览器安全机制很难防御。后端加一个X-Frame-Options: DENY响应头,或者用CSP的frame-ancestors指令,禁止页面被嵌入第三方iframe,就能基本杜绝。
2.3 前端防御的正确姿势
前端漏洞修复不能只看“输出编码”这一点。我建议团队做一次整体梳理:
第一,全局搜索DOM操作位置,重点关注把innerHTML、outerHTML、document.write直接赋值的场景,统一改成textContent或安全的DOM构建API。
第二,梳理所有敏感接口,逐个检查请求方法是否合理、是否带了CSRF防护、是否有额外的身份校验字段。特别留意使用GET方法完成状态修改的接口。
第三,设置安全响应头。除了X-Frame-Options和CSP,还有X-Content-Type-Options: nosniff,防止浏览器把上传文件或其他类型资源当脚本解析。这三个响应头配起来几分钟,能挡掉一大片基于浏览器默认行为的攻击。
前端安全很容易被当成“小问题”,实际上一旦出了存储型XSS,整个系统的会话安全都形同虚设。把它排在和SQL注入同等的优先级,一点不过分。
3. 服务端信任边界的失守:文件上传、SSRF与XXE
这一类的共同点是:服务端对外部数据太过信任,在输入进入系统内部逻辑之前,没有做必要的隔离和约束。尤其是文件上传和SSRF,是攻破内网的两条高频路径。
3.1 文件上传:后缀白名单不是终点
文件上传功能几乎每个业务系统都有:头像、附件、导入Excel、发布图片。漏洞也几乎都出在这些地方,因为开发往往只做了后缀校验,甚至只是前端校验。
攻击者的思路很简单:
- 先上传一个
.jpg文件,但Content-Type设置为application/x-php,配合Apache的解析特性,某些老配置下能被当成PHP执行; - 或者上传
.php.jpg、.php%00.jpg这类文件,看哪个环境配置存在解析歧义; - 更直接的是上传一个合法的WebShell,然后通过目录遍历找到路径,直接访问执行。
我的建议是文件上传的修复至少做四层:
- 白名单后缀:不是允许哪些,而是只允许业务必要的扩展名,其他一律拒绝;
- 内容校验:图片用
getimagesize或服务端图形库重新解码编码,不保留原始字节流;文档类校验文件头的魔数; - 存储隔离:上传文件保存到独立存储服务或独立目录,执行权限一律去掉,Web服务器不能把该目录下的文件当脚本执行;
- 随机命名:文件名改成随机字符串,后缀固定为业务白名单内的扩展名,剥离原始文件名。
曾经有一次渗透测试,我在某系统上传了一个图片马,因为对方只做了后缀检测,没有做内容检测,图片的EXIF信息里塞的PHP代码被解析执行了。这类问题在加固后基本可以杜绝,但前提是白名单覆盖的是完整链路,而不是只堵了一个入口。
3.2 SSRF:从外网打进内网的后门
SSRF(服务端请求伪造)是很多内网攻击的起点。它发生在:服务端接收了一个URL参数,然后由服务器去请求这个URL。典型的场景有:图片代理、Webhook配置、远程文件导入、URL预览。
攻击者把参数改成内网地址,比如http://192.168.1.10:80/或http://127.0.0.1:3306/,服务器就会去访问内网服务。通过返回的内容或响应时间,攻击者能探测内网端口、读取内网服务信息,再配合其他漏洞进入内网横向移动。
修复SSRF的思路相对固定:
- 协议限制:只允许HTTP/HTTPS,禁止
file://、gopher://、dict://等协议; - 地址限制:解析目标域名的IP,过滤内网IP段、回环地址、链路本地地址,禁止直接访问;
- 端口限制:业务需要只开放特定端口,其余一律拒绝;
- 请求抓包核对:有的攻击会用DNS重绑定绕过IP校验,这时候最好的兜底是实际访问后再核对一次Socket连接的IP,确保连接对端就是白名单范围内的目标。
有一次排查某个在线预览服务,攻击者通过构造特殊URL读取到了本地的配置文件。原因就是代码直接用了Ruby的open()方法,方法支持file://协议。把协议白名单设置好之后,这个问题就没再出现过。
3.3 XXE:被忽略的XML解析器
XXE(XML外部实体注入)曾经是Java和PHP老系统中的常客,现在依然偶尔出现,尤其是那些处理SOAP协议、XML配置导入、Office文档解析的服务。
攻击原理很简单:XML规范允许定义外部实体,如果解析器开启了外部实体解析,而且没有禁止外部实体加载,攻击者就可以在XML里写:
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]> <root>&xxe;</root>解析结果会把外部文件内容带入XML响应里,攻击者因此读取到服务器上的敏感文件。更高级的利用还能发起内网端口探测、读取源代码。
修复方式非常直接:如果业务不用DTD,直接禁用DTD和外部实体。Java解析器设置setFeature,PHP设置libxml_disable_entity_loader(true),Python的lxml关闭resolve_entities。如果确实要用XML,而且必须支持外部实体,那就要把白名单范围做得很窄,并且对外部实体内容做严格过滤。
现在很多系统已经改用JSON传输数据,XML类接口大幅减少,但遗留系统和第三方对接过程中依然存在,渗透测试时值得重点盯一下。
4. 权限与认证的薄弱环节:越权、暴力破解与反序列化
注入、XSS这些是“代码逻辑”层面的漏洞,权限与认证这一章则是“业务逻辑”层面的漏洞。这类漏洞没有统一的payload可查,扫描器很难发现,往往需要人工测试,所以也最考验开发者的安全意识。
4.1 IDOR越权:可遍历ID带来的越权
越权漏洞,特别是IDOR(不安全的直接对象引用),是说攻击者通过修改URL或参数中的ID,访问到了不属于自己的资源。
最典型的例子:用户下单后查看订单详情,接口是/order/detail?id=10001,后端查询时只用了这个ID,没有验证当前登录用户是否是这笔订单的归属者。攻击者把ID从10001改成10002,就能看到别人的收货地址、手机号、支付记录。
这种漏洞的可怕在于开发完全没写错代码——查询、返回、展示,每一步都是正常的,唯一的疏漏是少了一次权限校验。
修复方案是在服务端对每个资源访问都做归属权校验:查数据库的时候,条件里加上WHERE order_id = ? AND user_id = ?,后面的user_id从当前会话取,绝不从前端JSON或URL参数里取。还要额外注意,有些团队用Nginx直接按id匹配静态文件路径,比如/files/{id}/xxx.pdf,这类场景也要做同样的鉴权。
我做代码审计时会给团队立一个规矩:涉及用户数据的接口,随手写一条断言,校验数据归属和角色权限,没有校验不许上生产。
4.2 弱口令与无效限速:暴力破解为何屡试不爽
很多系统像堡垒一样防住了所有漏洞点,最后却在登录页被弱口令攻陷。原因不外乎两个:一是用户密码太简单,二是登录接口没有有效的防暴力破解机制。
暴力破解的手段不外乎:
- 使用默认密码表(admin/admin123、test/test)逐个尝试;
- 批量撞库,拿已经泄露的帐号密码组合来撞未改密用户;
- 对某个特定用户发起高频字典攻击,只要能连续尝试,大部分弱密码都撑不过几轮。
修复要从两端同时入手。入口端,登录接口必须部署防暴力破解:IP维度限流、账号维度锁定、验证码(图形验证或滑块验证均可)在连续失败多次后弹出来。配置端,强制初始密码修改、弱密码复杂度校验、密码定期轮换。更关键的是公司内部系统启用双因素认证,即使某位用户的密码真的泄露,攻击者也进不来。
我不太赞成用单纯的次数锁定来防破解——因为攻击者可以用分布式IP池绕开IP维度限制。更合理的做法是:连续5次失败就要求图形验证码,连续10次失败锁定15分钟,锁定时间随次数递增,并允许用户通过绑定的手机号自助解锁。这样既防止滥用,又不影响正常用户体验。
4.3 反序列化:一个对象还原引发的代码执行
反序列化漏洞属于原理较深、利用门槛较高但一旦成功危害极大的类型。它发生在:把二进制格式或字符串格式的序列化数据还原成对象的过程中,如果还原逻辑里调用了某些“魔法方法”,攻击者就能控制参数触发危险操作。
PHP中经典的利用点是一些魔法方法像__wakeup()、__destruct()在对象还原时自动执行;Java中利用点是ObjectInputStream.readObject()配合存在危险方法调用链的Gadget类。攻击者会构造一个恶意的序列化字符串,传给任何接收反序列化数据的入口(Cookie、Session存储、缓存数据、消息队列消息),从而在目标机器上执行任意代码。
修复优先选替换方案:能不用原生序列化就不用,换成JSON等纯数据格式。如果一定要用,则采用白名单机制,只允许反序列化指定类的对象,并对反序列化数据签名校验,杜绝恶意流进入解析流程。另外,升级依赖库版本也能堵住很大一部分已知Gadget链。很多反序列化漏洞的根因不是业务代码本身,而是低版本第三方库。
这类漏洞不是每个项目都遇得到,但只要遇到,基本都是严重以上等级。建议团队排查所有入口是否使用了原生反序列化函数,有的话单独列一个专项治理计划。
5. 信息泄露与配置缺陷:最容易被低估的隐患
上面聊的都是主动攻击,这一章说的信息泄露与配置缺陷更像是“被动送给攻击者的礼物”。很多团队对外部漏洞严防死守,却忘了自己不小心在响应头、错误页、备份文件里泄露了大量信息。
5.1 错误页与调试信息泄露
生产环境开着debug模式,可以说是最令人无语的安全漏洞之一。页面报错时直接把堆栈打印出来,攻击者看到代码路径、数据库版本、中间件信息、甚至配置文件片段,后续的定向攻击就变得非常容易。
还有一类是备份文件和源码泄露:.git目录可以直接访问、备份压缩包放在Web目录下、开发环境测试脚本部署到了生产。攻击者把这些文件下载下来,代码审计就像看自家项目一样轻松。
修复方式很简单,但需要刻意检查:
- 生产环境关闭debug、trace、verbose日志;
- 统一错误页面,只展示友好提示,不暴露任何异常详情,详细错误走服务端日志;
- 检查Web根目录下是否有
.git、backup.zip、.sql、test.php这些文件,发现立即移走; - 用目录枚举工具或者手动检查排除规则,把不应该被公众访问的路径在Nginx/Apache配置里显式拒绝。
5.2 传输与存储中的明文问题
明文问题分两类:传输过程明文化和存储过程明文化。
传输过程,很多系统内部接口还在走HTTP而非HTTPS。攻击者在同一Wi-Fi或者在运营商链路中做中间人抓包,账号密码、Token、业务数据直接裸奔。修复就是全局启用HTTPS,并在响应头加Strict-Transport-Security(STS),强制浏览器在一段时间内只走HTTPS连接,防止降级攻击。
存储过程的明文问题,常见于:
- 密码库用了明文或只用MD5摘要存储,没有加盐,撞库时能反向匹配;
- 用户手机号、身份证号这类敏感字段在数据库中直接明文保存,一旦脱库就是批量泄露。
存储层的修复思路是:用户密码用bcrypt、argon2这类自适应慢哈希,并加随机盐;敏感数据字段按合规要求做加密存储;端到端的加密传输尤其需要注意日志系统,别把账号密码打到日志文件里。曾有一起事件,攻击者没有黑进业务系统,而是直接下载了未授权访问的日志文件,从中提取到了上万条明文密码。
5.3 几个必查的默认配置陷阱
- 目录列表:Apache/Nginx的
autoindex开启后,访问没有index文件的目录就能列出所有文件名,源码、备份、内部资料一览无余。关闭方法:autoindex off,或者把Nginx的index配置清除掉。 - 默认账号:很多中间件和数据库安装时会生成默认账号(如某数据库的
admin/admin),上线前必须改掉。开发环境、测试环境和生产环境的密码要完全区分,内部测试库的弱密码也是泄露源头。 - CORS配置过宽:
Access-Control-Allow-Origin: *允许任意跨域读取数据,配合CSRF或XSS,用户数据可能被第三方站点拉走。正确配置应该是:一个精确的允许列表,并且非公开数据接口不设置通配符。
配置类问题往往不被当成“漏洞”,但它确实是行业漏洞库里的常客。强烈建议上线流程里加一个配置检查脚本,把上述几项做成自动化校验,每次发版自动跑一遍,发现违规直接阻断发布。由此,能看到很多安全隐患都在配置检查中消解掉大半。
6. 按优先级排险:从自动化扫描到人工复核
各项漏洞类型都梳理完了,解决完“这是什么”之后,真正关键的是怎么把它们变成一套可持续的行动方案。最后这章想分享的是我平时在项目里实践出来的漏洞排查顺序和工具使用方式。
6.1 常用工具链与使用场景
安全测试工具很多,但每一类工具适合的场景不一样,选对工具能省大量时间:
| 工具/类型 | 主要用途 | 适用阶段 |
|---|---|---|
| 抓包代理工具 | 分析与修改HTTP请求,测试越权、参数篡改、CSRF | 人工测试 |
| 注入检测工具 | 自动化检测SQL注入、XSS、SSRF等Web漏洞 | 上线前扫描 |
| 端口/服务扫描工具 | 发现对外开放的端口与服务,识别默认配置风险 | 资产梳理 |
| 依赖安全检查工具 | 检测第三方库已知漏洞(如某些版本的反序列化问题) | 持续集成 |
| 源码静态分析工具 | 从代码层面发现不安全函数、硬编码密钥、注入风险 | 开发阶段 |
以经验而言,老老实实按顺序做往往效率最高:先用端口/服务扫描工具摸清资产边界,再做Web应用漏洞扫描,最后抓包工具逐接口人工复核业务逻辑漏洞。
很多团队买了扫描器,发现报告生成得很快,结果全是中低危的噪音。原因就是扫描器只能发现“规则已知”的问题,像越权、逻辑漏洞这类没有固定模式的缺陷,必须人工测试。所以我的排险流程一定包括“自动化工具+人工复核”两步,缺一不可。
6.2 修复优先级怎么排
漏洞修复不能“扫出来就全量修”,一定要排优先级,否则会被低危问题淹死,真正的关键风险反而无人处理。
我的排序逻辑是看三件事:
- 可被直接利用的程度:无需登录或只需低权限就能利用的,优先级最高。比如未授权访问、存储型XSS、RCE类漏洞。
- 影响范围:能影响所有用户还是只影响个别角色?管理员权限被拿下,基本就是最高优先级。
- 被攻击的难度:需要社工配合、需要特定浏览器版本、需要内网环境,这些会降低优先考虑价值。
参考这个思路,我会把漏洞分成四个梯队:
- P0:远程代码执行、未授权访问、SQL注入、存储型XSS——立即修,4小时内响应,最好当天上线修复补丁。
- P1:越权、SSRF、批量账号信息泄露、弱口令——2-3天内修完,并同步排查日志确认没有被利用痕迹。
- P2:信息泄露、配置缺陷、缺少安全响应头——下一个版本周期修复即可,但要进入待办清单。
- P3:COS配置过宽、日志记录不完整——持续改善,不用单独排期。
这里需要强调的是,P2和P3类问题不能永远不修。因为攻击者经常组合利用多个低危问题打出一条高危链路,比如“日志泄露+弱口令+CORS配置过宽”组合起来,往往就能完成一次完整的横向攻击。
6.3 建立自己的漏洞清单与复测机制
最后建议每个团队维护一份自己的“漏洞历史清单”,把这段时间踩过的坑记录在案。这份清单不需要多复杂,包含:漏洞类型、出现位置、数据库表或API接口、修复方案、发现人、复测状态。
这么做的好处是:同样的错误,我们这个行业每天都在不同团队重复发生;而有了清单,新成员接手时可以先对照历史清单自查一遍,把常见的“历史遗留”问题先排查掉,再开始新的功能开发。
复测机制也很有必要。每次修复后都要跑一遍“回归测试”——用和发现漏洞时相同的payload、相同的路径去验证漏洞是否真的关闭了。有些修复只是加了个前端校验,绕过前端直接构造HTTP请求还是能打穿。只有重新用攻击者视角去测试,才能确认修复到位。
安全修复没有“一劳永逸”这回事,代码在变、依赖在变、业务也在变,过三个月再扫描,大概率又有新的问题。把漏洞治理变成周期性的习惯,比憋大招搞一次“安全月”有用得多。
我自己在带项目时,曾试过把代码审计、外部渗透测试、配置基准检查三个环节穿插进每一次迭代中,虽然每次投入的精力不多,但持续半年后,线上系统的重点漏洞基本清零。治安全漏洞,靠的从来不是哪一次大扫除,而是那套反复迭代的流程能不能跑起来。希望这份清单能给你一点启发,从你自己的第一份漏洞记录开始。