@@ -5,6 +5,8 @@ import { IScenarioOptions } from '../IScenarioOptions.js';
55import { IncomingMessage } from 'http' ;
66import { IncomingHttpHeaders } from 'http2' ;
77import { CheckerScenario } from '../../baseScenarios/CheckerScenario.js' ;
8+ import { LRUCache } from 'lru-cache' ;
9+ import * as crypto from 'crypto' ;
810
911export interface IXForwardedForOptions {
1012 /**
@@ -34,6 +36,7 @@ export class XForwardedForChecker extends CheckerScenario {
3436
3537 private reverseProxiesRange : Array < AddressObject > ;
3638 private readonly currentOptions : IXForwardedForOptions ;
39+ private extractIpCache : LRUCache < string , Array < IExtractedIP > , unknown > ;
3740
3841 constructor ( options ?: IScenarioOptions ) {
3942 debug ( 'construct' ) ;
@@ -45,6 +48,9 @@ export class XForwardedForChecker extends CheckerScenario {
4548 ...currentOptions
4649 } ;
4750 this . reverseProxiesRange = ( currentOptions ?. trustedProxies ?? [ ] ) . map ( ( cidr ) => this . getAddressObjectWithCache ( cidr ) ) ;
51+ this . extractIpCache = new LRUCache < string , Array < IExtractedIP > > ( {
52+ max : this . ipObjectCache . max
53+ } ) ;
4854 }
4955
5056 private generateAlert (
@@ -81,11 +87,12 @@ export class XForwardedForChecker extends CheckerScenario {
8187 const ipResult = this . extractIps ( req ) ;
8288
8389 const ipStr = ip . addressMinusSuffix ?? '' ;
84- if ( ipResult . findIndex ( ( i ) => ! i . trustedProxy ) < ipResult . length ) {
90+ const untrustedProxyIndex = ipResult . findIndex ( ( i ) => ! i . trustedProxy ) ;
91+ if ( untrustedProxyIndex + 1 < ipResult . length ) {
8592 localDebug ( 'untrusted "proxy" pass header, send alert ? %o' , alertOnNotTrustedIps ) ;
8693 if ( alertOnNotTrustedIps ) {
87- //create alert
8894 const scenarioName = `${ XForwardedForChecker . scenarioName } /untrusted-proxy` ;
95+ const headers = this . getXForwardedForHeader ( req . headers ) ;
8996 //create alert
9097 alerts . push (
9198 this . generateAlert (
@@ -95,6 +102,14 @@ export class XForwardedForChecker extends CheckerScenario {
95102 events : [
96103 {
97104 meta : [
105+ {
106+ key : 'http_forwarded_for' ,
107+ value : headers [ 0 ]
108+ } ,
109+ {
110+ key : 'http_forwarded_for_parsed' ,
111+ value : ipResult . map ( ( { ip } ) => ip ) . join ( ', ' )
112+ } ,
98113 {
99114 key : 'source_ip' ,
100115 value : ipStr
@@ -175,7 +190,16 @@ export class XForwardedForChecker extends CheckerScenario {
175190
176191 let firstUntrustedIpFound = false ;
177192
178- return [ remoteAddressIPResult , ...this . extractIpsFromHeader ( this . getXForwardedForHeader ( req . headers ) ) ] . map ( ( ipResult ) => {
193+ const xForwardedForHeaderValues = this . getXForwardedForHeader ( req . headers ) ;
194+
195+ //use cache if this xForwardedFor is always known
196+ const cacheKey = xForwardedForHeaderValues . map ( ( h ) => crypto . createHash ( 'sha256' ) . update ( h ) . digest ( 'hex' ) ) . join ( '|' ) ;
197+ const cache = this . extractIpCache . get ( cacheKey ) ;
198+ if ( cache ) {
199+ return [ ...cache ] ;
200+ }
201+
202+ const result = [ remoteAddressIPResult , ...this . extractIpsFromHeader ( this . getXForwardedForHeader ( req . headers ) ) ] . map ( ( ipResult ) => {
179203 localDebug ( 'test if %o is a reverse proxy' , ipResult . ip ) ;
180204
181205 ipResult . valid = true ;
@@ -208,6 +232,8 @@ export class XForwardedForChecker extends CheckerScenario {
208232 localDebug ( '%o is not a reverse proxy' , ipResult . ip ) ;
209233 return ipResult ;
210234 } ) ;
235+ this . extractIpCache . set ( cacheKey , result ) ;
236+ return [ ...result ] ;
211237 }
212238
213239 public extractIp = ( req : IncomingMessage ) : IIpExtractionResult | undefined => {
0 commit comments