介绍

条件竞争(Race Condition)是一种并发编程中的问题,指的是多个线程或进程在无正确同步机制的情况下访问共享资源,导致程序行为不可预测错误的情况。

触发条件

  • 并发,即至少存在两个并发执行流。这里的执行流包括线程,进程,任务等级别的执行流。
  • 共享对象,即多个并发流会访问同一对象。常见的共享对象有共享内存,文件系统,信号。一般来说,这些共享对象是用来使得多个程序执行流相互交流。此外,我们称访问共享对象的代码为临界区。在正常写代码时,这部分应该加锁。
  • 改变对象,即至少有一个控制流会改变竞争对象的状态。因为如果程序只是对对象进行读操作,那么并不会产生条件竞争。

现实场景

网店购物

假设网店有 1 件商品,两个用户几乎同时下单:

  1. 用户 A 读取库存(库存 = 1)
  2. 用户 B 也读取库存(库存 = 1)
  3. 用户 A 付款成功,系统更新库存 1 → 0
  4. 由于用户 B 读取库存的时候用户A并没有付款成功,所以读到的库存也是1允许继续执行付款操作,最后付款成功,库存实际变成 -1(超卖)

银行取钱

假设银行账户余额为 1000 元,两个人同时在不同的 ATM 机上取款 800 元:

  1. 线程 1(ATM A) 读取账户余额 1000
  2. 由于线程1此时还没有取款就已经执行了线程 2(ATM B) ,所以线程2也读取到账户余额 1000
  3. 线程 1 计算 取款后余额:200
  4. 线程 2 计算 取款后余额:200
  5. 线程 1 写入 新余额 200
  6. 线程 2 也写入 新余额 200,但实际应该是 -600(透支)

票务系统

如果两个用户同时购买最后一张票:

  1. 用户 A 读取票数(剩余 1 张)
  2. 用户 B 也读取票数(剩余 1 张)
  3. 用户 A 付款成功,票数变 0
  4. **用户 B 也付款成功,车票相同产生冲突

CTF中的应用

HgameCTF2025

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from flask import Flask, request, render_template, render_template_string, redirect
import os
import templates

app = Flask(__name__)
pwd = os.path.dirname(__file__)
show_msg = templates.show_msg


def readmsg():
filename = pwd + "/tmp/message.txt"
if os.path.exists(filename):
f = open(filename, 'r')
message = f.read()
f.close()
return message
else:
return 'No message now.'


@app.route('/index', methods=['GET'])
def index():
status = request.args.get('status')
if status is None:
status = ''
return render_template("index.html", status=status)


@app.route('/send', methods=['POST'])
def write_message():
filename = pwd + "/tmp/message.txt"
message = request.form['message']

f = open(filename, 'w')
f.write(message)
f.close()

return redirect('index?status=Send successfully!!')

@app.route('/read', methods=['GET'])
def read_message():
if "{" not in readmsg():
show = show_msg.replace("{{message}}", readmsg())
return render_template_string(show)
return 'waf!!'


if __name__ == '__main__':
app.run(host = '0.0.0.0', port = 5000)

render_template导致产生ssti注入漏洞,虽然waf过滤了{但是可以利用代码存在的条件竞争漏洞

重点关注 /send 的文件写入操作和 /read 的文件读取操作,在高并发情况下,多个请求可能同时修改 message.txt

/send路由接收到信息后先将内容写入message.txt,之后访问/read才会进行waf检查,

如果message在/read检查时是正常内容,正准备输出内容之前这个空隙又一次执行了/send覆盖了原本正常的message内容,那么就可以成功写入恶意代码,实现bypass

因此我们同时间进行三个请求:

  1. /send 发送正常内容
  2. /send 发送恶意代码
  3. /read 读内容

就可以绕过waf实现代码执行

参考

Race Condition - CTF Wiki