Skip to content

Commit

Permalink
feat(runner): raise an error with detailed info about a found issue (#…
Browse files Browse the repository at this point in the history
…106)

closes #105
  • Loading branch information
derevnjuk authored May 31, 2022
1 parent 9b57c45 commit 1c14240
Show file tree
Hide file tree
Showing 18 changed files with 306 additions and 78 deletions.
48 changes: 1 addition & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,53 +81,7 @@ $ yarn add @sec-tester/runner \

### Usage examples

Here is an example to check your own application for XSS vulnerabilities:

```ts
import { SecRunner, SecScan } from '@sec-tester/runner';
import { Severity, TestType } from '@sec-tester/scan';

describe('/api', () => {
let runner!: SecRunner;
let scan!: SecScan;

beforeEach(async () => {
runner = new SecRunner({ hostname: 'app.neuralegion.com' });

await runner.init();

scan = runner
.createScan({ tests: [TestType.XSS] })
.threshold(Severity.MEDIUM) // i. e. ignore LOW severity issues
.timeout(300000); // i. e. fail if last longer than 5 minutes
});

afterEach(async () => {
await runner.clear();
});

describe('/orders', () => {
it('should not have persistent xss', async () => {
await scan.run({
method: 'POST',
url: 'https://localhost:8000/api/orders',
body: { subject: 'Test', body: "<script>alert('xss')</script>" }
});
});

it('should not have reflective xss', async () => {
await scan.run({
url: 'https://localhost:8000/api/orders',
query: {
q: `<script>alert('xss')</script>`
}
});
});
});
});
```

Full documentation can be found in [**runner**](https://github.com/NeuraLegion/sec-tester-js/tree/master/packages/runner).
Full configuration & usage examples can be found in our [demo project](https://github.com/NeuraLegion/sec-tester-js-demo).

## Documentation & Help

Expand Down
44 changes: 43 additions & 1 deletion packages/reporter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ npm i -s @sec-tester/reporter

## Usage

The package provides only one implementation of the `Reporter` that lets to get results to stdout, i.e. `StdReporter`:
The package provides an implementation of the `Reporter` that lets to get results to stdout, i.e. `StdReporter`:

```ts
import { Reporter, StdReporter } from '@sec-tester/reporter';
Expand All @@ -36,6 +36,48 @@ await reporter.report(scan);

</details>

In addition, the package exposes a `PlainTextFormatter` that implements a `Formatter` interface:

```ts
import { Formatter, PlainTextFormatter } from '@sec-tester/reporter';

const formatter: Formatter = new PlainTextFormatter();
```

To convert an issue into text, you just need to call the `format` method:

```ts
formatter.format(issue);
```

<details>
<summary>Sample output</summary>

```
Issue in Bright UI: https://app.neuralegion.com/scans/djoqtSDRJYaR6sH8pfYpDX/issues/8iacauN1FH9vFvDCLoo42v
Name: Missing Strict-Transport-Security Header
Severity: Low
Remediation:
Make sure to proprely set and configure headers on your application - missing strict-transport-security header
Details:
The engine detected a missing strict-transport-security header. Headers are used to outline communication and
improve security of application.
Extra Details:
● Missing Strict-Transport-Security Header
The engine detected a missing Strict-Transport-Security header, which might cause data to be sent insecurely from the client to the server.
Remedy:
- Make sure to set this header to one of the following options:
1. Strict-Transport-Security: max-age=<expire-time>
2. Strict-Transport-Security: max-age=<expire-time>; includeSubDomains
3. Strict-Transport-Security: max-age=<expire-time>; preload
Resources:
- https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#hsts
Issues found on the following URLs:
- [GET] https://qa.brokencrystals.com/
```

</details>

## License

Copyright © 2022 [Bright Security](https://brightsec.com/).
Expand Down
63 changes: 63 additions & 0 deletions packages/reporter/src/__fixtures__/issues.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { HttpMethod, Severity } from '@sec-tester/scan';

export const issueWithoutResourcesText = `Issue in Bright UI: http://app.neuralegion.com/scans/pDzxcEXQC8df1fcz1QwPf9/issues/pDzxcEXQC8df1fcz1QwPf9
Name: Database connection crashed
Severity: Medium
Remediation:
The best way to protect against those kind of issues is making sure the Database resources are sufficient
Details:
Cross-site request forgery is a type of malicious website exploit.`;
export const issueWithoutResources = {
id: 'pDzxcEXQC8df1fcz1QwPf9',
order: 1,
details: 'Cross-site request forgery is a type of malicious website exploit.',
name: 'Database connection crashed',
severity: Severity.MEDIUM,
protocol: 'http',
remedy:
'The best way to protect against those kind of issues is making sure the Database resources are sufficient',
cvss: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L',
time: new Date(),
originalRequest: {
method: HttpMethod.GET,
url: 'https://brokencrystals.com/'
},
request: {
method: HttpMethod.GET,
url: 'https://brokencrystals.com/'
},
link: 'http://app.neuralegion.com/scans/pDzxcEXQC8df1fcz1QwPf9/issues/pDzxcEXQC8df1fcz1QwPf9'
};

export const fullyDescribedIssueText = `${issueWithoutResourcesText}
Extra Details:
● Missing Strict-Transport-Security Header
\tThe engine detected a missing Strict-Transport-Security header, which might cause data to be sent insecurely from the client to the server.
\tLinks:
\t● https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#hsts
References:
● https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#hsts`;
export const fullyDescribedIssue = {
...issueWithoutResources,
comments: [
{
headline: 'Missing Strict-Transport-Security Header',
text: 'The engine detected a missing Strict-Transport-Security header, which might cause data to be sent insecurely from the client to the server.',
links: [
'https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#hsts'
]
}
],
resources: [
'https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#hsts'
]
};
export const issueWithoutExtraInfoText = `${issueWithoutResourcesText}
References:
● https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#hsts`;
export const issueWithoutExtraInfo = {
...issueWithoutResources,
resources: [
'https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#hsts'
]
};
44 changes: 44 additions & 0 deletions packages/reporter/src/formatters/PlainTextFormatter.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { PlainTextFormatter } from './PlainTextFormatter';
import {
fullyDescribedIssue,
fullyDescribedIssueText,
issueWithoutExtraInfo,
issueWithoutExtraInfoText,
issueWithoutResources,
issueWithoutResourcesText
} from '../__fixtures__/issues';
import { Issue } from '@sec-tester/scan';

describe('PlainTextFormatter', () => {
let formatter!: PlainTextFormatter;

beforeEach(() => {
formatter = new PlainTextFormatter();
});

describe('format', () => {
it.each([
{
input: fullyDescribedIssue,
expected: fullyDescribedIssueText,
title: 'fully described issue'
},
{
input: issueWithoutExtraInfo,
expected: issueWithoutExtraInfoText,
title: 'issue without extra info'
},
{
input: issueWithoutResources,
expected: issueWithoutResourcesText,
title: 'issue without resources'
}
])('should format $title', ({ input, expected }) => {
// act
const result = formatter.format(input as Issue);

// assert
expect(result).toEqual(expected);
});
});
});
84 changes: 84 additions & 0 deletions packages/reporter/src/formatters/PlainTextFormatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Formatter } from '../lib';
import { Comment, Issue } from '@sec-tester/scan';
import { format } from 'util';

export class PlainTextFormatter implements Formatter {
private readonly BULLET_POINT = '●';
private readonly NEW_LINE = '\n';
private readonly TABULATION = '\t';

public format(issue: Issue): string {
const {
link,
name,
severity,
remedy,
details,
comments = [],
resources = []
} = issue;
const template = this.generateTemplate({
extraInfo: comments.length > 0,
references: resources.length > 0
});

const message = format(
template,
link,
name,
severity,
remedy,
details,
this.formatList(comments, comment => this.formatExtraInfo(comment)),
this.formatList(resources)
);

return message.trim();
}

private generateTemplate(options: {
extraInfo: boolean;
references: boolean;
}): string {
return `
Issue in Bright UI: %s
Name: %s
Severity: %s
Remediation:
%s
Details:
%s${options.extraInfo ? `\nExtra Details:\n%s` : ''}${
options.references ? `\nReferences:\n%s` : ''
}`.trim();
}

private formatExtraInfo({ headline, text = '', links = [] }: Comment) {
const footer = links.length
? this.combineList(['Links:', this.formatList(links)])
: '';
const blocks = [text, footer].map(x => this.indent(x));
const document = this.combineList(blocks);

return this.combineList([headline, document]);
}

private indent(x: string, length: number = 1) {
const lines = x.split(this.NEW_LINE);

return this.combineList(
lines.map(line => `${this.TABULATION.repeat(length)}${line}`)
);
}

private formatList<T>(list: T[], map?: (x: T) => string): string {
const items = list.map(
x => `${this.BULLET_POINT} ${typeof map == 'function' ? map(x) : x}`
);

return this.combineList(items);
}

private combineList(list: string[], sep?: string): string {
return list.join(sep ?? this.NEW_LINE);
}
}
1 change: 1 addition & 0 deletions packages/reporter/src/formatters/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './PlainTextFormatter';
3 changes: 2 additions & 1 deletion packages/reporter/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { Reporter } from './lib';
export { PlainTextFormatter } from './formatters';
export { Reporter, Formatter } from './lib';
export { StdReporter } from './std';
7 changes: 7 additions & 0 deletions packages/reporter/src/lib/Formatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Issue } from '@sec-tester/scan';

export interface Formatter {
format(issue: Issue): string;
}

export const Formatter: unique symbol = Symbol('Formatter');
3 changes: 2 additions & 1 deletion packages/reporter/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { Reporter } from './Reporter';
export * from './Reporter';
export * from './Formatter';
9 changes: 9 additions & 0 deletions packages/runner/src/lib/IssueFound.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Formatter } from '@sec-tester/reporter';
import { SecTesterError } from '@sec-tester/core';
import { Issue } from '@sec-tester/scan';

export class IssueFound extends SecTesterError {
constructor(public readonly issue: Issue, formatter: Formatter) {
super(`Target is vulnerable\n\n${formatter.format(issue)}`);
}
}
8 changes: 4 additions & 4 deletions packages/runner/src/lib/SecRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
RepeaterFactory,
RepeatersManager
} from '@sec-tester/repeater';
import { Reporter, StdReporter } from '@sec-tester/reporter';
import { ScanFactory } from '@sec-tester/scan';
import { Formatter, PlainTextFormatter } from '@sec-tester/reporter';

export class SecRunner {
private readonly configuration: Configuration;
Expand Down Expand Up @@ -64,15 +64,15 @@ export class SecRunner {
repeaterId: this.repeater.repeaterId
},
this.configuration.container.resolve<ScanFactory>(ScanFactory),
this.configuration.container.resolve<Reporter>(Reporter)
this.configuration.container.resolve<Formatter>(Formatter)
);
}

private async initConfiguration(configuration: Configuration): Promise<void> {
await configuration.loadCredentials();

configuration.container.register(Reporter, {
useClass: StdReporter
configuration.container.register(Formatter, {
useClass: PlainTextFormatter
});
}
}
Loading

0 comments on commit 1c14240

Please sign in to comment.