条件竞争致使的支付安全问题

最近在整理关支付安全的内容,其中就是涉及到了一个在支付过程当中的条件竞争问题。如下都是基于mysql的与php的架构来描述该问题,大佬勿喷。php

0x01. 条件竞争

  1. 什么是条件竞争:mysql

    竞争条件 发生在多个线程同时访问同一个共享代码、变量、文件等没有进行锁操做或者同步操做的场景中。【Wikipedia-computer_science】sql

  2. 一个简单的购买的业务:

后台代码实现以下:数据库

以上是一个购买商品的流程,看似并无什么问题。后端

若是每次请求都是一个单线程的请求是没有什么问题的,可是若是采用多线程的并发请求就会出现问题。安全

由于每次的购买流程都是须要必定的时间去按照购买的流程执行,若是咱们在第一次购买的流程3尚未结束时,就再去执行这一遍流程时,那么在第流程2时查询用户的余额就仍是初始的余额,这是由于第一次购买的流程3尚未结束,没有结束也就意味着余额没有扣除,因此金额就是初始的值。markdown

在上面的百年青花瓷购买的案例中,若是咱们只有1000块钱,可是咱们在多线程的状况下去购买青花瓷的时候就可能购买到10件以上的数目。多线程

0x02. 实际测试

  1. 在数据库查看用户数据架构

  2. 打开购买页面并发

打开burp拦截购买商品的数据包,点击购买。

设置intruder发送50个数据包,线程调到25后发包

并发请求后查看购买页面,已拥有17件,余额成了-700。

咱们调出mysql的查询日志。

从日志中能够看到咱们的请求是并发的执行的,在一次查询尚未结束时就进行了下一次的查询,因此这也很容易产生两次查询的余额是相同的。

因此当我只剩100块的只能购买一件商品的时候,可是有可能两次查询余额都100,是符合购买流程的操做的,后面也会对余额100进行两次扣除操做,因此最后余额变成了负数,购买的数量也大于10。

0x03. Solve the problem

mysql事务

  1. 在网上看到一篇关于mysql与php的条件竞争的分析中的解决方案是这样的:

这种解决的方案的意思是给mysql查询进行一个事务的处理,在mysql的查询前添加一个BEGIN,开始一个事务,在结束时添加一个COMMIT提交一个事务,完成一个查询操做。

本地测试一下:

设置好线程再次并发购买一次,结果发现仍是失败了。并无解决条件竞争带来的问题,因此解决方案是不行的。

  1. 什么是mysql的事务

事务是一组原子性sql查询语句,被看成一个工做单元。若mysql对改事务单元内的全部sql语句都正常的执行完,则事务操做视为成功,全部的sql语句才对数据生效,若sql中任意不能执行或出错则事务操做失败,全部对数据的操做则无效(经过回滚恢复数据)。

经过上面一句话差很少就知道了缘由,只有在查询语句不能执行或出错则事务操做失败, 因此咱们添加事务并不能解决mysql竞争的问题,由于咱们的查询是不会错误的,既然不会出错也就会照样执行并发的请求。

0x04. mysql锁

悲观锁

悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)是一种并发控制的方法。它能够阻止一个事务以影响其余用户的方式来修改数据。若是一个事务执行的操做对某行数据应用了锁,那只有当这个事务把锁释放,其余事务才可以执行与该锁冲突的操做。 悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。

当咱们查询的数据随时可能会被其余操做修改时,咱们对这个数据进添加一个悲观锁,若是想再次对这个数据进行操做时,只有这条查询的操做结束后释放这个悲观锁,其余查询才能够对这条数据进行操做,若是锁定没有结束时,其余查询会一直进行一个等待的状态。

mysql 悲观锁的实现

select * from goods where id = 1 for update;

for update仅适用于InnoDB引擎,且必须在事务块(BEGIN/COMMIT)中才能生效。在进行事务操做时,经过“for update”语句,MySQL会对查询结果集中每行数据都添加排他锁,其余线程对该记录的更新与删除操做都会阻塞。排他锁包含行锁、表锁。

使用悲观锁解决上述并发问题:

并发测试:

通过屡次测试后发现商品购买正常。没有出现条件竞争的问题

咱们这边来看下后端mysql查询的日志

咱们吧mysql的查询分红了11组

前七组都没条件竞争的问题,全部操做都是有序执行的,可是在第八组的时候开始出现问题

在第八条数据查询的事务尚未结束时就开始查询第九条的数据了

可是因为咱们使用了for update(悲观锁),对select语句进行锁定,因此在执行到第九条的时候发现第八条的事务尚未结束,因此他就只能等待第八条的更新完库存以后执行commit(提交事务)操做,第八条查询的锁才会进行释放,而后第九条查询才能获取到用户的余额进行下一步操做。因此经过悲观锁解决了条件竞争带来的问题。

可是悲观锁的弊端在每次查询都会对数据进行锁定,在高并发的请请求下会变得很慢。因此高并发的请求不建议使用悲观锁。

做者:0xchery

相关文章
相关标签/搜索