前言
之前linux内核竞争条件漏洞"Dirty COW",直接导致了普通用户提权到root权限。这是系统级别的安全,然而web安全同样存在竞争条件漏洞场景
本文就谈谈web竞争条件漏洞的场景及修复
1、先来看看线程安全,下段代码用两个线程操作一个公共变量infos
import threading
def thread1(info):
for i in range(0,5000):
info.append('1');
def thread2(info):
info.append('a');
def multiprocess():
infos = []
t = threading.Thread(target=thread1, args=(infos,))
t.start()
t1 = threading.Thread(target=thread2, args=(infos,))
t1.start()
print(infos)
multiprocess()
因为list在python中是线程安全的,所以最终结果是a在列表最后。若线程不安全,线程1操作infos的过程中,可能会被中断,导致字符a被追加到infos的中间位置。
2、再来看看竞争条件
比如付费抽奖功能demo
def thread1_getyue(uid, has_money):
has_money = getmoney(uid)#返回True/False
def thread2_choujiang(uid, has_money):
if has_money:
choujiang(uid)
realpay(uid)#扣款
def pay_and_luckdraw(uid):
has_money = False
thread1_getyue(uid, has_money)#查是否有余额,会改变has_money的值
thread2_choujiang(uid, has_money)#先判断has_money,然后开始抽奖,跨系统处理,需要很长时间
(仅为展示竞争条件写的demo代码,实际过程中扣款失败可以撤销抽奖结果)
整个过程:判断余额 》抽奖流程 》扣款
如果攻击者的余额只够一次抽奖,若他高并发请求抽奖功能,试想下,第一请求正在处理抽奖流程时第二个请求也来了,这时还没扣款,has_money>0,所以第二请求也能进入抽奖流程。has_money成了竞争条件,余额不足也可以进行抽奖
3、其实,竞争条件问题不一定与线程安全相关,如下段顺序执行的代码
def pay_and_luckdraw(uid):
has_money = getyue(uid)#查是否有余额
if has_money>0:
choujiang(uid)#开始抽奖,跨系统处理,需要很长时间,导致不能及时更改getyue返回状态
realpay(uid)#扣款,会改变getyue返回结果
只要流程中有关键的三步:判断状态》进行操作》改变状态,就可能有竞争条件的问题
如果改成判断状态》改变状态》进行操作,就相当稳妥些。
比如付费抽奖这个功能,可以判断有余额后(状态),先扣费(改变状态),后抽奖(操作)。当然流程上可以增加更多异常判断
二、漏洞场景&修复
仔细分析下来,发现竞争条件问题关键点是多步操作中未考虑高并发的情况。
所以很多有 判断状态》进行操作》改变状态 过程的功能都有可能存在问题,比如按天签到发奖励,付费抽奖等等
修复思路就是 “判断状态》改变状态》进行操作”,先改变状态后进行费时操作,充分考虑流程中的各种异常情况