漏洞描述
攻击点比较多。这里以activate.php文件的 token参数为例。
首先以任意邮件地址注册freeshell,然后开始猜验证地址的token:
循环第一位:
- 第01次尝试:a0000000000000000000000000
- …
- 第26次尝试:z0000000000000000000000000
- 第27次尝试:A0000000000000000000000000
- …
- 第52次尝试:Z0000000000000000000000000
- 第53次尝试:00000000000000000000000000
- …
- 第62次尝试:90000000000000000000000000
- 第63次尝试:a0000000000000000000000000
- …
如此循环,记下每次尝试的时间。会发现某个字符的时间比较长,固定这一位,继续猜第二位,直到猜完30位。猜完freeshell也就注册好了。
漏洞原理
请看比较代码
if ($info['token'] !== $_GET['token'])
我没见过底层是怎么实现的,但可以想象,如果第一位正确,就会比较第二位;如果第一位不正确,那么就直接判断为不相等。
虽然这之间的时间差比较小,但统计学告诉我们,所有微小的差距,在大数据量统计中都会被放大。
测试
我获得的地址是:https://freeshell.ustc.edu.cn/activate.php?appid=1450&token=COf4aFFJsEO73f8P1bjkW7zmFbl7vthX5mS5SlE8
第一位是“C”,测试结果见图(矢量图请见 http://z.gaoyifan.com/side-channel-freeshell/)
可以从图中明显看出,编号28的那位(也就是“C”),花费时间比较久。(注:数据已经经过一次清洗,去除了一些噪点)
在服务器负载比较小的时候,猜测一位大约需要3~5分钟,这也就意味着30位的token一个半小时就破解了
修复
修改为:
if(hash_hmac("sha256",$info['token'],$appid) !== hash_hmac("sha256",$_GET['token'],$appid))
这样经过一次hash后,比较的串变成随机的,时间差就被随机化了。
感谢boj深夜修复bug:https://gitlab.lug.ustc.edu.cn/boj/freeshell/commit/74ea87e829ffaacc65b5f4fb1099fc2146cbef98
这样做比较的效率会大大降低吧?不如自己写一个固定运行时间的比较函数?
这么做的确会降低效率,但Hash速度是很快的,效率在可接受的范围内。
当然,写一个恒定时间比较函数是一个很好地做法。