目录

django下防御race-condition漏洞竞争型漏洞

django下防御race condition漏洞(竞争型漏洞)


竞争型漏洞

概念

竞争型漏洞,也称为竞态条件漏洞(Race Condition Vulnerability),是由于程序在处理共享资源时,未能正确同步多个并发操作,导致执行结果依赖于这些操作的时序,从而可能被攻击者利用的安全漏洞。

假设有一个银行应用,处理转账的时候,如果两个转账操作同时进行,而余额检查没有正确同步,可能导致余额错误地被处理。例如,用户A有100元,同时发起两笔转账,各转出100元,如果系统没有正确锁定资源,可能两笔转账都通过,导致用户A的余额变成负数,这就是竞争条件导致的漏洞。

常见类型及示例

  1. TOCTOU(Time-of-Check to Time-of-Use)

    • 原理 :程序在检查资源状态后、使用资源前,攻击者篡改资源。
    • 示例 :检查文件权限后,攻击者替换为恶意文件,导致高权限执行。
    • 场景 :文件系统操作、权限验证。
  2. 资源争用

    • 原理 :并发操作修改同一资源(如余额、库存)时未同步。
    • 示例 :电商系统中,两用户同时购买最后一件商品,导致超卖。
    • 场景 :金融交易、库存管理。
  3. 异步操作漏洞

    • 原理 :回调或事件处理顺序不当引发状态混乱。
    • 示例 :JavaScript中多个异步回调修改同一变量,导致数据错误。
    • 场景 :前端应用、分布式任务处理。

下面介绍一个关于竞争性的漏洞

环境搭建

可以在资源中下载文件解压

将文件上传到linux下,我们修改文件按中的错误,

将.env.defaul修改为.env

修改文件中的DEBUG=True

然后进行如下操作

pip3 install -r requirements.txt 

python3 manage.py migrate

python3 manage.py collectstatic

python3 manage.py createsuperuser

python3 manage.py runserver  0.0.0.0:8080

完成之后可以访问网站

漏洞复现

ucenter/1/

无锁无事务时的竞争攻击

class WithdrawView1(BaseWithdrawView):
    success_url = reverse_lazy('ucenter:withdraw1')

    def form_valid(self, form):
        amount = form.cleaned_data['amount']
        self.request.user.money -= amount
        self.request.user.save()
        models.WithdrawLog.objects.create(user=self.request.user, amount=amount)

        return redirect(self.get_success_url())

https://i-blog.csdnimg.cn/direct/f23af5c56d674effaed6253bbe9f67b4.png

利用bp抓包,当出现两次以上的扣款成功,就说明漏洞利用成功

竞争成功

https://i-blog.csdnimg.cn/direct/aa0b626697604b98b199f6b78d4d72ea.png

查看日志

https://i-blog.csdnimg.cn/direct/0ce56355343d4f2d9e6ce40d19e55c57.png

ucenter/2/

无锁有事务时的竞争攻击

class WithdrawView2(BaseWithdrawView):
    success_url = reverse_lazy('ucenter:withdraw2')

    @transaction.atomic
    def form_valid(self, form):
        amount = form.cleaned_data['amount']
        self.request.user.money -= amount
        self.request.user.save()
        models.WithdrawLog.objects.create(user=self.request.user, amount=amount)

        return redirect(self.get_success_url())

继续竞争

https://i-blog.csdnimg.cn/direct/abe2d07aa106485d8dd397a35568a0c3.png 查看日志

https://i-blog.csdnimg.cn/direct/5c2eead1977a411883c0fe9970456d2b.png

竞争成功。

ucenter/3/

悲观锁加事务防御Race Condition

class WithdrawView3(BaseWithdrawView):
    success_url = reverse_lazy('ucenter:withdraw3')

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs['user'] = self.user
        return kwargs

    @transaction.atomic
    def dispatch(self, request, *args, **kwargs):
        self.user = get_object_or_404(models.User.objects.select_for_update().all(), pk=self.request.user.pk)
        return super().dispatch(request, *args, **kwargs)

    def form_valid(self, form):
        amount = form.cleaned_data['amount']
        self.user.money -= amount
        self.user.save()
        models.WithdrawLog.objects.create(user=self.user, amount=amount)

        return redirect(self.get_success_url())

https://i-blog.csdnimg.cn/direct/795259911a654b128b252ecddbbadbff.png 发现只有一次提款成功

ucenter/4/

乐观锁加事务防御Race Condition

class WithdrawView4(BaseWithdrawView):
    success_url = reverse_lazy('ucenter:withdraw4')

    @transaction.atomic
    def form_valid(self, form):
        amount = form.cleaned_data['amount']
        rows = models.User.objects.filter(pk=self.request.user, money__gte=amount).update(money=F('money')-amount)
        if rows > 0:
            models.WithdrawLog.objects.create(user=self.request.user, amount=amount)

        return redirect(self.get_success_url())

https://i-blog.csdnimg.cn/direct/e73b13fe10884df3a12ba77c5a586dd7.png

发现也只有一次成功

总结

悲观锁

概念

  • 悲观锁假设并发冲突一定会发生,因此在访问数据时直接加锁,确保其他事务无法修改数据,直到当前事务完成。
  • 悲观锁通常通过数据库的锁机制实现,如 SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODE

特点

  • 优点 :保证数据的一致性,适合写操作多的场景。
  • 缺点 :加锁会降低并发性能,可能导致死锁。

使用场景

  • 高并发写操作。
  • 数据一致性要求高的场景。

实例

该例子需要实现一个转账操作,确保余额不会出现负数。

SELECT ... FOR UPDATE 中,账户A的记录被锁定,其他事务无法修改,直到当前事务提交或回滚。

-- 开启事务
START TRANSACTION;

-- 使用悲观锁锁定账户A的记录
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;

-- 检查余额是否足够
IF (SELECT balance FROM accounts WHERE id = 1) >= 100 THEN
    -- 扣除账户A的余额
    UPDATE accounts SET balance = balance - 100 WHERE id = 1;
    -- 增加账户B的余额
    UPDATE accounts SET balance = balance + 100 WHERE id = 2;
END IF;

-- 提交事务
COMMIT;

乐观锁

概念

  • 乐观锁假设并发冲突很少发生,因此在访问数据时不加锁,而是在提交时检查数据是否被其他事务修改过。
  • 乐观锁通常通过版本号(Version)或时间戳(Timestamp)实现。

特点

  • 优点:提高并发性能,适合读操作多的场景。
  • 缺点:如果冲突频繁,会导致大量事务回滚。

使用场景

  • 高并发读操作。
  • 冲突较少的场景。

实例

在乐观锁中,通过 version 字段检查数据是否被修改。如果 version 不匹配,说明数据已被其他事务修改,当前事务需要回滚并重试。

即,当用户A进行提款操作时,若存在两个以上的请求进程(查询的version为1),当某一个进程率先请求成功,version会自增1,其他的进程就无法查询到该版本号,从而不会执行账户操作,事务回滚。

-- 开启事务
START TRANSACTION;

-- 查询账户A的余额和版本号
SELECT balance, version FROM accounts WHERE id = 1;

-- 假设查询到的 balance = 500, version = 1
-- 检查余额是否足够
IF 500 >= 100 THEN
    -- 扣除账户A的余额,并更新版本号
    UPDATE accounts SET balance = balance - 100, version = version + 1 WHERE id = 1 AND version = 1;

    -- 检查是否更新成功
    IF ROW_COUNT() = 0 THEN
        -- 版本号不匹配,说明数据已被其他事务修改,回滚事务
        ROLLBACK;
    ELSE
        -- 增加账户B的余额
        UPDATE accounts SET balance = balance + 100 WHERE id = 2;
        -- 提交事务
        COMMIT;
    END IF;
END IF;