1.1.1重放漏洞
重放漏洞是逻辑漏洞中常见的漏洞之一,攻击者通过嗅探受害者的数据包,将此数据包对服务器恶意重放从而造成危害,如截取登录时的数据包重放,就可直接登录系统;截取成功购买物品时的 数据包重放,就有可能实现付1买10的操作。
区块链中也曾爆出重放漏洞。以太坊硬分叉后分为了两种代币:ETH和ETC,由于两条链的数据结构是相同的,所有在ETH上有效的交易都可以被重放到ETC。因此恶意攻击者不断地充币、提币,从而获取额外的ETC,造成了巨大的损失。
对于重放漏洞的防御,我们一般采取数字签名的方法:
数字签名 Sign = sign_function(token, timestrap, params[])
其中timestrap为时间戳,params为需要签名的数据集合,token从后台获取。后台接收到签名后,首先确认时间戳在有效的时间范围内,接着计算签名,由于攻击者无法直接获取token,因此无法伪造有效时间戳的签名,从而导致重放失败。
当然,还有例外的情况:由于网络延迟的缘故,时间戳的有效范围不会太小。若合法客户端主动使用脚本抓包,并进行快速重放,由于时间戳有效,是可以造成重放攻击的。在这种情形下,我们便不得不说起由其导致的竞态条件漏洞。
1.1.2竞态条件兑换商品的原理
有一个这样的场景:用户账户内有700积分,想要兑换价值500积分的商品,那么编写逻辑可能为:读取剩余积分 → 判定此剩余积分是否大于所兑换商品的积分 → (商品检测、登记入库等操作) → 计算剩余积分 → 写入剩余积分。
假设这个场景存在竞态条件漏洞,下图解释了漏洞的成因。
如图,在并发请求1写入剩余积分且商品增加的逻辑发生前,并发请求2进行积分读取操作,此时数据库内的数据尚未改变,因此读取到的数据仍为700积分,从而实现兑换超过积分所允许兑换的数量的商品。
1.1.3竞态条件漏洞演示
为了更直观的感受漏洞,本次使用PHP实现了简单的兑换逻辑。
下面为购买商品的逻辑。
<?php
header("Content-Type: text/html;charset=utf-8");
$conn = mysql_connect("localhost", "root", "root");
mysql_select_db("fnshow", $conn);
# 读取积分
$res = mysql_query("SELECT score FROM user WHERE id=1");
$row = mysql_fetch_array($res);
$score = $row["score"];
# 判断积分
if($score < 500) {
die("<script>alert('积分不足!');javascript:history.go(-1)</script>");
}
# 一些其他的耗时操作,暂用sleep代替
sleep(1);
# 修改商品质量和积分状态
mysql_query("UPDATE user SET goods = goods+1 WHERE id=1");
mysql_query("UPDATE user SET score = ".$score."-500 WHERE id=1");
mysql_close($conn);
exit("<script>alert('购买成功!');javascript:history.go(-1);</script>");
?>
先来看正常的流程。打开商品兑换页面。
点击花费500积分,购买商品。
可以看到购买成功的提示,返回到原界面,数据正常。
再次购买,显然因为积分不足,购买失败。
将状态重置回商品数量0,积分700,开始重放攻击。
首先,点击花费500积分购买商品。
使用burpsuite抓包。
发送到Intruder模块进行重放攻击,将payload type修改为Null payloads,数量修改为15,注意还需要调整并发线程。
开始重放。
可以看到共有5次兑换成功。值得一提的是,兑换成功的数量与线程的并发是成正比的,但并非所有的并发都会生效。
1.1.4结语
由于通过时间戳签名验证数据包有效性的方式过于局限,因此现在一般加上Nonce(一次性随机值)。每次请求都需要生成新的不同的Nonce值,对于相同Nonce值的数据包,服务器会直接过滤。这样时间戳+Nonce的组合可以有效防止重放攻击。
至于竞态条件的漏洞,上述的情景其实只是最常见的竞态条件漏洞之一,竞态条件漏洞并不仅仅依靠重放来攻击,不同功能间使用相同数据源时也可能导致竞态条件漏洞。The DAO事件便是利用了以太坊的竞态条件漏洞进行攻击的,此次事件直接导致了以太坊的硬分叉。对于此类漏洞,究其根本是因为整个操作并非一个原子操作,导致整个操作间的步骤可以交叉进行。针对这种情况,只需要添加互斥锁,在一次操作完成前禁止互斥的操作即可。