【逻辑漏洞】并发漏洞——经典案例
【逻辑漏洞】并发漏洞——经典案例
原创 狗头安全 狗头网络安全 2024-11-26 09:16
一.简单介绍
1.1 并发漏洞概述
并发漏洞是指在多线程或多进程环境中,由于对共享资源的访问没有正确的同步控制,导致程序行为异常或安全问题的漏洞
当多个线程或进程并发访问共享资源,并且操作的执行顺序未被正确同步时,可能会导致不可预知的结果。例如,两个线程同时检查一个共享变量,然后尝试修改它,而没有任何锁机制,可能会导致数据不一致
利用竞争条件的主要障碍是确保多个请求同时处理,且它们的处理时间几乎没有差异—最好小于 1 毫秒
1.2 并发漏洞常出现的功能点及解决方法
1. 用户注册/账户管理
- 问题场景: 重复注册:多个线程同时验证用户名是否存在,可能导致两个用户使用相同的用户名注册成功。余额更新:并发更新账户余额,可能导致余额被覆盖或错误。
- 重复注册:多个线程同时验证用户名是否存在,可能导致两个用户使用相同的用户名注册成功。
- 余额更新:并发更新账户余额,可能导致余额被覆盖或错误。
- 解决方法:
- 在注册逻辑中加锁,或使用数据库的唯一约束。使用事务机制确保一致性。
2. 秒杀/抢购功能
- 问题场景: 库存超卖:多个线程同时减库存,导致实际库存为负数。订单重复生成:同一用户重复提交请求,导致重复购买。
- 库存超卖:多个线程同时减库存,导致实际库存为负数。订单重复生成:同一用户重复提交请求,导致重复购买。
- 解决方法:
- 对库存更新操作加分布式锁(如Redis分布式锁)。使用队列(如RabbitMQ)进行请求排队。
3. 支付与结算
- 问题场景:
- 重复支付:用户点击支付按钮多次,导致支付记录重复。金额计算错误:并发情况下对支付金额进行累加/扣减出现错误。
- 解决方法:
- 引入防重机制(如幂等性设计,使用唯一订单号)。使用数据库事务或原子操作进行金额更新。
4. 文件读写
- 问题场景: 文件覆盖:多个线程/进程同时写入文件,导致内容混乱或丢失。文件锁失效:锁定机制未正确实现,导致多个线程读取未完成写入的文件。
- 文件覆盖:多个线程/进程同时写入文件,导致内容混乱或丢失。
- 文件锁失效:锁定机制未正确实现,导致多个线程读取未完成写入的文件。
- 解决方法:
- 对文件操作加锁或使用线程安全的I/O类。使用临时文件和替换机制避免部分写入文件被读取。
5. 日志记录
- 问题场景: 多个线程同时写入日志文件,可能导致日志内容交错或部分日志丢失。
-
多个线程同时写入日志文件,可能导致日志内容交错或部分日志丢失。
-
解决方法:
- 使用线程安全的日志框架(如Log4j、SLF4J)。对日志写入操作加锁或使用异步日志写入。
6. 缓存更新
- 问题场景: 缓存击穿:多个线程同时发现缓存过期,导致瞬间大量请求击中数据库。缓存与数据库不一致:并发更新时,缓存未及时刷新。
- 缓存击穿:多个线程同时发现缓存过期,导致瞬间大量请求击中数据库。缓存与数据库不一致:并发更新时,缓存未及时刷新。
- 解决方法: 使用双写机制(更新数据库时同步更新缓存)。引入分布式锁或互斥更新策略。
- 使用双写机制(更新数据库时同步更新缓存)。引入分布式锁或互斥更新策略。
7. 任务调度
- 问题场景:多个实例同时运行定时任务,导致重复执行。未正确同步任务状态,导致多个线程对同一任务执行操作。
-
多个实例同时运行定时任务,导致重复执行。未正确同步任务状态,导致多个线程对同一任务执行操作。
-
解决方法: 使用分布式任务调度框架(如Quartz的分布式锁)。通过数据库表记录任务状态,确保同一任务只被执行一次。
- 使用分布式任务调度框架(如Quartz的分布式锁)。通过数据库表记录任务状态,确保同一任务只被执行一次。
8. 数据库操作
- 问题场景: 脏读、幻读、不可重复读:事务隔离级别不足,导致数据不一致。更新丢失:两个线程同时修改同一行数据,后者覆盖前者的修改。
- 脏读、幻读、不可重复读:事务隔离级别不足,导致数据不一致。更新丢失:两个线程同时修改同一行数据,后者覆盖前者的修改。
- 解决方法: 设置合适的事务隔离级别(如Serializable或Repeatable Read)。使用悲观锁或乐观锁机制(如版本号控制)。
- 设置合适的事务隔离级别(如Serializable
或Repeatable Read
)。使用悲观锁或乐观锁机制(如版本号控制)。
9. 队列消费
- 问题场景: 消费者并发处理同一条消息,导致重复处理。消息未确认,导致重复投递。
-
消费者并发处理同一条消息,导致重复处理。消息未确认,导致重复投递。
-
解决方法: 消息队列(如Kafka、RabbitMQ)配置幂等性检查。确保消息处理完成后再进行确认(ACK)。
- 消息队列(如Kafka、RabbitMQ)配置幂等性检查。确保消息处理完成后再进行确认(ACK)。
10. 多线程计算
- 问题场景:
- 多个线程同时写入共享变量,导致结果错误。临界区访问未同步,导致中间结果不一致。
- 解决方法:
- 使用线程安全的数据结构或锁机制。使用原子类(如Java的AtomicInteger)。
11. 微服务间的并发请求
- 问题场景: 重复调用导致资源浪费。多个服务对同一资源更新,造成数据不一致。
-
重复调用导致资源浪费。多个服务对同一资源更新,造成数据不一致。
-
解决方法: 通过请求去重机制(如唯一请求ID)。使用分布式锁(如ZooKeeper、Redis)。
- 通过请求去重机制(如唯一请求ID)。使用分布式锁(如ZooKeeper、Redis)。
12. 配置或状态更新
- 问题场景: 配置被多个线程同时更新,导致最终结果不可预期。并发读取未完全更新的配置。
-
配置被多个线程同时更新,导致最终结果不可预期。并发读取未完全更新的配置。
-
解决方法: 使用原子操作或锁保护配置更新。引入版本号或时间戳,确保配置更新有序。
- 使用原子操作或锁保护配置更新。引入版本号或时间戳,确保配置更新有序。
二.经典短信短信轰炸
2.1 一次中危提升到高危的肾透
在注册或者登录时会需要获取验证码,抓取获取验证码的请求包即可
mobile
即为我们的手机号参数,这里不对请求包进行修改直接进行并发
手机端查看,存在纵向并发漏洞
这里注意看请求包中包含code参数,很有可能就是我们的验证码,验证码在请求包中,这里对code参数进行修改
然后查看手机短信内容
成功将中危漏洞提升到高危,一定要对参数敏感,万物皆可并发
三.并发漏洞的思考与总结
1. 并发漏洞的本质
并发漏洞的核心问题在于多个线程或进程同时访问共享资源时,未能正确处理访问控制和协调机制。这种问题通常由以下原因引发:
– 共享资源竞争:
– 多个任务对同一资源(内存、文件、数据库等)进行修改,导致数据一致性问题。
– 执行顺序的不确定性:
– 由于操作的非确定性,程序在不同时间点表现出不可预测的行为。
– 缺乏同步机制:
– 开发者没有正确使用锁、事务或其他同步工具,导致竞态条件。
– 死锁与资源饥饿:
– 线程间相互依赖资源,形成环路或不公平竞争。
思考
:
并发本质上是利用时间或资源切片来提升性能,但如果设计和实现不当,可能带来更多的维护成本和复杂问题。
2. 设计并发系统时需要考虑的因素
2.1. 数据一致性
- 问题:在并发环境下,数据的一致性和完整性是首要挑战。
- 思考方向:
- 确定一致性模型:强一致性、最终一致性还是弱一致性?是否需要事务支持?事务的隔离级别如何选择?使用锁还是无锁设计(如CAS操作)?
2.2. 性能与扩展性
- 问题:引入锁或同步机制可能降低系统性能。
- 思考方向: 是否存在资源竞争的瓶颈?是否可以通过分区或分片减少并发冲突?引入的锁粒度是否合适?能否采用读写分离优化?
- 是否存在资源竞争的瓶颈?是否可以通过分区或分片减少并发冲突?引入的锁粒度是否合适?能否采用读写分离优化?
2.3. 并发控制粒度
- 问题:锁粒度过细会增加复杂性,过粗则影响性能。
- 思考方向: 资源的并发访问是否可以分区处理?需要保护的临界区范围是否可以缩小?是否可以引入读写锁或分段锁?
- 资源的并发访问是否可以分区处理?需要保护的临界区范围是否可以缩小?是否可以引入读写锁或分段锁?
2.4. 异常与恢复机制
- 问题:程序在并发条件下的异常处理可能导致资源泄漏或数据不一致。
- 思考方向: 如果操作失败,如何回滚或重试?在系统崩溃时,如何确保数据一致性?是否需要持久化日志或状态以实现幂等性?
- 如果操作失败,如何回滚或重试?在系统崩溃时,如何确保数据一致性?是否需要持久化日志或状态以实现幂等性?
2.5. 死锁与活锁
- 问题:多个线程/进程间相互依赖资源可能导致系统阻塞。
- 思考方向: 是否可以采用无锁编程?是否存在固定的锁获取顺序?是否引入了优先级反转等问题?
- 是否可以采用无锁编程?是否存在固定的锁获取顺序?是否引入了优先级反转等问题?
3. 编程实现中的常见错误与对策
3.1. 忽略共享资源的同步
- 错误:假定访问共享变量是原子的。
- 对策:
- 使用线程安全的数据结构(如ConcurrentHashMap),或通过锁保护共享资源。
3.2. 鉴别和避免竞态条件
- 错误:在检查和修改共享资源的操作之间缺乏同步。
- 对策:
- 引入原子操作或CAS(Compare-And-Swap)来避免竞态。
3.3. 滥用锁
- 错误:加锁范围过大或锁粒度过小,导致性能下降或代码复杂度增加。
- 对策: 优化锁的范围,仅对必要的临界区加锁。使用读写锁或分布式锁。
- 优化锁的范围,仅对必要的临界区加锁。使用读写锁或分布式锁。
3.4. 忽视锁的公平性
- 错误:使用非公平锁可能导致某些线程长期得不到执行机会。
- 对策:
- 在需要公平性的场景下,选择公平锁(如ReentrantLock(true))。
3.5. 不考虑系统资源的限制
- 错误:线程数量设置不当,导致CPU饱和或资源浪费。
- 对策:
- 根据实际情况设置线程池大小,避免无限制创建线程。
4. 系统层面的优化方向
4.1. 无锁编程
- 思路:通过CAS操作或版本控制避免锁的使用,减少锁带来的开销。
- 适用场景:多线程环境下共享变量的简单更新。
4.2. 使用事务或分布式事务
- 思路:通过事务机制保证操作的原子性和一致性,特别是对数据库的操作。
- 挑战:分布式事务的性能和复杂度需要权衡。
4.3. 幂等性设计
- 思路:保证并发操作的幂等性,使得重复操作不会带来额外的影响。
- 适用场景:支付系统、订单系统等需要保证操作结果一致性的场景。
4.4. 分布式锁
- 思路:在分布式系统中使用Redis、ZooKeeper等实现分布式锁。
- 注意:需要考虑锁的超时问题,避免死锁。
4.5. 任务排队
- 思路:通过消息队列(如Kafka、RabbitMQ)进行任务排队,降低瞬时并发量。
- 适用场景:秒杀活动、批量数据处理等场景。
5. 预防并发漏洞的思维习惯
5.1. 从最坏的情况出发
- 思考可能出现的竞态条件、资源冲突以及异常情况。设计代码时要假设所有线程都可能同时竞争资源。
5.2. 使用正确的工具
- 善用语言和框架提供的并发工具(如线程池、锁、原子操作等)。避免“造轮子”,直接使用成熟的并发库。
5.3. 充分测试
- 引入并发测试工具,模拟高并发场景,查找潜在问题。使用静态分析工具(如FindBugs)检测并发问题。
5.4. 持续监控
- 通过日志、指标监控并发热点和瓶颈。定期进行代码审查,优化并发处理。
6. 总结
并发漏洞本质上是程序对资源的不正确协调或访问控制引发的问题。在设计和实现并发程序时,既要考虑性能和效率,也要关注一致性和安全性。通过合理的设计模式、合适的同步机制以及全面的测试手段,可以有效减少并发漏洞的发生,提升系统的稳定性和可靠性