很多刚接触网络安全的朋友,第一次听到“漏洞”这个词,总觉得是黑客电影里那种神秘又遥远的东西。其实漏洞就在我们身边,小到个人博客,大到企业核心系统,每天都在被扫描、被试探。作为这些年一直在做安全测试和应急响应的人,我可以很负责任地告诉你:绝大多数安全事故,翻来覆去用的都是那几个经典的漏洞类型。把“网络安全常见十大漏洞”的原理和防御吃透,你就能挡住至少八成的基础攻击。这篇文章不搞虚的,从零基础视角出发,把每个漏洞的成因、危害、防御方案拆开揉碎讲清楚,配合我实际踩坑得来的经验,一篇看完就能上手排查自己的项目。
1. SQL注入:最经典的数据库杀手
1.1 为什么一个输入框就能让数据库裸奔
SQL注入是所有漏洞里历史最悠久、影响也最直接的一类。它的成因一句话就能概括:开发者把用户输入的内容,直接拼接到了SQL查询语句里,让数据变成了代码的一部分。
举个例子,一个登录功能,很多人早期会这样写:
SELECT * FROM users WHERE username = 'admin' AND password = '123456'如果用户名输入框里填的是admin' --,拼接之后语句就变成了:
SELECT * FROM users WHERE username = 'admin' -- ' AND password = '123456'--在SQL里是注释符,后面的密码判断整个被注释掉,攻击者不需要知道密码就能以admin身份登录。这只是最基础的单引号闭合。再往后,还能用UNION SELECT把其他表的数据拖出来,用LOAD_FILE读服务器文件,甚至借助INTO OUTFILE写入web目录拿到系统权限。
1.2 危害程度不是说说而已
SQL注入的危害上限非常高。轻则绕过登录、篡改页面内容,重则直接把整个数据库脱走——用户名、密码哈希、订单记录、身份证号,全量泄露。我在一次应急响应中处理过一个用了开源电商系统二次开发的项目,因为商品搜索功能存在注入点,攻击者把所有会员表数据全部导出,还在服务器上留下了后门脚本。事后排查发现,注入点仅仅是搜索框没有做任何参数化处理。
这里要特别强调一点:很多人以为“我用了过滤函数就安全了”,比如把单引号替换掉、把select替换掉。实际上,黑名单过滤的思路在有经验的测试者面前不堪一击。大小写绕过、编码绕过、注释符绕过,方法多到超乎想象。真正可靠的方案是下面要说的参数化查询。
1.3 防御方案与实操要点
防御SQL注入,第一选择永远是预编译语句(参数化查询)。它的核心逻辑是:SQL结构提前固定,用户输入只作为“参数值”传入,永远不会参与语句结构的拼接。
# 错误示范:拼接SQL sql = "SELECT * FROM users WHERE username = '" + username + "'" # 正确示范:参数化查询 cursor.execute("SELECT * FROM users WHERE username = %s", (username,))不同语言对应不同写法,但思路一致:Java用PreparedStatement,PHP用PDO::prepare,Go用database/sql的占位符。只要参数化做到位,SQL注入基本就堵死了。
另外两条必须配合的机制:一是数据库账号最小权限,应用账号不要用root或dba,只给增删改查所需的表级权限;二是在代码层面做输入校验,比如搜索接口只允许字母和数字,用白名单正则直接拦掉特殊字符。纵深防御的思路是:即使第一层被绕过,后面的权限限制还能兜底。
2. XSS跨站脚本:前端世界的信任危机
2.1 反射型、存储型、DOM型怎么区分
XSS(Cross-Site Scripting)的本质,是把恶意脚本注入到用户浏览器中执行。很多初学者分不清它的三种类型,我用最直白的方式讲:
- 反射型:恶意脚本通过URL参数带入,服务端未经处理直接渲染回页面。比如搜索框输入
<script>alert(1)</script>,页面立刻显示这段HTML。它只能打给点了链接的人,属于“一次性利用”。 - 存储型:恶意脚本被保存到了数据库里,任何访问该页面的用户都会触发。比如在留言板提交一段脚本,之后每个打开留言板的人都会中招。这种杀伤力最大,因为影响的人是持续的。
- DOM型:不需要经过服务端,纯粹是前端JavaScript读取URL参数并直接操作DOM时产生的。比如从
location.hash取值插入页面,客户端的脚本自己把自己卖了。
2.2 一个Cookie被拿走意味着什么
XSS最常用的危害是窃取Cookie。攻击者在页面上执行一段JavaScript,把当前用户的Cookie发送到自己的服务器,拿到Cookie就可以伪装成这个用户,直接登进后台。这就是所谓的“会话劫持”。
Cookie之外,XSS还能做更多事情:伪造登录表单诱导用户输入账号密码、窃取页面里的敏感信息、修改页面内容发布垃圾消息、甚至通过调用后台API执行敏感操作。尤其在后端接口没有做好CSRF防护时,XSS几乎等于拿到了该用户的一切权限。
2.3 三层防御缺一不可
防御XSS不是靠一次操作就完事,它需要三道防线同时生效:
第一层是输出编码。在服务端把用户输入的内容渲染到HTML页面之前,对< > & " '等字符做HTML实体编码,让脚本变成纯文本显示。不同位置(HTML标签内、属性内、script内、CSS内)需要不同的编码方式,新手最容易漏掉属性内的编码,导致" onmouseover=...这种攻击手法生效。
第二层是Content Security Policy(CSP)。通过HTTP响应头限制页面允许加载的脚本来源:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com这样即使攻击者成功注入了<script src="恶意地址">,浏览器也会直接拒绝加载。CSP是最后的“安全气囊”,强烈建议所有Web项目默认开启。
第三层是Cookie属性加固。设置HttpOnly后,JavaScript无法读取Cookie值,XSS窃取Cookie这条路就断了。再配合Secure属性要求HTTPS传输,以及SameSite属性限制跨站发送,会话劫持的成本大大提升。
实操提示:我处理过很多被XSS困扰的项目,最常见的修复漏网点不是留言板、不是输入框,而是URL参数直接回显的“搜索无结果”页面。开发者觉得这页没功能就忽略了输出编码,结果整个入口全是洞。
3. CSRF与SSRF:两种方向相反的“伪造”
3.1 CSRF:借你的手干坏事
CSRF(跨站请求伪造)的逻辑是:攻击者构造一个恶意请求,诱导已登录用户点击,浏览器自动携带用户的Cookie发起请求,服务端以为是用户本人的操作,于是照单执行。
最常见的场景是修改密码和转账。你登录了银行系统没有退出,然后打开了攻击者精心构造的页面,页面里悄悄嵌了一个表单:
<form action="https://bank.example.com/transfer" method="POST"> <input type="hidden" name="to" value="attacker"> <input type="hidden" name="amount" value="10000"> </form> <script>document.forms[0].submit();</script>表单自动提交,浏览器带上你的Cookie,后端只验证了会话有效,没有验证请求来源是不是你本人操作,钱就这么转走了。
防御CSRF的标准方案有三个:
- CSRF Token:服务端渲染页面时生成随机token存在Session里,表单提交时必须带上,后端比对不一致就拒绝。攻击者无法预知你的token,自然构造不出有效请求。
- SameSite Cookie属性:设置
SameSite=Lax或Strict,浏览器跨站发送Cookie的行为会被禁止,从根源上堵住“自动携带身份”。 - 请求头校验:校验
Origin或Referer字段,只允许同源请求。但注意Referer有时会被隐私设置剥离,Origin更可靠一些。
3.2 SSRF:让服务器替你去内网“串门”
与CSRF相反,SSRF(服务端请求伪造)是攻击者诱导服务端去访问本不该访问的地址。很多功能都有这种隐患:图片转码、URL预览、Webhook回调、导入远程文件——只要你的服务器会根据用户提供的URL发起请求,就可能存在SSRF。
攻击者把URL填成http://127.0.0.1:3306、http://169.254.169.254/latest/meta-data/(云厂商元数据接口,可能获取到临时密钥),甚至内网其他主机的管理后台,服务端就会代他去访问。我之前测试过一个“网页截图”功能,传一个URL进去,系统渲染截图返回给用户。把URL改成内部运维平台的登录页,截图直接把内网面板内容暴露了出来,这就是典型的SSRF。
防御SSRF的核心是控制请求的边界:
- 解析URL后校验目标IP必须是公网合法地址,禁止内网段、回环地址、链路本地地址、保留段
- 禁止协议白名单之外的类型,比如默认只允许HTTP/HTTPS,防止通过
file://、gopher://读取本地文件 - DNS解析之后再做一次IP校验,防止“DNS重绑定”绕过
- 尽量让请求走专用的代理或跳板机,把SSRF的攻击面隔离出去
4. 文件上传与文件包含:通往服务器权限的后门
4.1 上传一个“图片”怎么就Getshell了
文件上传漏洞是所有Web木马中最常见的入口。场景再熟悉不过:头像上传、附件上传、证件照片上传。攻击者尝试上传一个包含恶意代码的PHP、JSP、ASP文件,如果服务器没有严格校验,文件落到了web目录里,访问它就能直接执行里面的代码——这一步通常叫做“GetShell”,通俗讲就是拿到了服务器的执行权限。
很多系统做得像模像样,前端限制了图片格式,后端也检查了Content-Type,但依然被绕过。攻击者改扩展名(shell.php.jpg)、修改Content-Type为image/png、在图片尾部拼接PHP代码(图片马),再配合服务器解析漏洞或者包含漏洞,照样能执行代码。
防御文件上传,我建议必须同时满足这几点:
- 扩展名白名单:只允许业务必须的类型,比如jpg、png、pdf,黑名单思路不要用
- 内容校验:服务端用图像库重渲染图片,彻底清除其中的脚本代码
- 存储与执行分离:文件存储在web目录外部或独立对象存储,通过程序读取代为直接访问
- 随机文件名:不使用用户提供的原始文件名,避免
shell.php这种名字直接落地 - 限制大小与频率:防止恶意用户刷爆磁盘,也为审计保留日志
4.2 文件包含:参数里的“路径”别乱传
文件包含漏洞常出现在代码设计不良的PHP应用中,核心逻辑是:开发者把文件路径作为参数动态加载,用户控制了路径内容。
本地包含可以读取系统文件,用../../../../etc/passwd这样的路径穿越一张张翻文件;如果能包含日志文件或图片马,就能在页面中解析出PHP代码执行命令。远程包含更直接,加载攻击者服务器上的恶意脚本,一键拿下执行权限。
防御文件包含其实不复杂:永远不要直接拼接用户输入的文件路径。一是对文件名做参数校验,只允许预先定义好的白名单映射关系,比如page=profile对应加载profile.php;二是关闭远程包含开关(PHP的allow_url_include);三是不用动态包含,改写为静态引入。
5. 命令注入与代码执行:一旦存在就是最高权限
5.1 为什么说命令注入是“杀招”
命令注入的本质,是开发者调用了系统命令,但其中拼接了用户输入。最常见的场景是运维工具类系统:Ping检测、域名查询、日志搜索,代码里写system("ping " . $ip);,如果$ip被填成127.0.0.1 && whoami,回车之后服务器多执行一条命令。
这一招的可怕之处在于,命令是以Web服务运行用户的身份执行的,通常是www或nobody。如果Web服务权限配置不当直接跑在root下,攻击者的命令就是root权限执行,整个服务器沦为肉鸡。命令注入之后,攻击者通常会尝试连接外部的方式获取交互式权限,然后做权限维持、横向移动。
防御命令注入,最根本的手段是尽量不调用系统命令。Ping检测用ICMP包构造库实现,域名查询用dns解析库实现,都能在代码层完成。如果非得用系统命令,必须对参数做严格的模式匹配,只允许IP的四个数字段;顺带设置执行超时和命令白名单,确保即使输入异常,也跑不出既定范围。
5.2 代码执行与表达式注入
与命令注入类似,代码注入是用户输入直接进入了eval()、assert()、create_function()这类函数。在模板引擎、动态报表、规则引擎里最容易出现。另一个变体是表达式注入,比如Java的SpEL表达式、Python的eval、Node的vm沙箱逃逸,它们都能从“一条数据”扩展成“一段逻辑”。
这类问题的防御思路和命令注入一致:别让用户触达代码解释器。如果业务必须支持,那就用一个隔离度极高的沙箱,配合资源限制和超时控制。我见过一个报表系统,为了让用户自定义计算公式,直接用了Python的eval,排查时发现可以导入库执行系统调用。后来重构成白名单函数解析器,才彻底解决。
6. 反序列化漏洞与XXE:藏在“数据交换”里的杀手
6.1 反序列化:数据还原竟成了代码执行
序列化是把对象变成字符串便于存储传输,反序列化是把这个字符串还原成对象。问题在于,还原的过程中,许多语言会触发对象里的魔术方法。Java的readObject、PHP的__wakeup、Python的pickle,一旦攻击者精心构造了序列化数据,还原时就会执行预埋的恶意逻辑。
Java生态的反序列化漏洞在近年影响面极广,因为很多框架默认依赖了带风险的组件。攻击者不需要账号,只往接口发一段精心构造的序列化字节流,就可能直接执行命令。这类漏洞经常被评为最高危,因为它可以从前端一路打通到服务器。
防御反序列化的措施,优先级如下:不要反序列化不可信数据;用了就做加签校验(HMAC),确认数据没被篡改;对Java对象流加白名单类过滤(ObjectInputFilter);把可能引入攻击面的组件升级到修复版本。对于RPC、MQ这类内部通信系统,强制启用认证和加密,别把接口裸奔在内网。
6.2 XXE:XML的老家被“外联”了
XXE(XML外部实体注入)在新手圈里知道的人不多,但危害一点不小。罪魁祸首是XML的DOCTYPE声明允许定义外部实体,如果解析器开启外部实体加载,攻击者可以在XML里这样写:
<?xml version="1.0"?> <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> <data>&xxe;</data>解析后,&xxe;会被替换成服务器本地文件内容,通过报错信息或页面渲染泄露出来。如果目标允许访问内网地址,XXE还能变成SSRF,去探测内网服务。
防御XXE简单有效:禁止外部实体加载。在解析XML之前,关闭DTD和外部实体功能。具体实现上,JavaDocumentBuilderFactory设置setFeature禁用,PHP禁用libxml_disable_entity_loader,Python的xml.etree换用defusedxml库。能不用XML就尽量不用XML,JSON、Protobuf这类格式天然没有外部实体的概念。
7. 不安全配置与弱口令:最容易被忽视的“同行人”
7.1 一切默认值都是攻击者的好朋友
还有一类漏洞和代码无关,纯粹是“配置”问题。框架默认后台路径没改、调试模式在生产环境开启、目录列表打开、错误信息直接返回完整堆栈、版本号明文暴露。这些看似无关紧要的细节,全部是攻击者的情报来源。
比如生产环境开启了debug模式,报错页面把数据库连接串、文件绝对路径、依赖版本全部打印出来,等于把地图递到对手手里。目录列表开启,备份文件.sql.bak、源码压缩包直接下载。版本信息暴露,攻击者去漏洞库查对应版本,CVE编号直接照着打。
配置加固的原则是“默认安全”:生产环境关闭一切调试信息;移除默认后台入口,改到独立端口或IP白名单内访问;服务器响应头隐藏具体的Web容器版本;静态目录禁止列目录;备份文件一律不放在web可访问路径下。这些基线配置项目整理成SQL脚本,部署时一键执行,才是团队协作的正确姿势。
7.2 弱口令为什么永远杀不完
很多人觉得弱口令是“低级问题”,但数据泄露事件里,90%的突破口都是弱口令或者同一个密码走天下。后台账号用admin/admin123,数据库用root/root,Redis无认证,SSH开密码登录且口令简单,这些都是真实案例里高频出现的项目。
防御弱口令,技术上能做的事情很多:强制密码复杂度策略、启用多因素认证、限制登录失败次数并加入防暴力破解逻辑、禁止内网服务对外无认证暴露、定期做口令熵值评估。但比技术更重要的是一套规矩:新员工入职默认开通MFA,服务器登录统一走密钥认证,数据库连接串从配置中心下发而不是硬编码在代码里。
8. 常见问题与排查技巧实录
8.1 明明用了参数化查询,为什么还是被注入进了报表老代码
有一次我给一个统计报表系统做审计,发现一个数据导出功能存在注入。开发者很委屈地说“我全部用了预编译啊”。后来排查发现,报表排序功能需要根据前端传的排序字段动态拼接ORDER BY,开发者为了省事在这里用了字符串拼接。这就是典型的“只注意到查询条件、忘了排序字段也算动态内容”。排序字段必须走白名单映射,前端传score就映射到固定的列名,绝不允许直接进SQL语句。
这类问题的排查思路是:审计代码时不要只看有没有用预编译,要看有没有任何字符串拼进SQL。全局搜+、concat、String.format,一个个确认变量来源。在开发阶段还应该加SQL日志输出到独立文件,上线前人工检查一遍所有SQL日志中的动态部分。
8.2 加了WAF依然被绕过,问题出在哪
有同行问过我:系统前面挂了WAF,为什么模拟攻击还是能打进测试环境?答案通常有两个。一是WAF只对部分URL做了防护,接口路径改个大小写、加个路径穿越.jsp/../api、或者换成带Content-Type为JSON的POST体,规则就匹配不上了。二是WAF拦下了HTTP层的攻击,但应用本身业务逻辑存在漏洞,比如我前面讲的低权限用户遍历ID查看他人订单,这属于越权类问题,WAF根本无从感知。
所以我的经验是:WAF是“延迟满足”的补充项,不是“一劳永逸”的防线。真正的重心永远在代码层修复和架构层隔离。不要因为挂了WAF就降低对开发的约束。
8.3 发现漏洞后修复顺序怎么排
实际项目里漏洞经常一抓一大把,但资源有限,怎么排优先级?我的做法是四步评估规则:该漏洞是否暴露在公网、是否涉及核心资产(数据库、账号体系、资金交易)、利用难度高不高、自带危害大不大。四个条件同时命中,立即停工修复;只命中部分,定级为中期计划;只是理论风险且攻击面受限,放入日常迭代。
8.4 学习路线与自查工具推荐
零基础学习漏洞防御,不需要一上来就啃几百页的协议书。我建议的路线是:先把十大漏洞的“原理”看懂,再去本地搭一个存在漏洞的靶场做代码审计练手,注意不要针对未经授权的真实目标做测试。等能轻松找到靶场漏洞并给出修复建议后,再系统学习HTTP、TCP这类基础协议,最后研究代码审计方法论。
在自查工具方面,开源社区的扫描器可以作为辅助,例如OWASP ZAP适合Web应用完整扫描,针对依赖组件可以用开源依赖检查工具看看自己引的第三方包有没有公开漏洞记录。这里特别提醒一句:扫描器报的风险等级参考价值有限,误报和漏报都很常见,所有告警必须手工验证。
9. 结束前,再分享一点我的实际体会
这几年来我处理过不少“惨烈”的安全事故,回头复盘,每一桩几乎都能对应到上面的某类漏洞。SQL注入、XSS、弱口令这三样占了大头;反序列化和XXE这类偏门漏洞一次没堵住,造成的损失往往最重。安全工作没有一劳永逸,新功能上线,新漏洞面就跟着出现。我自己的习惯是把安全自查做成发布流水线里的一道关卡,每次上线前自动跑一遍扫描和依赖检查,“人人皆知安全”不如“流程里全是检查点”。
这篇十大漏洞的总结写出来,希望对你有一份真正的参考价值。如果你照着检查自己的项目,发现一处问题、修掉一个隐患,那这篇文章的目的就达到了。遇到具体漏洞卡住排查的时候,再回来翻一翻对应章节,按着防御清单一项项核对,多半能找到突破口。