Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions packages/backend/src/checks/aspnet-debugging/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { createMockRequest, createMockResponse, runCheck } from "engine";
import { describe, expect, it } from "vitest";

import aspNetDebugCheck from "./index";

describe("ASP.NET debugging check", () => {
it("detects debug true marker", async () => {
const request = createMockRequest({
id: "req-1",
host: "example.com",
method: "GET",
path: "/web.config",
});

const response = createMockResponse({
id: "res-1",
code: 200,
headers: { "content-type": ["text/xml"] },
body: '<configuration><system.web><compilation debug="true" /></system.web></configuration>',
});

const executionHistory = await runCheck(aspNetDebugCheck, [
{ request, response },
]);

const findings =
executionHistory[0]?.steps[executionHistory[0].steps.length - 1]
?.findings ?? [];
expect(findings).toHaveLength(1);
expect(findings[0]?.name).toBe("ASP.NET debugging enabled");
});

it("ignores responses without marker", async () => {
const request = createMockRequest({
id: "req-2",
host: "example.com",
method: "GET",
path: "/web.config",
});

const response = createMockResponse({
id: "res-2",
code: 200,
headers: { "content-type": ["text/xml"] },
body: '<configuration><system.web><compilation debug="false" /></system.web></configuration>',
});

const executionHistory = await runCheck(aspNetDebugCheck, [
{ request, response },
]);

const findings =
executionHistory[0]?.steps[executionHistory[0].steps.length - 1]
?.findings ?? [];
expect(findings).toHaveLength(0);
});
});
72 changes: 72 additions & 0 deletions packages/backend/src/checks/aspnet-debugging/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { defineCheck, done, Severity } from "engine";

import { Tags } from "../../types";
import { keyStrategy } from "../../utils";

const DEBUG_MARKERS = [
'<compilation debug="true"',
'<compilation debug = "true"',
'<compilation debug= "true"',
];

const bodyIndicatesDebugging = (body: string): boolean => {
const lower = body.toLowerCase();
return DEBUG_MARKERS.some((marker) => lower.includes(marker));
};

export default defineCheck(({ step }) => {
step("detectAspNetDebug", (state, context) => {
const { response, request } = context.target;

if (response === undefined) {
return done({ state });
}

const body = response.getBody()?.toText() ?? "";
if (body.length === 0) {
return done({ state });
}

if (!bodyIndicatesDebugging(body)) {
return done({ state });
}

const description = [
'The response appears to expose ASP.NET debugging configuration (`<compilation debug="true">`).',
"",
"Running in debug mode disables important optimisations and can disclose stack traces or sensitive data.",
'**Recommendation:** Set `debug="false"` in Web.config for production deployments.',
].join("\n");

return done({
state,
findings: [
{
name: "ASP.NET debugging enabled",
description,
severity: Severity.MEDIUM,
correlation: {
requestID: request.getId(),
locations: [],
},
},
],
});
});

return {
metadata: {
id: "aspnet-debugging",
name: "ASP.NET debugging enabled",
description:
'Detects ASP.NET pages that indicate `debug="true"` in the compilation configuration.',
type: "passive",
tags: [Tags.DEBUG, Tags.INFORMATION_DISCLOSURE],
severities: [Severity.MEDIUM],
aggressivity: { minRequests: 0, maxRequests: 0 },
},
initState: () => ({}),
dedupeKey: keyStrategy().withHost().withPort().withPath().build(),
when: () => true,
};
});
3 changes: 3 additions & 0 deletions packages/backend/src/checks/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import antiClickjackingScan from "./anti-clickjacking";
import applicationErrorsScan from "./application-errors";
import aspNetDebuggingScan from "./aspnet-debugging";
import bigRedirectsScan from "./big-redirects";
import commandInjectionScan from "./command-injection";
import corsMisconfigScan from "./cors-misconfig";
Expand Down Expand Up @@ -71,6 +72,7 @@ export const Checks = {
SQL_STATEMENT_IN_PARAMS: "sql-statement-in-params",
SSN_DISCLOSURE: "ssn-disclosure",
SUSPECT_TRANSFORM: "suspect-transform",
ASPNET_DEBUGGING: "aspnet-debugging",
// MYSQL_TIME_BASED_SQLI: "mysql-time-based-sqli" - TODO: fix false positives
} as const;

Expand Down Expand Up @@ -111,5 +113,6 @@ export const checks = [
sqlStatementInParams,
ssnDisclosureScan,
suspectTransformScan,
aspNetDebuggingScan,
// mysqlTimeBased,
] as const;
8 changes: 8 additions & 0 deletions packages/backend/src/stores/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ export class ConfigStore {
checkID: Checks.MISSING_CONTENT_TYPE,
enabled: true,
},
{
checkID: Checks.ASPNET_DEBUGGING,
enabled: false,
},
],
},
{
Expand Down Expand Up @@ -371,6 +375,10 @@ export class ConfigStore {
checkID: Checks.MISSING_CONTENT_TYPE,
enabled: true,
},
{
checkID: Checks.ASPNET_DEBUGGING,
enabled: true,
},
],
},
{
Expand Down