blackhat 2023中有一篇议题《Smashing the state machine: The true potential of web race conditions》中介绍了利用单数据包进行条件竞争这么一个新技术,并且提供了很多的Lab来练习。
本文记录的是我阅读议题的paper进行学习和做Lab的过程,部分段落翻译自议题的paper。
单数据包攻击技术,就是这篇论文的核心,后续的攻击基本都要依赖这种技术。
它利用了HTTP/2的一个新特性:允许HTTP请求通过单一连接同时发送。在HTTP/1.1中,它们必须是顺序的。
使用单个TCP数据包完全消除了网络抖动的影响,并结合HTTP/1.1的最后字节同步技术的一个技巧。由于服务器只有在认为请求完成后才处理请求,通过从每个请求中保留一小部分数据,可以预先发送大部分数据,然后用单个TCP数据包“完成”20-30个请求。
实现单数据包攻击的关键功能是多路复用:在单个连接上支持多个并发消息
首先,预先发送每个请求的大部分内容:
接下来,准备发送最后的帧:
最后,发送被保留的帧。
超过限制的条件竞争,最常见的条件竞争,利用小的竞争窗口:
要实现这个条件竞争,Burp Suite 2023.9为Burp Repeater增加了强大的新功能,可以轻松地发送一组并行请求,从而大大减少其中一个因素的影响,即网络抖动。Burp会自动调整其使用的技术以适应服务器支持的HTTP版本:
要买价值1337刀的夹克,但是只有50刀。有一个应用折扣的步骤,通过尝试发现这个与session相关,因此可以猜测处理的流程:从数据库中查看当前session的折扣是否使用,如果没有使用则应用到购物车的总金额中,并将数据库的折扣设置为已使用。
因此可能存在条件竞争漏洞,利用bp重复打开20个repeater,放到一个组中,选择single-packet attack
:
多次尝试后发现折扣被多次使用,低于50刀,可以购买:
主要是改成用bp的Turbo Intruder插件。
环境主要就是用户名密码的爆破,但是一个用户名尝试3次后就会锁定,因此尝试次数与用户名相关联而不是session。因此考虑提交登录尝试和网站增加与特定用户名关联的失败登录尝试次数的计数器之间存在竞争窗口。
攻击:
将password改成%s
并在python代码中爆破。
爆破成功后拿session登录然后删除用户即可。
隐藏的多步骤序列,即一个单个请求实际上背后可能隐藏有多个子状态的转换
猜测处理逻辑是这样的:
/login2
页面,要求输入2FA code因此存在这样的逻辑漏洞,登录成功后直接拿session访问account页面即可,无需输入2FA code。
session['userid'] = user.userid
if user.mfa_enabled:
session['enforce_mfa'] = True
# generate and send MFA code to user
# redirect browser to MFA code entry form
paper的作者提出了一种检测和利用隐藏的多步骤序列的方法论
预测潜在的碰撞。需要考虑三点:
探索线索。实际上就是测试了,具体的测试步骤类似于模糊测试。下面是大概的两个方向:
证明概念。即发现线索后进行思考和排查,排除其他的因素,证明该概念并将其转化为可行的攻击。
具体的方法论可以看paper。
多端点的条件竞争。
购买流程流程如下:
POST /cart/checkout
GET /cart/order-confirmation?order-confirmed=true
存在的漏洞就是在确认订单的时候,没有充足的验证,没有验证当前购物车的总金额和付款的金额是否一致,导致了攻击。攻击流程:
/cart/checkout
。GET /cart/order-confirmation?order-confirmed=true
如果付款和订单确认是由同一个请求处理的时候,就会有一种变体:
这时候就需要在支付成功到确认订单完成这段时间窗口内进行条件竞争。
在实现多端点条件竞争时,,即使用单数据包技术同时发送所有请求,也可能遇到需要对齐每个请求的竞争窗口的问题。
这可能由下面两个原因导致:
后端连接延迟通常不会干扰条件竞争,因为它会同等地延迟并行请求,导致这些请求虽然存在延迟,但是彼此仍然算是同步的。
可以用连接预热来判断延迟是否由端点特定因素引起。在Burp Repeater中,在标签组开头添加一个GET /
请求,然后使用Send group in sequence (single connection)
选项。如果第一个请求仍然有较长的处理时间,但其余的请求现在都在短时间窗口内处理完毕,则说明是则说明不是由端点特定因素引起,可以忽略延迟继续测试。
如果连接预热没有造成差别,那说明可能确实是由端点特定因素引起,因此有两种解决办法。
一种就是引入短暂的客户端延迟,即通过多次请求确定两个端点之间延迟差别的时间,让处理更快的请求之前加上这么一个短暂的延迟。相应的就会导致无法使用单数据包攻击技术。
第二种方法是利用一项常见的安全功能。如果发送请求过快且数量过多,Web服务器通常会延迟处理这些请求。通过发送大量虚假请求故意触发速率或资源限制,可以造成适当的服务器端延迟。这种情况使得单数据包攻击变得可行。
这个实验经过测试想要条件竞争有两个端点,一个是GET /cart/checkout
付款并确认订单,一个是POST /cart
将商品添加到购物车中。因为付款和订单确认都是在/cart/checkout
中处理(上图),因此竞争窗口会非常小。
在基准测试阶段,如果将POST /cart
和POST /cart/checkout
放在一个组然后执行Send group in sequence (single connection)
多次,会发现每次第一个请求都会比第二个请求延迟大一半甚至一倍,尝试用连接预热的方法,发现GET /
请求仍然有较长的处理时间,其他的两个请求执行时间会缩短,POST /cart
经常延迟会比POST /cart/checkout
小,就说明这种延迟是由后端连接延迟导致,不会干扰条件竞争。
因此直接将POST /cart/checkout
和20个POST /cart
放在同一个组然后执行单数据包攻击,多次尝试即可成功。
如果一个端点足够复杂也可能存在条件竞争,例如下面的设想情况:
现实生成中电子邮件地址确认或任何基于电子邮件的操作通常是单端点条件竞争的一个很好目标,因为电子邮件通常在服务器向客户端发出HTTP响应后,在后台线程中发送,导致存在一个竞争窗口。
这个实验实际上是paper中提到的Gitlab邮箱修改的那个例子。
逻辑产生漏洞的原因是在更改电子邮件时,会更新user.unconfirmed_email,将token保存在user.confirmation_token中,并将包含令牌的link通过电子邮件发送到user.unconfirmed_email。这里的email是从http参数中取到的。
self.unconfirmed_email = self.email
// from 'email' parameter...
self.confirmation_token = @raw_confirmation_token = Devise.friendly_token
...
// this eventually gets handed off a different thread to render & send the email
send_devise_notification(:confirmation_instructions, @raw_confirmation_token, { to: unconfirmed_email } )
但在构造邮件内容的时候,email和token都是从数据库中提取的:
// an email is queued to the unconfirmed\_email argument
// but the body is generated via a template engine reads the variables back from the database
- confirmation_link = confirmation_url(@resource, confirmation_token: @token)
- if @resource.unconfirmed\_email.present? || !@resource.created\_recently?
#content
= email_default_heading(@resource.unconfirmed_email || @resource.email)
%p= _('Click the link below to confirm your email address.')
#cta
= link_to _('Confirm your email address'), confirmation_link
因此存在这么一个竞争窗口,第一次更新邮箱请求发送后后端会发送电子邮件,邮件发送前后端处理了第二个更新邮箱请求,将第二个邮箱地址和token写到了数据库中。因此如果第一次发送的邮件内容出现了第二次的邮箱,就说明竞争成功。
例如php session的原生处理模块一次只会处理session相关的一个请求,可能要使用不同的sessionid发送请求。
许多应用程序在创建对象时需要经过多个步骤,这可能会引入一个临时的中间状态,在该状态下对象是可以被利用的。具体例子看Lab就行。
这个实验开始有点现实渗透的意思了,也是Partial construction race conditions一个典型的例子。
后端注册的逻辑大概就是先将用户名、密码、邮箱存入到数据库中,然后生成这个邮箱对应的token发送到邮箱并将token存入数据库中。用户在邮箱中查收token并验证就可以通过注册了。
对应的漏洞就是,存在这么一个竞争窗口,用户名、密码、邮箱存入到数据库之后,生成token存入数据库之前,在这个窗口中token实际上是null,因此这时候的confirm接口传一个等同于null的token就可以验证通过。
confirm接口是从/resources/static/users.js
中找到,通过测试发现POST /confirm?token[]=
的响应结果是"Incorrect token: Array"
而不是forbidden
,因此等效于null。
拿Turbo Intruder中的examples/race-single-packet-attack.py
攻击:
def queueRequests(target, wordlists):
# if the target supports HTTP/2, use engine=Engine.BURP2 to trigger the single-packet attack
# if they only support HTTP/1, use Engine.THREADED or Engine.BURP instead
# for more information, check out https://portswigger.net/research/smashing-the-state-machine
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=1,
engine=Engine.BURP2
)
confirmationReq = '''POST /confirm?token[]= HTTP/2
Host: 0a4e00ff043eb735814e490200960010.web-security-academy.net
Cookie: phpsessionid=BRTkAgx6CPAghuEp5LnJpmdoRvynz5es
Content-Length: 0
'''
# the 'gate' argument withholds part of each request until openGate is invoked
# if you see a negative timestamp, the server responded before the request was complete
for i in range(20):
username = 'ff'+str(i)
engine.queue(target.req, username,gate='race1')
for j in range(20):
engine.queue(confirmationReq, gate='race1')
# once every 'race1' tagged request has been queued
# invoke engine.openGate() to send them in sync
engine.openGate('race1')
def handleResponse(req, interesting):
table.add(req)
时间敏感的攻击实际上不算严格意义上的条件竞争,利用单数据包技术可以在一定次尝试哪实现两次请求的时间戳相同,这就让后端内通过时间戳生成的一些类似token等有可能出现碰撞。具体看Lab的例子。
密码重置需要输入的token的产生是根据时间戳生成的,因此利用单数据包技术有机会让两次请求的token相同。
在测试时可以尝试将重置wiener
用户密码的两个请求放在一个数据包中发送,并且考虑到PHPsession,让这两个请求用不同的session。多次发送后在邮箱中可以发现碰撞:
说明确实存在漏洞,将其中一个请求的用户名改成carlos
多次尝试就可以碰撞token,实现carlos
用户的密码重置。
最后则是总结了如何阻止条件竞争漏洞:
单数据包攻击技术提出来感觉大部分的HTTP2服务都可能会被条件竞争影响,技术本身其实并不复杂但是非常有意思,通过paper和Lab也是学习了很多东西。
Race conditions | Web Security Academy
Smashing the state machine: the true potential of web race conditions | PortSwigger Research
2 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!