Skip to content
This repository has been archived by the owner on Apr 29, 2022. It is now read-only.

Commit

Permalink
feat: Find insecure comparison of secrets
Browse files Browse the repository at this point in the history
  • Loading branch information
kgilpin committed Oct 12, 2021
1 parent 7015517 commit fd3f80e
Show file tree
Hide file tree
Showing 8 changed files with 23,522 additions and 28 deletions.
12 changes: 12 additions & 0 deletions src/analyzer/recordSecrets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Event } from '@appland/models';
import { emptyValue, verbose } from '../scanner/util';

export default function (secrets: Set<string>, e: Event): void {
if (emptyValue(e.returnValue.value)) {
return;
}
if (verbose()) {
console.warn(`Secret generated: ${e.returnValue.value}`);
}
secrets.add(e.returnValue.value);
}
17 changes: 17 additions & 0 deletions src/analyzer/secretsRegexes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { readFileSync } from 'fs';
import { join } from 'path';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import * as _secretsRegexes from './secretsRegexesData.json'; // import directly to include json file into the build

const regexData: { [key: string]: string | string[] } = JSON.parse(
readFileSync(join(__dirname, 'secretsRegexesData.json')).toString()
);

const REGEXES: { [key: string]: RegExp[] } = Object.keys(regexData).reduce((memo, key) => {
const value = regexData[key];
const regexes = Array.isArray(value) ? value : [value];
memo[key] = regexes.map((regex) => new RegExp(regex));
return memo;
}, {} as { [key: string]: RegExp[] });

export default REGEXES;
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"((?:A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16})",
"AKIA[0-9A-Z]{16}"
],
"BCrypt": "^\\$2[abxy]?\\$(?:0[4-9]|[12][0-9]|3[01])[$][.\\/0-9a-zA-Z]{53}$",
"Amazon MWS Auth Token": "amzn\\.mws\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
"AWS AppSync GraphQL Key": "da2-[a-z0-9]{26}",
"Facebook Access Token": "EAACEdEose0cBA[0-9A-Za-z]+",
Expand Down
2 changes: 2 additions & 0 deletions src/sampleConfig/railsSampleApp6thEd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import slowHttpServerRequest from '../scanner/slowHttpServerRequest';
import slowQuery from '../scanner/slowQuery';
import updateInGetRequest from '../scanner/updateInGetRequest';
import secretInLog from '../scanner/secretInLog';
import insecureCompare from '../scanner/insecureCompare';

const assertions: Assertion[] = [
slowHttpServerRequest.scanner(new slowHttpServerRequest.Options(0.5)),
Expand All @@ -15,6 +16,7 @@ const assertions: Assertion[] = [
leafExpected.scanner('http_client_request'),
leafExpected.scanner('sql_query'),
secretInLog.scanner(),
insecureCompare.scanner(),
updateInGetRequest.scanner(),
];

Expand Down
59 changes: 59 additions & 0 deletions src/scanner/insecureCompare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Event } from '@appland/models';
import recordSecrets from '../analyzer/recordSecrets';
import SecretsRegexes from '../analyzer/secretsRegexes';
import Assertion from '../assertion';

const BCRYPT_REGEXP = /^[$]2[abxy]?[$](?:0[4-9]|[12][0-9]|3[01])[$][./0-9a-zA-Z]{53}$/;

const secrets: Set<string> = new Set();

function stringEquals(e: Event): string | boolean | import('../types').MatchResult[] | undefined {
if (!e.parameters || !e.receiver || e.parameters!.length !== 1) {
return;
}

const args = [e.receiver!.value, e.parameters![0].value];

function isBcrypt(str: string): boolean {
return BCRYPT_REGEXP.test(str);
}

function isSecret(str: string): boolean {
return !!Object.keys(SecretsRegexes).find(
(key): boolean => !!SecretsRegexes[key].find((re: RegExp): boolean => re.test(str))
);
}

// BCrypted strings are safe to compare using equals()
if (args.every(isBcrypt)) {
return;
}
if (!args.every(isSecret)) {
return;
}

return true;
}

const scanner = function (): Assertion {
return Assertion.assert(
'insecure-compare',
'Insecure comparison of secrets',
'event',
(e: Event) => {
if (e.codeObject.labels.has('secret')) {
recordSecrets(secrets, e);
}
if (e.parameters && e.codeObject.labels.has('string.equals')) {
return stringEquals(e);
}
},
(assertion: Assertion): void => {
assertion.where = (e: Event) =>
e.codeObject.labels.has('string.equals') || e.codeObject.labels.has('secret');
assertion.description = `Insecure comparison of secrets`;
}
);
};

export default { scanner };
34 changes: 6 additions & 28 deletions src/scanner/secretInLog.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,9 @@
import { Event, ParameterObject } from '@appland/models';
import { readFileSync } from 'fs';
import { join } from 'path';
import { MatchResult } from 'src/types';
import Assertion from '../assertion';
import { verbose, emptyValue } from './util';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import * as secretsRegexes from './secretsRegexes.json'; // import directly to include json file into the build

const regexData: { [key: string]: string | string[] } = JSON.parse(
readFileSync(join(__dirname, 'secretsRegexes.json')).toString()
);
const REGEXES: { [key: string]: RegExp[] } = Object.keys(regexData).reduce((memo, key) => {
const value = regexData[key];
const regexes = Array.isArray(value) ? value : [value];
memo[key] = regexes.map((regex) => new RegExp(regex));
return memo;
}, {} as { [key: string]: RegExp[] });
import SecretsRegexes from '../analyzer/secretsRegexes';
import { emptyValue } from './util';
import recordSecrets from '../analyzer/recordSecrets';

class Match {
constructor(public regexp: RegExp | string, public value: string) {}
Expand All @@ -39,8 +27,8 @@ const findMatchingValue = (regexps: RegExp[], parameters: readonly ParameterObje
};

const findInLog = (e: Event): MatchResult[] | undefined => {
const matches: Match[] = Object.keys(REGEXES).reduce((memo, key) => {
const matches = findMatchingValue(REGEXES[key], e.parameters!);
const matches: Match[] = Object.keys(SecretsRegexes).reduce((memo, key) => {
const matches = findMatchingValue(SecretsRegexes[key], e.parameters!);
matches.forEach((match) => memo.push(match));
return memo;
}, [] as Match[]);
Expand All @@ -62,24 +50,14 @@ const findInLog = (e: Event): MatchResult[] | undefined => {
}
};

const recordSecrets = (e: Event) => {
if (emptyValue(e.returnValue.value)) {
return;
}
if (verbose()) {
console.warn(`Secret generated: ${e.returnValue.value}`);
}
secrets.add(e.returnValue.value);
};

const scanner = function (): Assertion {
return Assertion.assert(
'secret-in-log',
'Secret in log',
'event',
(e: Event) => {
if (e.codeObject.labels.has('secret')) {
recordSecrets(e);
recordSecrets(secrets, e);
}
if (e.parameters && e.codeObject.labels.has('log')) {
return findInLog(e);
Expand Down
Loading

0 comments on commit fd3f80e

Please sign in to comment.