@@ -9,6 +9,9 @@ describe('rewrite-request-smuggling', () => {
99 return
1010 }
1111
12+ const ssrfProbePath = '/secret-upgrade'
13+ const ssrfProbeBody =
14+ 'SSRF_CONFIRMED: You reached the internal service at 127.0.0.1'
1215 let backend : http . Server
1316 let backendPort : number
1417 let intermediary : http . Server
@@ -56,11 +59,53 @@ describe('rewrite-request-smuggling', () => {
5659 socket . write ( payload )
5760 } )
5861 socket . once ( 'error' , reject )
59- socket . setTimeout ( 1000 , ( ) => socket . destroy ( ) )
62+ socket . setTimeout ( 5000 , ( ) => socket . destroy ( ) )
6063 socket . once ( 'close' , ( ) => resolve ( ) )
6164 } )
6265 }
6366
67+ async function sendAbsoluteUrlUpgradePayload ( {
68+ nextPort,
69+ targetPort,
70+ } : {
71+ nextPort : number
72+ targetPort : number
73+ } ) {
74+ const payload = Buffer . from (
75+ `GET http://127.0.0.1:${ targetPort } ${ ssrfProbePath } HTTP/1.1\r\nHost: 127.0.0.1:${ nextPort } \r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n` ,
76+ 'latin1'
77+ )
78+
79+ return await new Promise < string > ( ( resolve , reject ) => {
80+ const socket = net . createConnection ( {
81+ host : '127.0.0.1' ,
82+ port : nextPort ,
83+ } )
84+ const chunks : Buffer [ ] = [ ]
85+ let settled = false
86+
87+ const finish = ( ) => {
88+ if ( settled ) return
89+ settled = true
90+ resolve ( Buffer . concat ( chunks ) . toString ( 'latin1' ) )
91+ }
92+
93+ socket . once ( 'connect' , ( ) => {
94+ socket . write ( payload )
95+ } )
96+ socket . on ( 'data' , ( chunk ) => {
97+ chunks . push ( Buffer . isBuffer ( chunk ) ? chunk : Buffer . from ( chunk ) )
98+ } )
99+ socket . once ( 'error' , ( err ) => {
100+ if ( settled ) return
101+ settled = true
102+ reject ( err )
103+ } )
104+ socket . setTimeout ( 1000 , ( ) => socket . destroy ( ) )
105+ socket . once ( 'close' , finish )
106+ } )
107+ }
108+
64109 beforeAll ( async ( ) => {
65110 backendPort = await findPort ( )
66111 intermediaryPort = await findPort ( )
@@ -84,6 +129,14 @@ describe('rewrite-request-smuggling', () => {
84129 res . end ( 'not-found' )
85130 } )
86131
132+ backend . on ( 'upgrade' , ( req , socket ) => {
133+ backendRequests . push ( `${ req . method } ${ req . url } ` )
134+ socket . write (
135+ `HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: ${ Buffer . byteLength ( ssrfProbeBody ) } \r\n\r\n${ ssrfProbeBody } `
136+ )
137+ socket . end ( )
138+ } )
139+
87140 intermediary = http . createServer ( ( req , res ) => {
88141 const connectionHeader = Array . isArray ( req . headers [ 'connection' ] )
89142 ? req . headers [ 'connection' ] . join ( ',' )
@@ -231,4 +284,19 @@ describe('rewrite-request-smuggling', () => {
231284 } )
232285 expect ( backendRequests ) . not . toContain ( 'GET /secret' )
233286 } )
287+
288+ it ( 'does not proxy upgrade requests with absolute URLs without an external rewrite' , async ( ) => {
289+ backendRequests . length = 0
290+
291+ const nextPort = Number ( new URL ( next . url ) . port )
292+ const response = await sendAbsoluteUrlUpgradePayload ( {
293+ nextPort,
294+ targetPort : backendPort ,
295+ } )
296+
297+ expect ( response ) . not . toContain ( ssrfProbeBody )
298+ expect (
299+ backendRequests . some ( ( request ) => request . includes ( ssrfProbePath ) )
300+ ) . toBe ( false )
301+ } )
234302} )
0 commit comments