diff --git a/backend/src/services/alert/index.ts b/backend/src/services/alert/index.ts index 7622cbe8..7c06e0c0 100644 --- a/backend/src/services/alert/index.ts +++ b/backend/src/services/alert/index.ts @@ -14,6 +14,7 @@ import { Alert, ApiEndpoint, ApiTrace, DataField, OpenApiSpec } from "models" import { AlertType, DataSection, + RestMethod, SpecExtension, Status, UpdateAlertType, @@ -523,4 +524,28 @@ export class AlertService { return [] } } + + static async createUnauthEndpointSenDataAlerts( + endpoints: Array<{ uuid: string, path: string, host: string, method: RestMethod }> + ) { + try { + if (!endpoints || endpoints?.length === 0) { + return [] + } + let alerts: Alert[] = [] + for (const item of endpoints) { + const description = `${item.method} ${item.path} in ${item.host} is returning sensitive data.` + const newAlert = new Alert() + newAlert.type = AlertType.UNAUTHENTICATED_ENDPOINT_SENSITIVE_DATA + newAlert.riskScore = ALERT_TYPE_TO_RISK_SCORE[AlertType.UNAUTHENTICATED_ENDPOINT_SENSITIVE_DATA] + newAlert.apiEndpointUuid = item.uuid + newAlert.description = description + alerts.push(newAlert) + } + return alerts + } catch (err) { + console.error(`Error creating alert for unauthenticated endpoints returning sensitive data: ${err}`) + return [] + } + } } diff --git a/backend/src/services/jobs/index.ts b/backend/src/services/jobs/index.ts index 52814094..34763f5e 100644 --- a/backend/src/services/jobs/index.ts +++ b/backend/src/services/jobs/index.ts @@ -11,7 +11,7 @@ import { } from "utils" import { ApiEndpoint, ApiTrace, OpenApiSpec, Alert, DataField } from "models" import { AppDataSource } from "data-source" -import { AlertType, DataType, RestMethod, SpecExtension } from "@common/enums" +import { AlertType, DataSection, DataTag, DataType, RestMethod, SpecExtension, Status } from "@common/enums" import { getPathTokens } from "@common/utils" import { AlertService } from "services/alert" import { DataFieldService } from "services/data-field" @@ -21,6 +21,7 @@ import { SpecService } from "services/spec" import { aggregateTracesDataHourlyQuery, aggregateTracesDataMinutelyQuery, + getUnauthenticatedEndpointsSensitiveData, updateUnauthenticatedEndpoints, } from "./queries" @@ -135,6 +136,9 @@ export class JobsService { try { await queryRunner.connect() await queryRunner.query(updateUnauthenticatedEndpoints) + const endpointsToAlert = await queryRunner.query(getUnauthenticatedEndpointsSensitiveData, [DataSection.RESPONSE_BODY, DataTag.PII, AlertType.UNAUTHENTICATED_ENDPOINT_SENSITIVE_DATA, Status.RESOLVED]) + const alerts = await AlertService.createUnauthEndpointSenDataAlerts(endpointsToAlert) + await queryRunner.manager.createQueryBuilder().insert().into(Alert).values(alerts).execute() } catch (err) { console.error( `Encountered error when checking for unauthenticated endpoints: ${err}`, diff --git a/backend/src/services/jobs/queries.ts b/backend/src/services/jobs/queries.ts index bede1ace..4681a003 100644 --- a/backend/src/services/jobs/queries.ts +++ b/backend/src/services/jobs/queries.ts @@ -161,3 +161,40 @@ export const updateUnauthenticatedEndpoints = ` AND "sessionMeta" ->> 'authenticationSuccessful' = 'true' ) ` + +export const getUnauthenticatedEndpointsSensitiveData = ` + With endpoints AS ( + SELECT + endpoint.uuid, + endpoint.path, + endpoint.method, + endpoint.host + FROM + "api_endpoint" "endpoint" + LEFT JOIN "data_field" "field" ON "field" ."apiEndpointUuid" = "endpoint" ."uuid" + WHERE + ( + endpoint."isAuthenticatedDetected" = FALSE + OR endpoint."isAuthenticatedUserSet" = FALSE + ) + AND field."dataSection" = $1 + AND field."dataTag" = $2 + GROUP BY + 1 + ) + SELECT + * + FROM + endpoints + WHERE + endpoints.uuid NOT IN ( + SELECT + "apiEndpointUuid" + FROM + alert + WHERE + alert."apiEndpointUuid" = endpoints.uuid + AND alert.type = $3 + AND alert.status != $4 + ) +` diff --git a/common/src/enums.ts b/common/src/enums.ts index a8b2a812..bd92d4ad 100644 --- a/common/src/enums.ts +++ b/common/src/enums.ts @@ -55,6 +55,7 @@ export enum AlertType { PATH_SENSITIVE_DATA = "Sensitive Data in Path Params", BASIC_AUTHENTICATION_DETECTED = "Basic Authentication Detected", UNSECURED_ENDPOINT_DETECTED = "Endpoint not secured by SSL", + UNAUTHENTICATED_ENDPOINT_SENSITIVE_DATA = "Unauthenticated Endpoint returning Sensitive Data", } export const VULNERABILITY_ALERT_TYPES = [ @@ -63,6 +64,7 @@ export const VULNERABILITY_ALERT_TYPES = [ AlertType.PATH_SENSITIVE_DATA, AlertType.BASIC_AUTHENTICATION_DETECTED, AlertType.UNSECURED_ENDPOINT_DETECTED, + AlertType.UNAUTHENTICATED_ENDPOINT_SENSITIVE_DATA, ] export enum AttackType { diff --git a/common/src/maps.ts b/common/src/maps.ts index 80299fad..f10896c2 100644 --- a/common/src/maps.ts +++ b/common/src/maps.ts @@ -86,6 +86,7 @@ export const ALERT_TYPE_TO_RISK_SCORE: Record = { [AlertType.PATH_SENSITIVE_DATA]: RiskScore.HIGH, [AlertType.BASIC_AUTHENTICATION_DETECTED]: RiskScore.MEDIUM, [AlertType.UNSECURED_ENDPOINT_DETECTED]: RiskScore.HIGH, + [AlertType.UNAUTHENTICATED_ENDPOINT_SENSITIVE_DATA]: RiskScore.HIGH, } export const ATTACK_TYPE_TO_RISK_SCORE: Record = { diff --git a/frontend/src/components/Alert/AlertDetail.tsx b/frontend/src/components/Alert/AlertDetail.tsx index 92ddaf6b..951001d9 100644 --- a/frontend/src/components/Alert/AlertDetail.tsx +++ b/frontend/src/components/Alert/AlertDetail.tsx @@ -24,6 +24,7 @@ import { getDateTimeString } from "utils" import { METHOD_TO_COLOR, STATUS_TO_COLOR } from "~/constants" import TraceDetail from "components/Endpoint/TraceDetail" import { getSpec } from "api/apiSpecs" +import Link from "next/link" export interface SpecDiffContext { pathPointer: string[] @@ -245,6 +246,17 @@ export const AlertDetail: React.FC = ({ setRightPanel(res.rightPanel) setLoadingSpec(false) break + case AlertType.UNAUTHENTICATED_ENDPOINT_SENSITIVE_DATA: + setLeftPanel( + + + + View Endpoint → + + + , + ) + break case AlertType.PII_DATA_DETECTED: case AlertType.QUERY_SENSITIVE_DATA: case AlertType.BASIC_AUTHENTICATION_DETECTED: