Skip to content

Commit

Permalink
refactor(scan): extract validation into a separate class
Browse files Browse the repository at this point in the history
  • Loading branch information
derevnjuk committed Apr 28, 2022
1 parent 74b14bc commit 6d14f6d
Show file tree
Hide file tree
Showing 6 changed files with 394 additions and 72 deletions.
12 changes: 6 additions & 6 deletions packages/scan/src/ScanFactory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'reflect-metadata';
import { Scans } from './Scans';
import { ScanFactory } from './ScanFactory';
import { AttackParamLocation, Discovery, Module, TestType } from './models';
import { ScanSettings } from './ScanSettings';
import { ScanSettingsOptions } from './ScanSettings';
import {
anything,
deepEqual,
Expand Down Expand Up @@ -47,7 +47,7 @@ describe('ScanFactory', () => {

describe('createScan', () => {
it('should create a scan', async () => {
const settings: ScanSettings = {
const settings: ScanSettingsOptions = {
target: { url: 'https://example.com' },
tests: [TestType.DOM_XSS]
};
Expand All @@ -72,7 +72,7 @@ describe('ScanFactory', () => {
});

it('should create a scan with custom name', async () => {
const settings: ScanSettings = {
const settings: ScanSettingsOptions = {
name: 'my scan',
target: { url: 'https://example.com' },
tests: [TestType.DOM_XSS]
Expand All @@ -98,7 +98,7 @@ describe('ScanFactory', () => {
});

it('should generate and upload a HAR file', async () => {
const settings: ScanSettings = {
const settings: ScanSettingsOptions = {
target: { url: 'https://example.com' },
tests: [TestType.DOM_XSS]
};
Expand All @@ -124,7 +124,7 @@ describe('ScanFactory', () => {
});

it('should create a scan with unique attack locations', async () => {
const settings: ScanSettings = {
const settings: ScanSettingsOptions = {
target: { url: 'https://example.com' },
tests: [TestType.XPATHI],
attackParamLocations: [
Expand Down Expand Up @@ -197,7 +197,7 @@ describe('ScanFactory', () => {
])(
'should raise the error `$expected` when invalid config ($input) is supplied',
async ({ input, expected }) => {
const settings: ScanSettings = {
const settings: ScanSettingsOptions = {
target: { url: 'https://example.com' },
tests: [TestType.XPATHI],
...input
Expand Down
79 changes: 14 additions & 65 deletions packages/scan/src/ScanFactory.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { Scans } from './Scans';
import { Scan } from './Scan';
import {
AttackParamLocation,
Discovery,
Module,
ScanConfig,
TestType
} from './models';
import { ScanSettings } from './ScanSettings';
import { Discovery, Module, ScanConfig } from './models';
import { ScanSettings, ScanSettingsOptions } from './ScanSettings';
import { Target, TargetOptions } from './Target';
import { v4 } from 'uuid';
import { Configuration } from '@secbox/core';
Expand All @@ -20,74 +14,29 @@ export class ScanFactory {
this.scans = sdkConfig.container.resolve(Scans);
}

public async createScan(options: ScanSettings): Promise<Scan> {
const target = new Target(options.target);
const config = await this.buildScanConfig({ ...options, target });

public async createScan(
options: ScanSettings | ScanSettingsOptions
): Promise<Scan> {
const config = await this.buildScanConfig(new ScanSettings(options));
const { id } = await this.scans.createScan(config);

return new Scan({ id, scans: this.scans });
}

// TODO: consider refactoring
// eslint-disable-next-line complexity
private async buildScanConfig({
name,
tests,
target,
repeaterId,
smart = true,
poolSize = 10,
targetTimeout = 5,
slowEpTimeout = 1000,
skipStaticParams = true,
attackParamLocations = [
AttackParamLocation.BODY,
AttackParamLocation.QUERY,
AttackParamLocation.FRAGMENT
]
smart,
poolSize,
targetTimeout,
slowEpTimeout,
skipStaticParams,
attackParamLocations
}: ScanSettings): Promise<ScanConfig> {
const fileId = await this.createAndUploadHar(target);

if (tests.some((x: TestType) => !Object.values(TestType).includes(x))) {
throw new Error('Unknown test type supplied.');
}

const uniqueTestTypes = new Set<TestType>(tests);

if (uniqueTestTypes.size < 1) {
throw new Error('Please provide a least one test.');
}

if (
attackParamLocations.some(
(x: AttackParamLocation) =>
!Object.values(AttackParamLocation).includes(x)
)
) {
throw new Error('Unknown attack param location supplied.');
}

const uniqueAttackParamLocations = new Set<AttackParamLocation>(
attackParamLocations
);

if (uniqueAttackParamLocations.size < 1) {
throw new Error('Please provide a least one attack parameter location.');
}

if (isNaN(poolSize) || poolSize > 50 || poolSize < 1) {
throw new Error('Invalid pool size.');
}

if (isNaN(slowEpTimeout) || slowEpTimeout < 100) {
throw new Error('Invalid slow entry point timeout.');
}

if (isNaN(targetTimeout) || targetTimeout > 120 || targetTimeout <= 0) {
throw new Error('Invalid target connection timeout.');
}

return {
fileId,
smart,
Expand All @@ -98,8 +47,8 @@ export class ScanFactory {
module: Module.DAST,
discoveryTypes: [Discovery.ARCHIVE],
name: name || `${target.method ?? 'GET'} ${target.url}`,
attackParamLocations: [...uniqueAttackParamLocations],
tests: [...uniqueTestTypes],
attackParamLocations: [...attackParamLocations],
tests: [...tests],
repeaters: repeaterId ? [repeaterId] : undefined
};
}
Expand Down
169 changes: 168 additions & 1 deletion packages/scan/src/ScanSettings.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { AttackParamLocation, TestType } from './models';
import { Target, TargetOptions } from './Target';
import { checkBoundaries } from './utils';

export interface ScanSettings {
export interface ScanSettingsOptions {
// The list of tests to be performed against the target application
tests: TestType[];
// The target that will be attacked
Expand All @@ -24,3 +25,169 @@ export interface ScanSettings {
// Defines which part of the request to attack
attackParamLocations?: AttackParamLocation[];
}

export class ScanSettings implements ScanSettingsOptions {
private _name!: string;

get name(): string {
return this._name;
}

private set name(value: string) {
this._name = value;
}

private _repeaterId?: string;

get repeaterId() {
return this._repeaterId;
}

private set repeaterId(value) {
this._repeaterId = value;
}

private _skipStaticParams!: boolean;

get skipStaticParams(): boolean {
return this._skipStaticParams;
}

private set skipStaticParams(value: boolean) {
this._skipStaticParams = !!value;
}

private _smart!: boolean;

get smart(): boolean {
return this._smart;
}

set smart(value: boolean) {
this._smart = !!value;
}

private _target!: Target;

get target(): Target {
return this._target;
}

private set target(value: Target | TargetOptions) {
this._target = new Target(value);
}

private _targetTimeout?: number;

get targetTimeout() {
return this._targetTimeout;
}

private set targetTimeout(value) {
if (!checkBoundaries(value, { max: 120, min: 0, exclusiveMin: true })) {
throw new Error('Invalid target connection timeout.');
}
this._targetTimeout = value;
}

private _slowEpTimeout?: number;

get slowEpTimeout() {
return this._slowEpTimeout;
}

private set slowEpTimeout(value) {
if (!checkBoundaries(value, { min: 100 })) {
throw new Error('Invalid slow entry point timeout.');
}

this._slowEpTimeout = value;
}

private _poolSize!: number;

get poolSize(): number {
return this._poolSize;
}

private set poolSize(value: number) {
if (!checkBoundaries(value, { min: 1, max: 50 })) {
throw new Error('Invalid pool size.');
}

this._poolSize = value;
}

private _tests!: TestType[];

get tests(): TestType[] {
return this._tests;
}

private set tests(value: TestType[]) {
if (value.some((x: TestType) => !Object.values(TestType).includes(x))) {
throw new Error('Unknown test type supplied.');
}

const uniqueTestTypes = new Set<TestType>(value);

if (uniqueTestTypes.size < 1) {
throw new Error('Please provide a least one test.');
}

this._tests = [...uniqueTestTypes];
}

private _attackParamLocations!: AttackParamLocation[];

get attackParamLocations() {
return this._attackParamLocations;
}

private set attackParamLocations(value: AttackParamLocation[]) {
if (
value.some(
(x: AttackParamLocation) =>
!Object.values(AttackParamLocation).includes(x)
)
) {
throw new Error('Unknown attack param location supplied.');
}

const uniqueAttackParamLocations = new Set<AttackParamLocation>(value);

if (uniqueAttackParamLocations.size < 1) {
throw new Error('Please provide a least one attack parameter location.');
}

this._attackParamLocations = [...uniqueAttackParamLocations];
}

constructor({
name,
tests,
target,
repeaterId,
smart = true,
poolSize = 10,
targetTimeout = 5,
slowEpTimeout = 1000,
skipStaticParams = true,
attackParamLocations = [
AttackParamLocation.BODY,
AttackParamLocation.QUERY,
AttackParamLocation.FRAGMENT
]
}: ScanSettingsOptions) {
this.attackParamLocations = attackParamLocations;
this.target = target;
this.name = name || `${this.target.method} ${this.target.url}`;
this.poolSize = poolSize;
this.repeaterId = repeaterId;
this.skipStaticParams = skipStaticParams;
this.slowEpTimeout = slowEpTimeout;
this.smart = smart;
this.targetTimeout = targetTimeout;
this.tests = tests;
}
}
1 change: 1 addition & 0 deletions packages/scan/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './number';
export * from './escape';

0 comments on commit 6d14f6d

Please sign in to comment.