Django下防御Race Condition

目录

漏洞原因

环境搭建

复现

A.无锁无事务时的竞争攻击

B.无锁有事务时的竞争攻击

防御

A.悲观锁加事务防御

B.乐观锁加事务防御

总结


漏洞原因

Race Condition 发生在多个执行实体(如线程、进程)同时访问共享资源时,由于执行顺序的不确定性,导致程序的最终行为依赖于这些实体的执行时序。如果程序逻辑未正确处理这种并发访问,就会产生意外的结果。

环境搭建

从github上下载源码GitHub - phith0n/race-condition-playground: Playground for Race Condition attack

然后拖到虚拟机上解压

复制代码
unzip race-condition-playground-main.zip

之后重命名并修改.env.default文件

复制代码
root@sxc-ubuntu:~/race-condition-playground-main# mv .env.default .env

安装依赖的库

复制代码
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 启动

最后还要修改一下templates目录下的form.html,将form表单的提交方式修改为form-data流

复现

A.无锁无事务时的竞争攻击

复制代码
class WithdrawView1(BaseWithdrawView):
    success_url = reverse_lazy('ucenter:withdraw1')
	//form表单验证
	//已经通过验证
    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())

整个操作没有使用事务,也没有加锁,理论上存在Race Condition漏洞。

1.登录后台

复制代码
http://192.168.217.128:8080/admin

2.在user模块添加Money

3.进入ucenter提现页面

复制代码
http://192.168.217.128:8080/ucenter/1/

4.点击提交使用burp抓包,发送到repeater模块。(注意,要将拦截到的包drop掉,不然10块钱直接没了,没法测试了)。然后将包内容复制到Yakit上的WebFuzzer下的request里面,并设置好重复发包数和并行线程数。

5.点击发送请求,我们会在request模块右边,看到响应结果,很幸运一次就成功了

在前端页面的withdrow logs模块也看到了日志信息

竞争成功

B.无锁有事务时的竞争攻击

复制代码
新编写一个`WithdrawView2`,加上`@transaction.atomic`修饰符:成功则成功,失败则回滚

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())

同样使用Yakit测试

发现结果并没有什么区别

防御

A.悲观锁加事务防御

Django在ORM里提供了对数据库Select for Update的支持,结合Where语句,可以实现行级的锁。 使用SELECT FOR UPDATE获取到的数据库记录,不会再被其他事务获取。

这样就可以保证我们在同一个事务内执行的操作的原子性,这是一个典型的悲观锁

"悲观锁"的意思是,我们先假设其他线程会修改数据,所以在操作数据库前就加锁,直到当前线程释放锁后,其他线程才能再次获取这个锁。

乐观锁通常通过版本号(Version)或时间戳(Timestamp)实现。

复制代码
我们使用`select_for_update()`来实现一个`WithdrawView3`:

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())

对于当前这个场景,我们再次尝试使用Yakit进行竞争攻击:

可见,此时返回包只有一个302响应了, 这意味着程序是按照预期运行,没有发生Race Condition问题。

B.乐观锁加事务防御

我们观察上述的WithdrawView3代码,其实会发现一个问题,如果有大量读操作的场景下,使用悲观锁会有性能问题。因为每次访问这个view都会锁住当前用户对象,此时其他要使用这个用户的场景(如查看用户主页)也会卡住。

另外,也不是所有数据库都支持select for update,我们也可以尝试使用乐观锁来解决Race Condition的问题。

乐观锁的意思就是,我们不假设其他进程会修改数据,所以不加锁,而是到需要更新数据的时候,再使用数据库自身的UPDATE操作来更新数据库。因为UPDATE语句本身是原子操作,所以也可以用来防御并发问题。

复制代码
我们新增一个`WithdrawView4`:

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())

使用Yakit进行测试,只有一次302返回:

乐观锁的优点就是不会锁住数据库记录,也就不会影响其他线程查询该用户。

总结

悲观锁乐观锁 是两种常见的并发控制机制,用于解决多个事务同时访问和修改同一数据时可能引发的冲突问题。它们的核心区别在于对并发冲突的处理方式。

相关推荐
2401_868534786 小时前
NFV:将安全设备部署到虚拟机上
网络
love530love6 小时前
LiveTalking 数字人项目 Windows 部署完全指南(EPGF 架构)
人工智能·windows·python·架构·livetalking·epgf
遇事不決洛必達6 小时前
【Python基础】GIL 锁是什么及其对爬虫的影响
爬虫·python·线程·进程·gil锁
星辰徐哥6 小时前
Spring Boot 微服务架构设计与实现
spring boot·后端·微服务
星辰徐哥6 小时前
Spring Boot 数据导入导出与报表生成
spring boot·后端·ui
明夜之约6 小时前
Spring Boot 自动装配源码
java·spring boot·后端
Leaton Lee6 小时前
Spring Boot分层架构详解:从Controller到Service再到Mapper的完整流程
java·spring boot·后端·架构
zhengfei6116 小时前
【渗透工具】Payloader — 渗透测试辅助平台(payload一键所有)
网络·安全·web安全
Micro麦可乐6 小时前
Spring Boot 实战:从零设计一个短链系统(含完整代码与数据库设计)
数据库·spring boot·后端·哈希算法·雪花算法·短链系统
Jinkxs6 小时前
Resilience4j- 与 Spring Boot 快速集成:自动配置与基础注解使用
java·spring boot·后端