Skip to content

Commit

Permalink
Merge branch 'main' into bot-blocking
Browse files Browse the repository at this point in the history
  • Loading branch information
timokoessler committed Dec 18, 2024
2 parents 5e053cd + a892b72 commit 9837642
Show file tree
Hide file tree
Showing 18 changed files with 1,815 additions and 154 deletions.
29 changes: 6 additions & 23 deletions library/agent/ServiceConfig.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { BlockList, isIPv4, isIPv6 } from "net";
import { IPMatcher } from "../helpers/ip-matcher/IPMatcher";
import { LimitedContext, matchEndpoints } from "../helpers/matchEndpoints";
import { Endpoint } from "./Config";
import { addIPAddressOrRangeToBlocklist } from "../helpers/addIPAddressOrRangeToBlocklist";
import { Blocklist as BlocklistType } from "./api/fetchBlockedLists";

export class ServiceConfig {
private blockedUserIds: Map<string, string> = new Map();
private allowedIPAddresses: Map<string, string> = new Map();
private nonGraphQLEndpoints: Endpoint[] = [];
private graphqlFields: Endpoint[] = [];
private blockedIPAddresses: { blocklist: BlockList; description: string }[] =
private blockedIPAddresses: { blocklist: IPMatcher; description: string }[] =
[];
private blockedUserAgentRegex: RegExp | undefined;

Expand Down Expand Up @@ -86,20 +85,9 @@ export class ServiceConfig {
isIPAddressBlocked(
ip: string
): { blocked: true; reason: string } | { blocked: false } {
let blocklist: { blocklist: BlockList; description: string } | undefined =
undefined;

if (isIPv4(ip)) {
blocklist = this.blockedIPAddresses.find((blocklist) =>
blocklist.blocklist.check(ip, "ipv4")
);
}

if (isIPv6(ip)) {
blocklist = this.blockedIPAddresses.find((blocklist) =>
blocklist.blocklist.check(ip, "ipv6")
);
}
const blocklist = this.blockedIPAddresses.find((blocklist) =>
blocklist.blocklist.has(ip)
);

if (blocklist) {
return { blocked: true, reason: blocklist.description };
Expand All @@ -112,13 +100,8 @@ export class ServiceConfig {
this.blockedIPAddresses = [];

for (const source of blockedIPAddresses) {
const blocklist = new BlockList();
for (const ip of source.ips) {
addIPAddressOrRangeToBlocklist(ip, blocklist);
}

this.blockedIPAddresses.push({
blocklist,
blocklist: new IPMatcher(source.ips),
description: source.description,
});
}
Expand Down
39 changes: 0 additions & 39 deletions library/helpers/addIPAddressOrRangeToBlocklist.test.ts

This file was deleted.

56 changes: 0 additions & 56 deletions library/helpers/addIPAddressOrRangeToBlocklist.ts

This file was deleted.

21 changes: 21 additions & 0 deletions library/helpers/attackPath.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import * as t from "tap";
import { getPathsToPayload as get } from "./attackPath";

t.test("it throws error if max matches is less than 1", async (t) => {
t.throws(() => get("payload", {}, 0));
t.throws(() => get("payload", {}, -1));
});

t.test("it gets paths to payload", async (t) => {
const testObj1 = {
a: {
Expand Down Expand Up @@ -87,3 +92,19 @@ t.test("respects max depth and array length", async (t) => {
t.same(get("100", testArr), [".[100]"]);
t.same(get("101", testArr), []);
});

t.test("first item in array", async (t) => {
t.same(get("id = 1", ["id = 1"]), [".[0]"]);
});

t.test("it checks max matches when iterating over object props", async (t) => {
const testObj = {
a: ["test"],
b: ["test"],
c: ["test"],
};

t.same(get("test", testObj, 1), [".a.[0]"]);
t.same(get("test", testObj, 2), [".a.[0]", ".b.[0]"]);
t.same(get("test", testObj, 3), [".a.[0]", ".b.[0]", ".c.[0]"]);
});
62 changes: 41 additions & 21 deletions library/helpers/attackPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,64 +37,84 @@ export function buildPathToPayload(pathToPayload: PathPart[]): string {
}, "");
}

class Matches {
private readonly matches: string[] = [];

constructor(private readonly max: number) {
if (max < 1) {
throw new Error("Max must be greater than 0");
}
}

add(path: PathPart[]) {
this.matches.push(buildPathToPayload(path));
}

getMatches() {
return this.matches;
}

found() {
return this.matches.length >= this.max;
}
}

export function getPathsToPayload(
attackPayload: string,
obj: unknown,
matchCount = DEFAULT_MATCH_COUNT
): string[] {
const matches: string[] = [];

const matches = new Matches(matchCount);
const attackPayloadLowercase = attackPayload.toLowerCase();

const traverse = (value: unknown, path: PathPart[] = [], depth = 0) => {
if (matches.length >= matchCount) {
return;
}

if (depth > MAX_DEPTH) {
if (matches.found() || depth > MAX_DEPTH) {
return;
}

// Handle strings
if (typeof value === "string") {
if (value.toLowerCase() === attackPayloadLowercase) {
matches.push(buildPathToPayload(path));
matches.add(path);
return;
}

const jwt = tryDecodeAsJWT(value);
if (jwt.jwt) {
traverse(jwt.object, path.concat({ type: "jwt" }), depth + 1);
}

return;
}

if (Array.isArray(value)) {
// Handle arrays
if (
value.length > 1 &&
value.length < MAX_ARRAY_LENGTH &&
value.join().toLowerCase() === attackPayloadLowercase
) {
matches.add(path);
return;
}

for (const [index, item] of value.entries()) {
if (index > MAX_ARRAY_LENGTH) {
if (matches.found() || index > MAX_ARRAY_LENGTH) {
break;
}
traverse(item, path.concat({ type: "array", index }), depth);
}

if (value.join().toLowerCase() === attackPayloadLowercase) {
matches.push(buildPathToPayload(path));
traverse(item, path.concat({ type: "array", index }), depth);
}

return;
}

if (isPlainObject(value)) {
// Handle objects
for (const key in value) {
if (matches.found()) {
break;
}

traverse(value[key], path.concat({ type: "object", key }), depth + 1);
}
}
};

traverse(obj);

return matches;
return matches.getMatches();
}
29 changes: 29 additions & 0 deletions library/helpers/ip-matcher/Address.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as t from "tap";
import { Address } from "./Address";

t.test("it works", async (t) => {
t.same(new Address("1.2.3.5").isIPv4(), true);
t.same(new Address("::1").isIPv4(), false);
t.same(new Address("::1").isIPv6(), true);

t.same(new Address("1.2.3.4").compare(new Address("1.2.3.5")), -1);
t.same(new Address("1.2.3.4").compare(new Address("1.2.3.4")), 0);
t.same(new Address("1.2.3.4").compare(new Address("1.2.3.4").duplicate()), 0);

t.same(new Address("1.2.3.4").bytes(), [1, 2, 3, 4]);
t.same(new Address().bytes(), []);
t.same(new Address("1.2.3.4").setBytes([3]).bytes(), []);

t.same(new Address("1.2.3.4").equals(new Address("1.2.3.4")), true);
t.same(new Address("1.2.3.4").equals(new Address("1.2.3.5")), false);

t.same(new Address().compare(new Address()), null);
t.same(new Address("1.2.3.4").compare(new Address()), null);
t.ok(new Address().applySubnetMask(0));

t.same(new Address().increase(0).bytes(), []);
t.same(new Address().bytes(), []);

const a = new Address("3.4.5.6");
t.same(a.compare(a), 0);
});
Loading

0 comments on commit 9837642

Please sign in to comment.