Web安全
老生常谈的几大经典安全问题
1. SQL注入
这一点可能都被说烂了,只要是动态网站,可以说基本上第一个必须考虑的点就是SQL注入。
那什么是SQL注入呢,先举一个例子吧:
这是一个典型的登录场景:
-------------------------------- | Username: | -------------------------------- | Password: | --------------------------------
网站要求用户输入用户名和密码以登录,这时候通常后端的SQL语句是:
select * from user_t where user_t.username = '...' and user_t.password = '...';
看起来好像没有什么问题,当获取到匹配用户名和密码的表项时,判断结果集的数量是否为1,再进行一些其他的逻辑判断,即可判定用户成功登陆,但是吧,这样会有一个问题,如果后端代码采用了字符串拼接,这里将出现一个致命的问题:
比如我按照如下方式输入:
-------------------------------- | Username: John Kindem'; -- | -------------------------------- | Password: mypassword | --------------------------------
此时SQL语句就会变成:
select * from user_t where user_t.username = 'John Kindem'; --' and user_t.password = '...';
整理一下,就会变成这样:
select * from user_t where user_t.username = 'John Kindem'; --' and user_t.password = '...';
可见原本密码这一条件,直接被注释掉了,而原来的SQL语句被提前结束了,这样你随意使用一个密码就能登录为站点任意一个用户,是不是很恐怖,设想一下,假如这个框中包含有管理员用户的登录功能,这一条语句将毁掉多少数据......
所以这就是SQL注入,这一点往往是Web开发中最危险的一个点,由于其往往与权限有关,一旦被攻破就会导致数据库遭到破坏或者是站点控制权的丢失。
SQL注入往往通过构建特殊的输入来进行,这些特殊的输入往往有以下的想法:
- 提前结束SQL语句
- 注释掉SQL语句的一部分
- 使用布尔表达式构造恒等式
- 在原有SQL语句中加入自己的插入、删除、修改等SQL语句
SQL的防范其实也很好做,在现代的Web开发中,往往遵循以下几条规则开发就能很好地避免被SQL注入:
- 不要随意对SQL语句使用字符串拼接
- 使用预编译的方法来使用SQL语句
对于后者,基本上所有的语言都支持这一点,拿JavaScript来说:
// Nodejs 连接 MySQL 进行查询 connection.query('select * from student where id = ?', 5, () => { ... });
这样可以查询到 student 表中 id = 5 的表项的所有信息,但同时能够防范SQL注入,因为这样的语句在使用之前就会被预编译,你无法再随意改变SQL语句的构成。
2. XSS - 跨站脚本攻击
XSS的全称是 Cross Site Scripting,意为跨站脚本攻击,为了区别于 CSS (Cascading Style Sheets) 而特意写成 XSS。这一攻击方法也是很常见的Web攻击之一,而且由于需要在写 JavaScript 的时候特别注意,这一攻击往往容易被忽略。
当然随着前端的发展,现在这一攻击的防范方法往往都会被集成在框架中,比如 React、Vue 中,很多地方就集成了 XSS 防范,你在使用框架的时候,很多地方往往都不需要注意这一点,框架往往帮你做了防护。
这里简单介绍一下 XSS,先举一个例子:
现在有这样一个博客网站:
- 写博客页面提供一个富文本编辑器,用于给用户写博客
- 看博客页面获取用户写的博客,并且将富文本编辑器产生的html文本显示在页面上
那么,假如用户在富文本编辑器上写:
hello world <script>alert(0);</script>
富文本编辑器的原理往往是将用户输入的内容转义成带有格式的html文本并且输出,很有可能它的输出结果是这样的:
<div class="post-editor"> hello world <script>alert(0);</script> </div>
想想看,如果这样的一段文本,如果原封不动地被显示在看博客的页面,会导致什么?
很显然,其中的 script 标签中的内容,将会直接被当成脚本执行,想想这会有很危险,有心的攻击者可以利用这一漏洞,随心所欲地写自己的攻击脚本,比如获取用户的 cookie、进行恶意请求、监听用户输入等......
这就是 XSS,在平日写 JavaScript 的过程中很容易就会产生这一的漏洞,XSS 的生效往往有两个条件:
- 构建恶意输入,使得输入中包含恶意的 JavaScript、html 代码
- 页面原有的代码会将这些输入不经处理地直接当成页面、原有代码的一部分
最简单的例子,就是网站直接将用户的输入作为输出显示在页面上,再或者,站点中有一些 eval() 之类的函数,能够直接执行用户输入......
当然 XSS 的防范也不是不无方法可循,最重要的一点是:
- 永远不要信任用户的输入,如果需要将用户的输入用在页面或者代码中,一定要转义
转义这一点,讲的是针对那些能够影响到原有代码的特殊符号,比如 <、> 等,这里给出一些常用的转义规则:
--------------------- | < | < | --------------------- | > | > | --------------------- | ' | & | --------------------- | " | " | ---------------------
进行完转义之后,这些字符会被当成普通显示的字符,而不是具有特殊意义的字符
3. CSRF - 跨站请求伪造
CSRF 全称为 Cross Site Request Forgery,即跨域请求伪造。这一点攻击往往容易被人一楼,在一些老一些的网站,基本上都没有考虑到这一点,就连百度、亚马逊这一些大型网站,在 CSRF 被人大规模利用进行攻击之前甚至都没有做这一方面的防范。隐蔽性高是这一攻击最大的特点。
这一攻击主要利用的是网站对用户身份的绝对信任,CSRF 有一些难以理解,这里用一个例子简单地说清楚:
举一个例子,现在这里有一个网购网站,一般来说,往往当用户登录之后,验证了身份后,站点在一次会话结束前,都是信任用户的,也就是说,站点相信用户是本人,但是吧,现在有这样一次操作:
- 用户先登录了网购网站
- 用户没有关闭网购网站的标签页,打开了另外一个网站
- 那一个网站是一个恶意网站,登录那个网站的一刻,它向网购网站发送了一个购买物品请求
现在想一想,会出现什么问题?由于用户没有关闭网购网站的标签页,上一次会话并没有结束,恶意网站发送的请求的 cookie 中将会带有与上一次会话相同的 sessionId,也就是说在网购网站的那一端将会认为这一次请求任然是用户的请求,所以会欣然接受。
是不是很可怕?你明明没有做任何操作,你的一切却已经属于了别人。很多时候,被 CSRF 攻击之后,用户往往都不知道发生了什么,自己的钱包就空了。CSRF 最可怕的地方就在于这里,它的攻击目标往往不是站点,而是站点的用户,再者,它的隐蔽性也让人忌惮。
CSRF 的防范,也是有规律可循的,最重要的一点是,站点永远不要信任任何一个用户,需要对用户进行时刻验证,一般来说现在都是这样操作的:
- 在每一次请求中,都带着双方按照一定约定产生的一个随机 Token,这一个 Token 可以通过公钥加密或者其他的方法产生,Token 验证通过了才说明是用户本人
一些危害相对较小的攻击
1. 静态资源枚举
在平时我们的开发中,往往静态资源的命名都是有规律的,比如吧,一些同一页面引用的js脚本,会被我们如此命名:
- index-script-1.js
- index-script-2.js
- index-script-3.js
- ...
同样的,图片、CSS 文件都会像这样命名,有心人就可以写一个脚本,遍历整个站点目录,获取一些文件。当然,这些静态文件给他也无妨,毕竟你摁 F12 也是一样的效果......
但是设想一下,假如站点文件服务器上有一些特殊的文件,比如哪一个傻傻的开发者使用 bak 格式备份了某一个后端文件,但是忘了删除然后一直存在了服务器上,比如:
- index-backend.php.bak
- index-login-backend.php.bak
这些文件要是给弄到了,相当于网站的后端逻辑直接暴露在了用户面前,有心人就可以通过这些文件来分析获取该站点的一些漏洞。
总的来说危害还是不大,但是平日里一定要注意:
- 不要把任何后端有关的文件仍在服务器上
- 文件使用 UUID 或者其他方法随机命名使之没有规律,加大枚举难度
2. 跨域问题和访问控制
跨域问题,自如其名,就是在站点域外获取站点的服务,这样的危害其实不是很大,因为站点一般都有访问控制,每一个身份都有自己能做的事情和不能做的事情,而且一般来说,浏览器和框架对这一攻击都有着严格的防范,基本上来说,非本域下的请求基本上不可能成功。
但是吧这里还是提一句吧,还是以一个例子来说明:
- 假如我得知了一个站点的注册用户的请求 url 和其参数列表,并且我发现它没有设定访问控制和非本域请求禁止
- 我直接直接写一个脚本,按照它的格式疯狂发送 http 请求,或者说操控一大批傀儡机,不断注册,影响站点的正常工作
虽然一般是不太可能成功的......但是我们还是假设站点真的傻到这种地步
跨域问题一般通过使用 http response header 中的 Access-Control-Allow-Origin 来防范,这一字段可以声明该 response 允许的域,比如:
Access-Control-Allow-Origin: www.kindemh.cn
那么非本域的所有请求,就算你请求了,你也无法获取到 response
这里值得一提的是,在现代开发中,我们往往会使用 Ajax 技术来发送异步请求,但是实际上,如果你使用 Ajax 技术,十有八九都会因为跨域限制被拦截,这时候你就需要使用上述字段来允许你自己的服务。所以说这一点其实本来没多大危害,要说真正的危害,还是对你的开发效率会造成一定的影响。