Skip to content

Commit

Permalink
Merge pull request #337 from AikidoSec/fix-needle
Browse files Browse the repository at this point in the history
Fix SSRF protection using needle
  • Loading branch information
willem-delbare authored Aug 22, 2024
2 parents e61e9f0 + fc267ac commit 4301ff1
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 2 deletions.
38 changes: 38 additions & 0 deletions library/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions library/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"mongodb": "^6.3.0",
"mysql": "^2.18.1",
"mysql2": "^3.10.0",
"needle": "^3.3.1",
"node-fetch": "^2",
"percentile": "^1.6.0",
"pg": "^8.11.3",
Expand Down
90 changes: 90 additions & 0 deletions library/sinks/HTTPRequest.needle.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import * as t from "tap";
import { Agent } from "../agent/Agent";
import { ReportingAPIForTesting } from "../agent/api/ReportingAPIForTesting";
import { Token } from "../agent/api/Token";
import { Context, runWithContext } from "../agent/Context";
import { LoggerNoop } from "../agent/logger/LoggerNoop";
import { HTTPRequest } from "./HTTPRequest";

const context: Context = {
remoteAddress: "::1",
method: "POST",
url: "http://localhost:4000",
query: {},
headers: {},
body: {
image: "http://localhost:5000/api/internal",
},
cookies: {},
routeParams: {},
source: "express",
route: "/posts/:id",
};

const redirectTestUrl =
"http://firewallssrfredirects-env-2.eba-7ifve22q.eu-north-1.elasticbeanstalk.com";

t.test("it works", async (t) => {
const agent = new Agent(
true,
new LoggerNoop(),
new ReportingAPIForTesting(),
new Token("123"),
undefined
);
agent.start([new HTTPRequest()]);

t.same(agent.getHostnames().asArray(), []);

const needle = require("needle");

await runWithContext(context, async () => {
await needle("get", "https://www.aikido.dev");
});

t.same(agent.getHostnames().asArray(), [
{ hostname: "www.aikido.dev", port: 443 },
]);
agent.getHostnames().clear();

const error = await t.rejects(
async () =>
await runWithContext(context, async () => {
await needle("get", "http://localhost:5000/api/internal");
})
);
if (error instanceof Error) {
t.same(
error.message,
"Aikido firewall has blocked a server-side request forgery: http.request(...) originating from body.image"
);
}

await runWithContext(
{
...context,
...{ body: { image: `${redirectTestUrl}/ssrf-test-domain` } },
},
async () => {
await new Promise<void>((resolve) => {
needle.request(
"get",
`${redirectTestUrl}/ssrf-test-domain`,
{},
{
// eslint-disable-next-line camelcase
follow_max: 1,
},
(error, response) => {
t.ok(error instanceof Error);
t.match(
error?.message,
/Aikido firewall has blocked a server-side request forgery/
);
resolve();
}
);
});
}
);
});
25 changes: 25 additions & 0 deletions library/sinks/http-request/getUrlFromHTTPRequestArgs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,28 @@ t.test("Do not get port 0 from request options", async (t) => {
new URL("https://localhost")
);
});

t.test("Pass port as string", async (t) => {
t.same(
getURL(
[{ protocol: "https:", hostname: "localhost", port: "4000" }],
"https"
),
new URL("https://localhost:4000")
);
t.same(
getURL(["https://localhost", { port: "4000" }], "https"),
new URL("https://localhost:4000")
);
});

t.test("Pass host instead of hostname", async (t) => {
t.same(
getURL([{ protocol: "https:", host: "localhost:4000" }], "https"),
new URL("https://localhost:4000")
);
t.same(
getURL(["https://localhost", { host: "test.dev" }], "https"),
new URL("https://test.dev")
);
});
14 changes: 12 additions & 2 deletions library/sinks/http-request/getUrlFromHTTPRequestArgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,18 @@ function getUrlFromRequestOptions(
str += "//";
if (typeof options.hostname === "string") {
str += options.hostname;
} else if (typeof options.host === "string") {
str += options.host;
}
if (typeof options.port === "number" && options.port > 0) {
str += `:${options.port}`;
if (options.port) {
if (typeof options.port === "number" && options.port > 0) {
str += `:${options.port}`;
}
if (typeof options.port === "string" && options.port.length > 0) {
str += `:${options.port}`;
}
}

if (typeof options.path === "string") {
str += options.path;
}
Expand All @@ -98,6 +106,8 @@ function mergeURLWithRequestOptions(
urlStr += "//";
if (options.hostname) {
urlStr += options.hostname;
} else if (options.host) {
urlStr += options.host;
} else {
urlStr += url.hostname;
}
Expand Down

0 comments on commit 4301ff1

Please sign in to comment.