11/* global describe, it */
2- " use strict" ;
2+ ' use strict'
33
4- const { expect } = require ( " chai" ) ;
5- const net = require ( " net" ) ;
6- const http = require ( " http" ) ;
4+ const { expect } = require ( ' chai' )
5+ const net = require ( ' net' )
6+ const http = require ( ' http' )
77
8- describe ( " Security: buildURL SSRF prevention" , ( ) => {
9- const { buildURL } = require ( " ../lib/utils" ) ;
8+ describe ( ' Security: buildURL SSRF prevention' , ( ) => {
9+ const { buildURL } = require ( ' ../lib/utils' )
1010
11- it ( " should allow relative paths within base" , ( ) => {
12- const url = buildURL ( " /hi" , " http://localhost:3000" ) ;
13- expect ( url . origin ) . to . equal ( " http://localhost:3000" ) ;
14- } ) ;
11+ it ( ' should allow relative paths within base' , ( ) => {
12+ const url = buildURL ( ' /hi' , ' http://localhost:3000' )
13+ expect ( url . origin ) . to . equal ( ' http://localhost:3000' )
14+ } )
1515
16- it ( " should block absolute URL bypassing base" , ( ) => {
17- expect ( ( ) => buildURL ( " http://evil.com/admin" , " http://127.0.0.1:3000" ) )
18- . to . throw ( / S S R F p r e v e n t i o n / ) ;
19- } ) ;
16+ it ( ' should block absolute URL bypassing base' , ( ) => {
17+ expect ( ( ) => buildURL ( ' http://evil.com/admin' , ' http://127.0.0.1:3000' ) )
18+ . to . throw ( / S S R F p r e v e n t i o n / )
19+ } )
2020
21- it ( " should block HTTPS absolute URL bypass" , ( ) => {
22- expect ( ( ) => buildURL ( " https://internal/api" , " http://127.0.0.1:3000" ) )
23- . to . throw ( / S S R F p r e v e n t i o n / ) ;
24- } ) ;
21+ it ( ' should block HTTPS absolute URL bypass' , ( ) => {
22+ expect ( ( ) => buildURL ( ' https://internal/api' , ' http://127.0.0.1:3000' ) )
23+ . to . throw ( / S S R F p r e v e n t i o n / )
24+ } )
2525
26- it ( " should allow absolute URL when no base" , ( ) => {
27- const url = buildURL ( " http://target.com/api" ) ;
28- expect ( url . href ) . to . equal ( " http://target.com/api" ) ;
29- } ) ;
26+ it ( ' should allow absolute URL when no base' , ( ) => {
27+ const url = buildURL ( ' http://target.com/api' )
28+ expect ( url . href ) . to . equal ( ' http://target.com/api' )
29+ } )
3030
31- it ( " should sanitize protocol-relative within base" , ( ) => {
32- const url = buildURL ( " //evil.com/hi" , " http://localhost" ) ;
33- expect ( url . origin ) . to . equal ( " http://localhost" ) ;
34- } ) ;
35- } ) ;
31+ it ( ' should sanitize protocol-relative within base' , ( ) => {
32+ const url = buildURL ( ' //evil.com/hi' , ' http://localhost' )
33+ expect ( url . origin ) . to . equal ( ' http://localhost' )
34+ } )
35+ } )
3636
37- describe ( " Security: hop-by-hop header stripping" , ( ) => {
38- const { stripHttp1ConnectionHeaders } = require ( " ../lib/utils" ) ;
37+ describe ( ' Security: hop-by-hop header stripping' , ( ) => {
38+ const { stripHttp1ConnectionHeaders } = require ( ' ../lib/utils' )
3939
40- it ( " should strip transfer-encoding" , ( ) => {
41- const h = { " transfer-encoding" : " gzip, chunked" , " x-custom" : " val" } ;
42- const r = stripHttp1ConnectionHeaders ( h ) ;
43- expect ( r ) . to . not . have . property ( " transfer-encoding" ) ;
44- expect ( r ) . to . have . property ( " x-custom" , " val" ) ;
45- } ) ;
40+ it ( ' should strip transfer-encoding' , ( ) => {
41+ const h = { ' transfer-encoding' : ' gzip, chunked' , ' x-custom' : ' val' }
42+ const r = stripHttp1ConnectionHeaders ( h )
43+ expect ( r ) . to . not . have . property ( ' transfer-encoding' )
44+ expect ( r ) . to . have . property ( ' x-custom' , ' val' )
45+ } )
4646
47- it ( " should strip connection and keep-alive" , ( ) => {
48- const h = { connection : " close" , " keep-alive" : " t=5" , " x-data" : "ok" } ;
49- const r = stripHttp1ConnectionHeaders ( h ) ;
50- expect ( r ) . to . not . have . property ( " connection" ) ;
51- expect ( r ) . to . not . have . property ( " keep-alive" ) ;
52- expect ( r ) . to . have . property ( " x-data" , "ok" ) ;
53- } ) ;
47+ it ( ' should strip connection and keep-alive' , ( ) => {
48+ const h = { connection : ' close' , ' keep-alive' : ' t=5' , ' x-data' : 'ok' }
49+ const r = stripHttp1ConnectionHeaders ( h )
50+ expect ( r ) . to . not . have . property ( ' connection' )
51+ expect ( r ) . to . not . have . property ( ' keep-alive' )
52+ expect ( r ) . to . have . property ( ' x-data' , 'ok' )
53+ } )
5454
55- it ( " should strip host header from response" , ( ) => {
56- const h = { host : " evil.com" , " content-type" : " text/plain" } ;
57- const r = stripHttp1ConnectionHeaders ( h ) ;
58- expect ( r ) . to . not . have . property ( " host" ) ;
59- expect ( r ) . to . have . property ( " content-type" , " text/plain" ) ;
60- } ) ;
61- } ) ;
55+ it ( ' should strip host header from response' , ( ) => {
56+ const h = { host : ' evil.com' , ' content-type' : ' text/plain' }
57+ const r = stripHttp1ConnectionHeaders ( h )
58+ expect ( r ) . to . not . have . property ( ' host' )
59+ expect ( r ) . to . have . property ( ' content-type' , ' text/plain' )
60+ } )
61+ } )
6262
63- describe ( " Security: SSRF end-to-end proxy" , ( ) => {
64- let gateway , service , close , proxy , gHttpServer ;
63+ describe ( ' Security: SSRF end-to-end proxy' , ( ) => {
64+ let gateway , service , close , proxy , gHttpServer
6565
66- it ( " setup" , async ( ) => {
67- const fastProxy = require ( " ../index" ) ( { base : " http://127.0.0.1:3000" } ) ;
68- close = fastProxy . close ;
69- proxy = fastProxy . proxy ;
70- gateway = require ( " restana" ) ( ) ;
71- gateway . all ( "/*" , ( req , res ) => proxy ( req , res , req . url , { } ) ) ;
72- gHttpServer = await gateway . start ( 8080 ) ;
73- service = require ( " restana" ) ( ) ;
74- service . get ( " /service/get" , ( req , res ) => res . send ( "OK" ) ) ;
75- service . get ( " /service/evil" , ( req , res ) => {
76- res . setHeader ( " transfer-encoding" , " gzip, chunked" ) ;
77- res . setHeader ( " keep-alive" , " timeout=99" ) ;
78- res . setHeader ( " x-custom" , " downstream" ) ;
79- res . end ( " evil" ) ;
80- } ) ;
81- await service . start ( 3000 ) ;
82- } ) ;
66+ it ( ' setup' , async ( ) => {
67+ const fastProxy = require ( ' ../index' ) ( { base : ' http://127.0.0.1:3000' } )
68+ close = fastProxy . close
69+ proxy = fastProxy . proxy
70+ gateway = require ( ' restana' ) ( )
71+ gateway . all ( '/*' , ( req , res ) => proxy ( req , res , req . url , { } ) )
72+ gHttpServer = await gateway . start ( 8080 )
73+ service = require ( ' restana' ) ( )
74+ service . get ( ' /service/get' , ( req , res ) => res . send ( 'OK' ) )
75+ service . get ( ' /service/evil' , ( req , res ) => {
76+ res . setHeader ( ' transfer-encoding' , ' gzip, chunked' )
77+ res . setHeader ( ' keep-alive' , ' timeout=99' )
78+ res . setHeader ( ' x-custom' , ' downstream' )
79+ res . end ( ' evil' )
80+ } )
81+ await service . start ( 3000 )
82+ } )
8383
84- it ( " should block SSRF via absolute-form request" , ( done ) => {
84+ it ( ' should block SSRF via absolute-form request' , ( done ) => {
8585 // Use raw http.createServer instead of restana because
8686 // restana routes cannot match absolute-form req.url values.
87- const fastProxy = require ( " ../index" ) ( { base : " http://127.0.0.1:3000" } ) ;
88- const { proxy, close } = fastProxy ;
87+ const fastProxy = require ( ' ../index' ) ( { base : ' http://127.0.0.1:3000' } )
88+ const { proxy, close } = fastProxy
8989 const server = http . createServer ( ( req , res ) => {
90- proxy ( req , res , req . url , { } ) ;
91- } ) ;
90+ proxy ( req , res , req . url , { } )
91+ } )
9292 server . listen ( 0 , ( ) => {
93- const port = server . address ( ) . port ;
94- const c = net . connect ( port , " 127.0.0.1" , ( ) => {
95- c . write ( " GET http://169.254.169.254/latest HTTP/1.1\r\n" ) ;
96- c . write ( " Host: 127.0.0.1\r\n" ) ;
97- c . write ( " Connection: close\r\n\r\n" ) ;
98- } ) ;
99- let d = "" ;
100- c . on ( " data" , ch => { d += ch . toString ( ) ; } ) ;
101- c . on ( " end" , ( ) => {
102- expect ( d ) . to . include ( " 400" ) ;
93+ const port = server . address ( ) . port
94+ const c = net . connect ( port , ' 127.0.0.1' , ( ) => {
95+ c . write ( ' GET http://169.254.169.254/latest HTTP/1.1\r\n' )
96+ c . write ( ' Host: 127.0.0.1\r\n' )
97+ c . write ( ' Connection: close\r\n\r\n' )
98+ } )
99+ let d = ''
100+ c . on ( ' data' , ch => { d += ch . toString ( ) } )
101+ c . on ( ' end' , ( ) => {
102+ expect ( d ) . to . include ( ' 400' )
103103 // 400 status + normal proxy still functional after SSRF attempt
104- close ( ) ;
105- server . close ( ) ;
106- done ( ) ;
107- } ) ;
108- c . on ( " error" , done ) ;
109- } ) ;
110- } ) ; it ( " should strip hop-by-hop headers end-to-end" , async ( ) => {
111- const res = await require ( " supertest" ) ( gHttpServer )
112- . get ( " /service/evil" )
113- . expect ( 200 ) ;
114- expect ( res . headers [ " transfer-encoding" ] ) . to . not . equal ( " gzip, chunked" ) ;
115- expect ( res . headers [ " keep-alive" ] ) . to . not . equal ( " timeout=99" ) ;
116- expect ( res . headers [ " x-custom" ] ) . to . equal ( " downstream" ) ;
117- } ) ;
104+ close ( )
105+ server . close ( )
106+ done ( )
107+ } )
108+ c . on ( ' error' , done )
109+ } )
110+ } ) ; it ( ' should strip hop-by-hop headers end-to-end' , async ( ) => {
111+ const res = await require ( ' supertest' ) ( gHttpServer )
112+ . get ( ' /service/evil' )
113+ . expect ( 200 )
114+ expect ( res . headers [ ' transfer-encoding' ] ) . to . not . equal ( ' gzip, chunked' )
115+ expect ( res . headers [ ' keep-alive' ] ) . to . not . equal ( ' timeout=99' )
116+ expect ( res . headers [ ' x-custom' ] ) . to . equal ( ' downstream' )
117+ } )
118118
119- it ( " teardown" , async ( ) => {
120- close ( ) ;
121- await gateway . close ( ) ;
122- await service . close ( ) ;
123- } ) ;
124- } ) ;
119+ it ( ' teardown' , async ( ) => {
120+ close ( )
121+ await gateway . close ( )
122+ await service . close ( )
123+ } )
124+ } )
0 commit comments