
做了快十年Web开发也断断续续扛了七八年线上告警我对Web攻击这件事最大的感受是真正让你睡不着觉的往往不是那些听起来很酷的0day而是排行榜上翻来覆去那几张老面孔。前两年帮一家电商公司做上线前的安全巡检登录接口被人用最基础的SQL注入 payload 刷了十几万次WAF日志里全是 or 11--这种教科书级手法。你说攻击者水平高吗不高。但就是这么老套的攻击依然能一晚上试出好几个可注入点因为业务代码里总有人图省事拼字符串。所以这次我打算把这几年在实战里遇到的、以及行业公认高发的Web攻击整理成一份清单。不会只列概念重点讲清楚每种攻击为什么会成功、在你自己的系统里最可能藏在哪个角落、以及用什么样的防御姿势才能真的挡住。看完你可以直接拿这份清单去对照自己的项目做一轮自查比单纯背OWASP Top 10有用得多。1. 为什么要反复聊这十个从攻击者的视角看Web系统很多开发兄弟看到十大攻击就觉得是老生常谈但我想先说一个反直觉的事实攻击者从来不会因为你用的是新框架、新语言就绕道走。他们找的是业务逻辑里永恒不变的东西——输入不可信、权限校验缺失、组件依赖过老。这些东西跟技术栈没太大关系十年前写的PHP站和今天用Go写的微服务犯的错可能是同一个。我自己做攻防演练的时候习惯把攻击路径分成四类方便梳理攻击类别典型攻击攻击目标注入类SQL注入、命令注入、反序列化篡改服务端执行逻辑前端对抗类XSS、CSRF劫持用户会话、骗取浏览器信任资源滥用类DDoS、CC攻击、慢速HTTP耗尽服务资源信任链利用类SSRF、文件上传、路径遍历、XXE突破网络边界、读取敏感文件这四类基本覆盖了我日常收到告警的90%以上。下面的内容就按这个逻辑展开每一类里挑最典型的攻击方式讲透原理和防御。你别把它当成安全课程就当是同事之间聊项目时互相提醒哎你那个上传接口得注意啊。2. 注入家族的常青树SQL注入与命令注入为什么堵不住2.1 SQL注入从一个登录框开始的噩梦SQL注入的原理我尽量用大白话讲你的程序本来打算执行一条固定的SQL但因为把用户输入直接拼进了SQL字符串用户输入的内容里带了特殊的引号或注释符就把你的SQL语句截胡了。经典的 or 11之所以能通杀很多登录接口就是因为它把原本的密码校验逻辑变成了永真条件。在我处理过的真实案例里SQL注入的危害通常分三个层次绕过认证不需要密码直接登录后台这是最低级的利用方式。拖库用UNION SELECT把其他表的数据查出来用户名、手机号、密码哈希统统被人拿走。写马提权如果数据库账号权限够大可以通过INTO OUTFILE往服务器写文件直接拿Webshell。有个误区我必须强调用了ORM框架不等于免疫SQL注入。我在排查一个Java项目的时候发现团队用了MyBatis但SQL语句是这么写的select idfindUser resultTypeUser SELECT * FROM users WHERE name ${name} /select${name}是字符串拼接相当于裸奔。正确写法应该是#{name}走预编译。所以就算你用了框架也一定要做一次全局搜索排查有没有${}拼SQL的地方。2.2 命令注入系统调用里最容易翻车命令注入跟SQL注入思路一样不同的是它往操作系统命令里拼东西。最常见的场景是后台管理工具里的网络检测功能让你填一个IP或域名去ping然后代码这么干String cmd ping -c 1 ip; Runtime.getRuntime().exec(cmd);这时候如果用户填的是127.0.0.1; cat /etc/passwd分号后面那条命令就会被当作新命令执行。还有一种隐蔽的注入方式是利用反引号或者$(command)在不知不觉中执行任意命令。防御上就两条硬规矩能用API绝不用命令行拼。比如Java里判断网络通不通用InetAddress.isReachable()替代去调系统ping命令。必须执行外部命令时用参数数组方式调用。不要拼完整命令行字符串而是把每一个参数作为独立数组元素传给exec这样即使参数里带特殊字符也只是被当作文本不会被Shell解释。2.3 注入类防御的统一打法很多人只盯着加过滤函数比如转义单引号、过滤分号这种思路防不住变了形的payload。我比较推荐的是三层防线的纵深配合第一层参数化查询/预编译。这是SQL注入的根治方案让SQL语句结构在执行前就固定下来用户输入永远只当数据看。第二层输入校验。能明确格式的字段就做白名单校验比如IP就只允许数字和点邮箱就只允许邮箱正则。第三层数据库最小权限。业务账号只用SELECT/INSERT/UPDATE/DELETE坚决不给FILE权限至少数据库被攻破时拖库的难度会大很多。提示对用户输入的过滤要放在服务端做前端校验只是用户体验不是安全防线。3. 前端对抗的攻防战XSS与CSRF的相爱相杀3.1 XSS不直接攻击你而是控制你的浏览器跨站脚本攻击XSS的本质是把攻击者的脚本注入到你的页面里让它在你浏览器的域下执行。为什么可怕因为脚本在你浏览器里跑相当于攻击者借了你的手操作你的账号——发微博、改密码、转账全都能代劳。我按触发方式把XSS分成三类实际开发中都要考虑反射型脚本藏在URL参数里比如?keywordscriptalert(1)/script服务端没做转义直接回显到页面。这种攻击要骗用户点击恶意链接才会触发通常配合钓鱼邮件。存储型脚本被存到数据库里。比如在评论区提交scriptalert(1)/script所有打开这个页面的用户都会中招危害更大。DOM型纯前端漏洞服务端根本没参与。比如JS用innerHTML直接渲染URL参数里的内容前端代码里躺着雷。防御XSS最核心的是分上下文编码。这句话我强调多少遍都不为过在HTML标签里输出变量就用HTML实体编码在JS字符串里输出变量就用JavaScript转义在style属性里就做CSS转义。一套编码通吃所有场景是不存在的。另外两个兜底手段很有效设置CSPContent Security Policy响应头比如Content-Security-Policy: default-src self告诉浏览器只加载本域名的脚本即使攻击者注入了脚本标签浏览器也会拒绝执行。Cookie 加HttpOnly标志这样JavaScript读不到会话Cookie就算XSS成功了攻击者也没法直接偷走登录态。3.2 CSRF浏览器替你出手的影子操作CSRF跨站请求伪造的思路完全反过来攻击者不碰你的脚本而是骗你的浏览器向目标网站发请求。原理是浏览器在发请求时会自动带上目标域名的Cookie服务器分不清这个请求是用户主动发的还是恶意页面诱导的。举个真实场景你登录了某论坛然后在另一个标签页打开了一个带恶意图片的网站img srchttps://forum.example.com/post?typedeleteid10086 /只要你的论坛登录态没过期这个请求发出去帖子就被删了。整个过程你完全无感。防御CSRF现代框架基本都有内置方案但很多老项目还是漏的。我建议按三层来加固CSRF Token服务端在渲染表单时生成一个随机Token存到session提交时校验。攻击者无法跨域读取你页面的Token所以伪造不了合法请求。SameSite Cookie给Cookie加SameSiteLax或Strict浏览器会限制跨站请求携带Cookie这是成本最低的一招。校验自定义请求头如果接口只接受X-Requested-With: XMLHttpRequest攻击者的跨域简单请求就发不出来因为自定义Header会触发浏览器的CORS预检而跨域预检通常过不了。3.3 前端对抗里最容易漏的细节这里说两个我在代码评审里反复提醒的点。第一个是富文本编辑器。很多团队意识到要过滤用户内容于是对评论区做了转义结果产品要求支持富文本就把HTML标签白名单放开了一部分。问题出在onerror、onclick这类事件属性上如果只过滤了script标签而忘了事件属性攻击者用一个img srcx onerroralert(1)就能绕过。处理富文本最靠谱的方式是用成熟的白名单库比如Java的OWASP Java HTML Sanitizer把标签和属性都限定死。第二个是接口是否需要校验Referer。CSRF的Token方案也有一个坑如果Token是写在URL参数里Referer会泄露Token如果Token放在请求头里就必须用AJAX而AJAX跨域又是个坎。所以我个人更推荐把Token放在自定义Header里然后对非AJAX请求直接拒绝算是双保险。4. 信任链上的两个大洞SSRF与文件上传4.1 SSRF让服务器替你访问内网服务端请求伪造SSRF属于这几年曝光率飙升的攻击类型。原因是云服务普及之后内网资源越来越有含金量而业务里经常有这样的功能用户提交一个URL服务器去抓取这个URL的内容再返回给用户。比如在线图片代理、PDF预览、Webhook回调测试、部分SSO配置。攻击者一旦发现这个功能会把URL改成内网地址去探测http://127.0.0.1:8080探测本机端口http://192.168.1.1/扫描内网网段file:///etc/passwd直接读本地文件如果后端支持file协议我在一次攻防演练中遇到过最狠的利用目标是某个云主机上的应用攻击者通过SSRF访问云平台的元数据地址拿到了临时访问凭证结果整个对象存储的读权限都丢了。所以SSRF的防御绝对不能只靠过滤用户提交的URL要按下面这套思路来协议白名单只允许http/https禁用file、dict、gopher等协议。IP黑名单重定向也要查解析URL拿到IP后判断是否为内网IP、保留IP、云元数据IP是就拒绝。注意必须对重定向后的新URL再检查一遍否则很容易用http://example.com/redirect?target内网地址绕过。域名解析校验防止DNS重绑定攻击就是在解析域名和建立连接之间IP被悄悄换了。稳妥的做法是自行解析域名并校验IP后再用这个IP去连接而不是让系统去解析。4.2 文件上传从图片马到Webshell的经典路径文件上传漏洞之所以危险是因为它往往直接通向代码执行。攻击思路通常是这样的上传一个包含恶意代码的PHP/JSP文件扩展名改成.jpg或.png。如果服务器配置不当比如Apache对某些扩展名的解析顺序有问题或者Nginx配置了php_engine off但允许某些路径直接执行这个图片可能被当作脚本执行。如果访问不了就尝试找文件包含漏洞把上传的图片内容包含进去执行。我之前排查过一个出问题的项目对方做了文件类型校验但只检查了HTTP头里的Content-Type。攻击者在Burp Suite里手改一下请求头Content-Type从image/png换成application/x-php直接就上传成功了。正确做法我建议一套组合拳魔数校验文件头校验用程序读取文件开头的几个字节比如JPEG的FF D8 FFPNG的89 50 4E 47跟扩展名匹配才放行。随机化存储文件名上传后重新命名为UUID.jpg用户永远没法控制实际文件路径和扩展名。存储与执行分离上传的文件存到独立的文件服务器或对象存储并设置为不支持脚本执行就算文件内容有问题最多也就是个静态图片。图片重新编码如果业务只需要图片可以对上传的图片做一次重压缩/重采样这个操作会破坏图片里嵌入的恶意代码堪称杀图片马神器。4.3 这两类攻击其实都是边界问题聊到这里你会发现一个共性SSRF是让服务器主动访问了不该访问的地方文件上传是让服务器被动接收了不该接收的文件。两者本质上都是边界信任被突破了。我在设计系统架构时习惯把这两类攻击的防御放在网关层统一处理对外暴露的文件上传接口统一走对象存储对外暴露的URL抓取功能统一走代理服务并且代理服务部署在独立的内网区域只开放必要的出站白名单。这样就算业务代码写得再粗糙边界上还能兜住一层。5. 藏在对象流里的定时炸弹反序列化攻击5.1 为什么反序列化会成为高危项反序列化攻击属于原理稍微难理解、但一旦被打通就是直接RCE远程代码执行的高危漏洞。我用大白话解释序列化是把对象变成字节流方便存储或传输反序列化就是把字节流还原成对象。问题出在——还原的过程中如果数据本身是攻击者构造的就可能触发对象生命周期里的某些魔法方法这些方法里如果存在危险操作就被攻击者利用上了。最典型的场景是Java配合Commons Collections等常用库攻击者构造一个特殊的序列化流当服务端调用ObjectInputStream.readObject()时利用库内部的反射链一步步达到执行任意命令的目的。PHP也有类似问题unserialize()配合魔术方**法__destruct、__wakeup等同样能打出命令执行。我接触过的真实案例里常见入口有这么几个没加鉴权的Dubbo/Feign接口直接传序列化对象登录态用Java序列化对象存Cookie消息队列里传Java对象某些老旧的Session存储方式5.2 反序列化防御的实操方案防反序列化攻击思路跟SQL注入的参数化很像核心是让不可信数据永远不要进入反序列化流程。具体落地建议优先用JSON等纯数据格式。能传字符串就传字符串别为了省事直接传对象。大部分业务场景用JSON序列化传输足够完全不碰readObject。加签名认证。如果你的系统确实需要反序列化对象在序列化之前加一个HMAC签名反序列化之前校验签名保证数据只可能来自你自己的服务端。这样攻击者虽然能看到序列化内容但没法篡改。类白名单过滤。使用ObjectInputFilterJDK 9或ObjectInputStream的重写只允许反序列化项目里已知的类其他类直接拒绝。提示反序列化漏洞很难靠WAF拦截因为攻击流量看起来就是一段二进制乱码。所以运行时防护RASP和类过滤这类内防手段更重要。5.3 我见过的最隐蔽的反序列化入口最难查的反序列化漏洞不是那种明显传对象的接口而是隐藏在第三方组件里的。比如某次排查日志里一直看不到异常请求但服务器被种了Webshell。最后定位到一个老版本Apache Shiro框架它默认用Java序列化来加密和管理Session攻击者只需要拿到加密密钥硬编码在开源版本里就能构造恶意序列化数据直接打穿。这类问题纯粹属于供应链层面的漏洞除了及时升级组件版本、关注依赖安全通告定期做依赖SCA扫描是唯一有效的办法。6. 资源层面的持久战DDoS、CC与慢速HTTP6.1 从流量型DDoS到应用层CC我先区分一组概念DDoS分布式拒绝服务通常指流量型攻击用大量僵尸主机向目标服务器发送海量数据包把带宽或连接数打满。常见的UDP Flood、SYN Flood、ICMP Flood都属于这一类。CC攻击Challenge Collapsar属于应用层攻击。攻击者不断请求某个消耗资源的业务接口比如搜索、导出、登录用大量假请求把应用服务器的CPU或数据库连接池占满。慢速HTTP攻击最阴险的一种攻击者建立连接后长时间不发送完整数据把服务器的并发连接数一点点耗尽比如Slowloris就是发一部分HTTP头然后一直吊着。我处理过一个比较典型的CC攻击某活动页面被刷了几百万次验证码请求实际上攻击者并不需要登录就是在不停地请求下发短信的接口。因为接口没有做频率限制导致短信通道被刷爆服务器也跟着慢下来。这种攻击不靠流量也不难识别但特别消耗运维精力。6.2 资源层防御的层次化思路对DDoS这类攻击防御没法靠应用代码自己搞定要分层次布局防御层次手段应对攻击类型网络层云服务商的DDoS高防、流量清洗大流量型DDoS接入层CDN隐藏源站IP、负载均衡限制连接数基础攻击、端口扫描应用层WAF拦截恶意特征、接口限流CC攻击、慢速HTTP业务层验证码、频控、用户行为分析针对核心接口的CC特别提醒一点CDN隐藏源站IP是应用层防御的基石。如果源站IP泄露了攻击者直接打源站再多的高防也是空转。泄露源站IP的常见渠道包括DNS历史记录、子域名的A记录、SSL证书日志、邮件头的Received字段这些都要定期查一遍。6.3 慢速HTTP攻击的排查与兜底慢速HTTP攻击最难防是因为它看起来像正常用户。排查的时候可以关注这几个指标服务器连接数持续走高但CPU、带宽都正常。大量连接的请求头不完整几十秒都没发完。同一IP的连接数异常多User-Agent千篇一律或故意模仿搜索引擎。防御手段上Web服务器层面可以做三件事设置请求头超时时间比如Nginx的client_header_timeout、设置最大请求体大小client_max_body_size、限制单IP最大连接数limit_conn。但这只是基础配置真正对抗慢速HTTP最好在四层负载均衡层限流或者使用能识别慢速连接的WAF。7. 古老但从未缺席的隐患路径遍历与XXE7.1 路径遍历看起来很简单绕起来千变万化路径遍历漏洞的原理一句话用户提交一个文件名程序没做检查就去拼接路径读取了。最常见的攻击方式GET /download?filename../../../../etc/passwd如果后端直接拼接文件路径去读文件攻击者就能一路往上跳目录把系统敏感文件读出来。你可能觉得现在不会有这么傻的代码了但实际上我见过加了过滤还照样被绕过的过滤了../但攻击者用....//因为过滤后剩下的../刚好能被解析。过滤了点号攻击者用URL编码%2e%2e%2f服务端如果先解码再拼接路径正好还原。用Windows路径的..\在支持Windows的服务器上也能生效。防御路径遍历业界一致认可的方法是规范化后校验前缀。具体做法是用户提交的文件名先做一次路径规范化比如Java的Paths.get(path).normalize()然后判断规范化后的完整路径是否仍然在允许的根目录内不在就拒绝。同时拼接文件路径时不要直接用用户输入而是用文件ID去数据库里查预定义的存储路径。7.2 XXE老旧XML解析器里的老熟人XXEXML外部实体注入在前几年爆发过一阵现在很多现代框架默认禁用了外部实体但老系统里依然是高发漏洞。它的攻击原理是XML解析器遇到DOCTYPE声明的外部实体时会把实体指定的文件内容替换进来。攻击者构造一个这样的XML?xml version1.0? !DOCTYPE foo [!ENTITY xxe SYSTEM file:///etc/passwd] fooxxe;/foo服务端解析完这个XML后返回值里就带上了/etc/passwd的内容。更高级的玩法是盲打带外数据实体指向攻击者控制的服务器通过请求日志读取目标内网文件内容绕过了回显限制。XXE防御最简单有效的手段就是在解析XML之前禁用外部实体。Java的DocumentBuilderFactory要设置setFeature(http://apache.org/xml/features/disallow-doctype-decl, true)PHP要用libxml_disable_entity_loader(true)。如果业务确实需要解析用户上传的XML建议先做分析看能否用JSON替代。7.3 这两类漏洞的共同启示路径遍历和XXE本质上都是用户输入被当成了路径或结构的一部分只是载体不同文件路径 vs XML结构。在代码评审的时候我会重点检查所有接收文件路径、URL、XML/JSON数据的地方问三个问题这个输入能到达什么位置它会被用来拼接什么解析逻辑里有没有放行外部引用的开关这三个问题问完大部分配置层面的漏洞就藏不住了。8. 统一防线纵深防御与自主排查清单8.1 安全的本质是纵深不是单点铜墙铁壁聊完十种攻击我想强调一个观点没有任何一种防御能单独挡住所有攻击。WAF会被绕过参数校验总有漏网之鱼框架总有未升级的版本。所以我才一直强调纵深防御——每一层都拦截一部分风险即使某一层被突破下一层还能兜住最终为应急响应争取时间。一个相对完整的Web纵深防线可以这样设计边界层CDN、WAF、DDoS高防拦截大部分流量型攻击和自动化工具有特征的行为。接入层网关统一做鉴权、限流、参数校验、异常检测。应用层代码层面落实参数化查询、输出编码、白名单校验、安全头。数据层数据库最小权限、敏感字段加密、审计日志。运行时层RASP、依赖SCA扫描、容器基线检测。8.2 一张可以照着做自查的清单每次有新项目上线或者老项目做安全整改我会把下面这张表拉出来当checklist过一遍检查项自查问题对应攻击SQL注入是否有${}拼接SQL所有查询是否预编译SQL注入命令执行是否有Runtime.exec或shell_exec参数可控命令注入输出编码动态内容按上下文做了编码吗CSP头配置了吗XSS会话安全Cookie加了HttpOnly和SameSite吗CSRF Token覆盖了吗CSRF文件上传魔数校验了吗存储和解析分离了吗文件上传URL请求服务器发起的请求有限制协议和网段吗SSRF序列化反序列化接口有类白名单或签名吗反序列化频率控制核心接口有限流吗连接数有上限吗DDoS/CC路径读取所有文件读取都校验了规范化路径吗路径遍历XML解析解析XML时禁用外部实体了吗XXE8.3 安全巡检不是一次性的差事最后说点经验层面的东西。我见过太多团队上线前搞一次安全测试修完就以为高枕无忧了。但Web系统的攻击面是不断变化的——新功能上线、第三方组件升级、配置被调整都可能引入新的漏洞。所以我的习惯是把安全巡检嵌入到CI/CD流水线里每次构建自动跑一遍依赖SCA扫描、常见漏洞端口检查、安全检查项扫描有高危问题直接阻断发布。这比定期人工渗透测试更贴合实际因为很多漏洞是在开发过程中就埋进去的越早发现修复成本越低。另外告警日志一定要有人看。WAF、Web服务器、应用日志里的异常告警如果只是躺在监控系统里没人处理那约等于没装。可以按严重级别做分级响应注入类的告警必须立刻人工排查扫描类的告警可以按天汇总分析恶意IP自动拉黑。这样安全运维才不会变成装了个寂寞。我自己踩过一个坑在这里也提醒大家某次上线前把所有高危漏洞都修了但用的是那种教条式的修复——比如一遇到XSS就加个全局编码过滤器一遇到SQL注入就统一在控制器入口做转义。结果呢编码过滤器把富文本标签也全编码了业务功能直接废了SQL转义在预编译语句面前多此一举还引入了字符集相关的乱码问题。所以我对安全的整体态度是理解漏洞的根本成因在正确的层做正确的防御而不是看见漏洞的名称就往上堆过滤器。这也是我写这份总结的初衷——希望看完之后你能对每一种攻击的为什么有真正的理解而不是只会搜XX攻击怎么防御然后复制粘贴。