Skip to content

Commit dec0c82

Browse files
authored
fix(Scenarios/XForwardedFor): (#30)
- correct the trigger of the alert - add cache to skip double ip extraction
1 parent 0700b0d commit dec0c82

1 file changed

Lines changed: 29 additions & 3 deletions

File tree

packages/crowdsec-client-scenarios/src/scenarios/XForwardedFor/XForwardedForChecker.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { IScenarioOptions } from '../IScenarioOptions.js';
55
import { IncomingMessage } from 'http';
66
import { IncomingHttpHeaders } from 'http2';
77
import { CheckerScenario } from '../../baseScenarios/CheckerScenario.js';
8+
import { LRUCache } from 'lru-cache';
9+
import * as crypto from 'crypto';
810

911
export 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

Comments
 (0)