由于进程竞争相关资源(如CPU的运行时间等)导致的程序错误,称为Race Condition漏洞。
漏洞场景:简易转账程序,开始的时候bank1001账号有10000元钱,bank1002账号0元钱,每一次请求就从bank1001账号转账10元钱到bank1002账号。
创建数据库tmpbank,其中有一张表users,对应的SQL执行语句如下:
drop database if exists tmpbank;
create database tmpbank;
use tmpbank;
create table users(id varchar(50) primary key,number int);
insert usersvalues("bank1001",10000);
insert users values("bank1002",0);
对应的PHP代码如下:
functiongetnum($db,$id){
$query="select * from users whereid='".$id."'";
$result= mysqli_query($db,$query);
$row=mysqli_fetch_assoc($result);
return $row['number'];
}
functionupdate($db,$id,$num){
$query="update users set number=$numwhere id='".$id."'";
$result= mysqli_query($db,$query);
}
$from_id="bank1001";
$to_id="bank1002";
$num=1;
$db=mysqli_connect('localhost','root','123456','tmpbank');
$num1=getnum($db,"bank1001");
if($num1>1){
update($db,$from_id,$num1-10);
$num2=getnum($db,"bank1002");
update($db,$to_id,$num2+10);
}
mysqli_close($db);
?>
下面再写一个并发请求的Python程序(程序在Ubuntu系统Python3运行正常,Windows系统不行—创建进程的方式不一样),代码如下:
importos
importrequests
print("start......");
os.fork()#2
os.fork()#4
os.fork()#8
os.fork()#16
os.fork()#32
os.fork()#64
os.fork()#128
ret=requests.get('http://127.0.0.1/test/t1.php')
按照PHP程序的基本逻辑,访问一次t1.php,则bank1001账号减少10元钱,而bank1002账号增加10元。每一个进程访问t1.php程序时,bank1001账号减少10元钱,而bank1002账号增加10元,Python并发程序同时开启128个进程,执行完成后,bank1001账号余额还有10000-1280=8720元,bank1002账号余额应该为1280元。实际运行结果如下(运行3次的不同结果,通过mysql数据库的控制台查看Python程序执行前和执行后的结果):
这是因为在并发的条件下,由于多个进程在竞争CPU时间时,出现了竞争条件漏洞。一个进程完成转账的基本核心操作步骤如下:
1 读取bank1001账号余额$num1;
2 将$num1-10,并更新bank1001账号余额;
3读取bank1002账号余额$num2;
4 将$num1-10,并更新bank1001账号余额。
在并发条件下,由于竞争CPU时间,一个进程可能没有处理完,另一个进程就会抢占CPU时间了,假设并发两个进程A和B,同时处理转账业务,则可能的执行顺序如下所示:
进程A和进程B交叉执行场景1:
A.1 读取bank1001账号余额(10000)
A.2 更新bank1001账号余额(9990)
A.3读取bank1002账号余额(0)
B.1读取bank1001账号余额(9990)
B.2 更新bank1001账号余额(9980)
B.3读取bank1002账号余额(0)
A.4 更新bank1002账号余额(10)
B.4 更新bank1002账号余额(10)
综合结果:bank1001账号余额9980(转出操作两次),bank1001账号余额10(B进程覆盖A进程的运行结果),这样钱的总数9980+10=9990少了10元。
进程A和进程B交叉执行场景2:
A.1 读取bank1001账号余额(10000)
B.1读取bank1001账号余额(10000)
A.2 更新bank1001账号余额(9990)
A.3读取bank1002账号余额(0)
A.4 更新bank1002账号余额(10)
B.2 更新bank1001账号余额(9990)
B.3读取bank1002账号余额(10)
B.4 更新bank1002账号余额(20)
综合结果:bank1001账号余额9990(B进程覆盖A进程的运行结果),bank1001账号余额20(转入操作两次),这样钱的总数9990+20=10010多了10元。