Skip to content

Commit

Permalink
Merge pull request #54 from AikidoSec/patch-body-headers
Browse files Browse the repository at this point in the history
Send body and headers
  • Loading branch information
hansott authored Feb 28, 2024
2 parents eefb7e8 + 499178e commit 063feff
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 29 deletions.
6 changes: 6 additions & 0 deletions library/src/agent/Agent.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
/* eslint-disable max-lines-per-function */
import { hostname, platform, release } from "node:os";
import { convertRequestBodyToString } from "../helpers/convertRequestBodyToString";
import { ip } from "../helpers/ipAddress";
import { isPlainObject } from "../helpers/isPlainObject";
import { filterEmptyRequestHeaders } from "../helpers/filterEmptyRequestHeaders";
import { API } from "./api/API";
import { AgentInfo, Kind, Stats } from "./api/Event";
import { Token } from "./api/Token";
Expand Down Expand Up @@ -134,6 +138,8 @@ export class Agent {
typeof request.headers["user-agent"] === "string"
? request.headers["user-agent"]
: undefined,
body: convertRequestBodyToString(request.body),
headers: filterEmptyRequestHeaders(request.headers),
},
agent: this.getAgentInfo(),
})
Expand Down
2 changes: 2 additions & 0 deletions library/src/agent/api/Event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ type DetectedAttack = {
ipAddress: string | undefined;
userAgent: string | undefined;
url: string | undefined;
headers: Record<string, string | string[]>;
body: string | undefined;
};
attack: {
kind: Kind;
Expand Down
26 changes: 26 additions & 0 deletions library/src/helpers/attackPath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export type PathPart =
| { type: "jwt" }
| { type: "object"; key: string }
| { type: "array"; index: number };

export function buildPathToPayload(pathToPayload: PathPart[]): string {
if (pathToPayload.length === 0) {
return ".";
}

return pathToPayload.reduce((acc, part) => {
if (part.type === "jwt") {
return `${acc}<jwt>`;
}

if (part.type === "object") {
return `${acc}.${part.key}`;
}

if (part.type === "array") {
return `${acc}.[${part.index}]`;
}

return acc;
}, "");
}
28 changes: 28 additions & 0 deletions library/src/helpers/convertRequestBodyToString.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as t from "tap";
import { convertRequestBodyToString } from "./convertRequestBodyToString";

t.test("it converts object body to JSON string", async (t) => {
t.same(
convertRequestBodyToString({ a: 1, b: 2, c: 3 }),
JSON.stringify({ a: 1, b: 2, c: 3 }, null, 2)
);
});

t.test("it converts string body to string", async (t) => {
t.same(convertRequestBodyToString("hello"), "hello");
});

t.test("it returns undefined for non-plain object", async (t) => {
t.same(convertRequestBodyToString(new Date()), undefined);
});

t.test("it limits length to maxLength", async (t) => {
t.same(convertRequestBodyToString("a".repeat(16385)), "a".repeat(16384));
});

t.test("it returns undefined for circular object", async (t) => {
const obj: Record<string, unknown> = {};
obj.a = obj;

t.same(convertRequestBodyToString(obj), undefined);
});
22 changes: 22 additions & 0 deletions library/src/helpers/convertRequestBodyToString.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { isPlainObject } from "./isPlainObject";

export function convertRequestBodyToString(
body: unknown,
maxLength = 16384
): string | undefined {
if (typeof body === "string") {
return body.length > maxLength ? body.slice(0, maxLength) : body;
}

if (isPlainObject(body)) {
try {
const serialized = JSON.stringify(body, null, 2);

return convertRequestBodyToString(serialized, maxLength);
} catch {
return undefined;
}
}

return undefined;
}
17 changes: 17 additions & 0 deletions library/src/helpers/filterEmptyRequestHeaders.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as t from "tap";
import { filterEmptyRequestHeaders } from "./filterEmptyRequestHeaders";

t.test("it filters empty headers", async (t) => {
t.same(
filterEmptyRequestHeaders({
string: "value",
array: ["a", "b"],
emptyArray: [],
undefined: undefined,
}),
{
string: "value",
array: ["a", "b"],
}
);
});
20 changes: 20 additions & 0 deletions library/src/helpers/filterEmptyRequestHeaders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Context } from "../agent/Context";

export function filterEmptyRequestHeaders(
headers: Context["headers"]
): Record<string, string | string[]> {
const normalized: Record<string, string | string[]> = {};
for (const key in headers) {
const value = headers[key];

if (Array.isArray(value) && value.length > 0) {
normalized[key] = value;
}

if (typeof value === "string" && value.length > 0) {
normalized[key] = value;
}
}

return normalized;
}
Original file line number Diff line number Diff line change
@@ -1,35 +1,9 @@
import { isDeepStrictEqual } from "node:util";
import { isPlainObject } from "../../helpers/isPlainObject";
import { tryDecodeAsJWT } from "../../helpers/tryDecodeAsJWT";
import { Context } from "../../agent/Context";
import { Source } from "../../agent/Source";

type PathPart =
| { type: "jwt" }
| { type: "object"; key: string }
| { type: "array"; index: number };

function buildPathToPayload(pathToPayload: PathPart[]): string {
if (pathToPayload.length === 0) {
return ".";
}

return pathToPayload.reduce((acc, part) => {
if (part.type === "jwt") {
return `${acc}<jwt>`;
}

if (part.type === "object") {
return `${acc}.${part.key}`;
}

if (part.type === "array") {
return `${acc}.[${part.index}]`;
}

return acc;
}, "");
}
import { buildPathToPayload, PathPart } from "../../helpers/attackPath";
import { isPlainObject } from "../../helpers/isPlainObject";
import { tryDecodeAsJWT } from "../../helpers/tryDecodeAsJWT";

function matchFilterPartInUser(
userInput: unknown,
Expand Down

0 comments on commit 063feff

Please sign in to comment.