前阵子朋友的公司被黑客攻击了,用户数据泄露,上了新闻。他跟我说代码Review过很多遍,以为没什么问题。结果安全公司一查,是个很低级的问题 —— 用户输入的内容直接显示在页面上,没做任何过滤。

这种事情在行业内太常见了。很多团队重功能轻安全,上线前没有做过安全测试,等出事了才后悔莫及。安全这件事,必须在开发过程中就考虑进去,等上线再补救就晚了。

这篇文章聊聊常见的Web安全漏洞和防范方法。不讲太深奥的原理,重点是告诉大家有哪些问题,以及怎么避免。

XSS跨站脚本攻击最常见

XSS就是攻击者在网页上注入恶意脚本,用户访问页面时脚本就会执行。轻则弹个广告,重则窃取cookie、劫持账号。

防范XSS的核心是对用户输入进行转义。HTML里的特殊字符像<、>、&、"、'都要转成HTML实体。不同场景转义规则不一样,不要以为用一个函数就能解决所有问题。

内容安全策略(CSP)是个有力的补充手段。通过设置HTTP响应头,告诉浏览器哪些脚本可以执行,哪些不能。即使有漏洞,恶意脚本也执行不了。不过CSP配置比较复杂,用不好会影响功能。

还有个容易忽略的点:富文本编辑器提交的内容。这类内容本身就允许HTML标签,不能简单转义。解决办法是用白名单,只允许安全的标签和属性,或者用专门的HTML净化库来处理。

CSRF-token必须加

CSRF攻击是借用户的身份发起请求。比如用户登录了银行网站,攻击者诱导用户访问一个页面,那个页面的JS自动向银行发起转账请求,浏览器会带上用户的cookie,银行以为是用户本人操作的。

防范CSRF主要靠token。每个表单或者AJAX请求都带一个服务端生成的随机token,服务端验证token有效性再处理请求。攻击者的页面没有这个token,请求就会被拒绝。

现在的框架基本都有CSRF token功能,但有些团队写API的时候把这个机制去掉了,觉得麻烦。千万不能省这一步。另外,敏感操作最好再验证一下密码或者手机验证码,多重验证更安全。

同源策略能挡住大部分CSRF攻击,但不是绝对的。某些特殊情况下的GET请求、设计有缺陷的JSONP接口,都可能绕过同源策略。防御不能只靠同源策略,token必须加。

SQL注入现在少多了但还有

SQL注入是通过拼接字符串构造恶意SQL语句。比如查询用户ID用request("id")直接拼进SQL,攻击者输入"1 or 1=1"就能查出所有用户。

防范SQL注入用参数化查询,不要拼接字符串。现在主流的ORM和数据库扩展都支持参数化查询,性能也没问题。如果用原生SQL,PDO的prepareStatement是最好的选择。

还有个建议是最小权限原则。数据库账号不要用root或者admin权限,给每个应用分配只有必要权限的账号。即使被注入,损失也有限。

现在这种漏洞新项目已经很少见了,但维护老项目的时候还是要检查一下。有些祖传代码可能是十年前写的,当时没有参数化查询的条件。

密码存储不能含糊

密码存储是个老生常谈的问题,但每年还是有公司因为密码存储不当被骂。最基本的要求是:绝对不能存明文密码,绝对不能使用MD5这种不安全的摘要算法。

正确的做法是用bcrypt、argon2或者scrypt这些专门为密码设计的哈希算法。它们的特点是计算慢、资源消耗大,暴力破解的代价很高。盐值不用单独存,这些算法会自动生成随机盐。

密码重置链接也是个容易出问题的地方。链接里的token要足够随机、要有过期时间、要用一次就作废。最好也检查一下是否是同一个浏览器生成的,防止token被盗用。

多因素认证(MFA)越来越普及了。密码加短信验证码、密码加邮件确认、或者用Authenticator App。重要账号最好都开启,能大幅提升安全性。

HTTPS必须用

HTTP传输的数据是明文的,被拦截了啥都能看到。登录信息、银行卡号、个人隐私,传输过程中一览无余。公共场所的WiFi尤其危险,内网里的攻击者能轻易截获所有HTTP流量。

HTTPS对数据加密传输,攻击者拿到的是密文,看不出内容。虽然HTTPS有性能开销,但现在的浏览器和服务器都做了优化,这点开销基本可以忽略。

证书选择正规CA签发的,不要用自签名证书。Let's Encrypt提供了免费的证书,个人项目和小型站点够用了。证书有效期现在越来越短,自动化续期是必须的。

HTTPS还要注意配置好TLS版本和加密套件,老的协议版本和加密算法有已知漏洞。Mozilla的SSL Configuration Generator能帮你生成安全的配置。

敏感数据要保护好

线上日志里不要记录密码、token、身份证号、手机号这些敏感信息。日志框架要有脱敏功能,或者在记录前手动处理。

接口返回的数据也要检查,不要多返回字段。前端可能只用了两个字段,但后端返回了一堆,用户信息全暴露了。按需返回,最小暴露原则。

敏感配置像数据库密码、API密钥不要写在代码里,用环境变量或者配置中心管理。代码仓库是多人能访问的,万一泄露出去就麻烦了。

敏感数据在客户端本地存储也要加密。浏览器的localStorage、sessionStorage不是安全的地方,恶意JS脚本能读到。必须存储的话用加密后的数据。

安全更新要及时

框架和依赖库的漏洞会被不定期披露。这些漏洞往往有公开的POC(概念验证代码),意味着攻击门槛很低。收到安全更新公告要第一时间评估和修复,不要拖。

依赖管理工具像npm、composer、pip都有审计功能,能检测依赖的已知漏洞。建议集成到CI流程里,每次构建都检查,发现问题及时处理。

服务器的操作系统、中间件、数据库也要定期更新。配置好自动安全更新,或者用容器镜像,镜像里的基础系统会同步更新。

写在最后

安全不是 отдельная тема,而应该融入到开发的每个环节。设计阶段就要考虑威胁模型,开发阶段要遵循安全编码规范,上线前要做安全测试,运营中要监控和响应安全事件。

不是每个人都能成为安全专家,但每个开发者都应该具备基本的安全意识。了解常见的漏洞类型和防范方法,才能在写代码的时候主动规避风险。希望这篇文章对大家有帮助。