三年前我刚入行的时候,觉得安全是一件很遥远的事情。那时候写代码只关心功能能不能跑起来,至于安不安全根本不在考虑范围内。直到公司的网站被黑了一次,我才真正意识到安全的重要性。

那次被黑的经历现在想起来还心有余悸。一个周末的晚上,运维突然打电话说网站打不开了,数据库里的数据全被删了。我赶到公司一看,整个人都懵了。后来查日志才发现,是通过一个登录接口的SQL注入进来的。就因为我写查询的时候直接把用户输入拼到了SQL语句里,没有做任何过滤。

从那以后我开始认真学习安全编程。这三年下来踩了不少坑,也积累了一些经验。今天想把这些经历写出来,希望能给刚入行的朋友提个醒。

记得最清楚的是SQL注入,这是最常见也是最危险的漏洞。我刚工作的时候写查询都是直接把用户输入拼到SQL语句里。现在想想真是后怕,只要用户名输入一个单引号就能把整个SQL语句搞乱。后来学会了用PDO的预处理语句,把用户输入和SQL语句分开,这个问题就彻底解决了。其实只要养成用预处理的习惯,SQL注入完全可以避免。

XSS跨站脚本攻击这个坑我也踩过。有一个用户评论功能,我直接把用户输入的内容输出到了页面上。结果有人在评论里写了一段JavaScript代码,所有看到这条评论的用户都会被弹框。更严重的是,如果这段代码是窃取Cookie的,那用户的账号就被盗了。后来我学会了用htmlspecialchars函数对输出进行转义,把特殊字符转换成HTML实体,这样浏览器就不会把它当成代码执行了。

CSRF跨站请求伪造是我工作到第二年踩的坑。当时做了一个修改密码的功能,没有验证请求的来源。结果攻击者可以构造一个页面,诱导已登录的用户访问,用户的密码就被改掉了。解决办法是在表单里加一个token,每次请求验证这个token是否正确。这样第三方网站就无法伪造请求了。

文件上传也是一个重灾区。我之前做过一个头像上传功能,只在前端用JavaScript判断了文件后缀名,觉得这样就安全了。结果有人把一个PHP文件改名为.jpg上传,然后通过访问这个文件的路径直接执行了PHP代码。后来我改成了在后端验证文件的真实类型,用getimagesize函数判断是不是真的图片,而且把上传目录放在了Web根目录外面,这样即使上传了PHP文件也无法通过URL访问。

还有一个容易被忽视的问题是错误信息泄露。开发环境下我习惯把displayerrors打开,这样出错了能直接看到错误信息。但上线的时候忘了关,结果访问一个不存在的页面时,PHP把数据库的账号密码都暴露在了错误信息里。这是非常危险的。上线前一定要把displayerrors关掉,把错误记录到日志文件里而不是显示给用户看。

密码存储也是一个大坑。我最早的时候是直接用MD5存密码的,后来知道MD5已经不安全了,就改成了MD5加盐。再后来了解到PHP 5.5提供了passwordhash函数,用的是bcrypt算法,自带盐值,就换成了这个。现在如果让我推荐,一定是用passwordhash和password_verify这对函数,不要自己造轮子。

还有一些细节比如Session固定攻击、URL跳转漏洞、逻辑漏洞等等,这些都是在实际项目中一点点学到的。安全这东西真的是学无止境,你知道的越多,就越觉得自己知道的太少。

这三年下来我最大的体会是,安全不是一个功能,而是一种习惯。写代码的时候多问自己一句,如果用户故意输入恶意内容会怎么样。不要相信任何来自客户端的数据,所有的输入都要验证和过滤。输出的时候要转义,数据库操作要用预处理,文件上传要严格检查。这些事情做起来不难,但能挡住绝大多数的攻击。

当然也不能因为怕出安全问题就不敢写代码了。安全是在实践中慢慢积累的,谁都是从踩坑走过来的。重要的是踩过坑之后要记住,不要再踩第二次。

我现在每次写完代码都会自己做一遍安全检查,看看有没有明显的漏洞。团队里也会互相做代码审查,一个人容易忽略的问题另一个人可能一眼就看出来了。安全这件事,多一个人检查就多一份保障。

做技术这行,学不完的东西,踩不完的坑。但每踩一个坑,你就比以前更强了一点。与各位PHP开发者共勉。