thuban 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package.json +63 -0
- package/dist/packages/crucible/rules/code-scanner-core.json +129 -0
- package/dist/packages/crucible/rules/command-injection.json +411 -0
- package/dist/packages/crucible/rules/crypto-misuse.json +35 -0
- package/dist/packages/crucible/rules/deserialization.json +152 -0
- package/dist/packages/crucible/rules/env-injection.json +46 -0
- package/dist/packages/crucible/rules/eval-abuse.json +104 -0
- package/dist/packages/crucible/rules/file-upload.json +46 -0
- package/dist/packages/crucible/rules/hallucination.json +22 -0
- package/dist/packages/crucible/rules/hardcoded-secret.json +397 -0
- package/dist/packages/crucible/rules/hardcoded-url.json +13 -0
- package/dist/packages/crucible/rules/insecure-crypto.json +302 -0
- package/dist/packages/crucible/rules/integer-overflow.json +35 -0
- package/dist/packages/crucible/rules/log-injection.json +33 -0
- package/dist/packages/crucible/rules/mass-assignment.json +112 -0
- package/dist/packages/crucible/rules/open-redirect.json +196 -0
- package/dist/packages/crucible/rules/path-traversal.json +422 -0
- package/dist/packages/crucible/rules/prototype-pollution.json +22 -0
- package/dist/packages/crucible/rules/sql-injection.json +597 -0
- package/dist/packages/crucible/rules/ssrf.json +557 -0
- package/dist/packages/crucible/rules/timing-attack.json +55 -0
- package/dist/packages/crucible/rules/unsafe-block.json +24 -0
- package/dist/packages/crucible/rules/xss.json +641 -0
- package/dist/packages/crucible/rules/xxe.json +66 -0
- package/dist/packages/crucible/rules/yaml-deserialization.json +22 -0
- package/dist/packages/crucible/rules/yaml-injection.json +22 -0
- package/dist/packages/scanner/executive-report.js +1 -1
- package/dist/packages/scanner/hallucination-detector.js +1 -1
- package/dist/packages/scanner/secret-scanner.js +1 -1
- package/package.json +7 -4
- package/dist/packages/crucible/seeds/python/seed-021 3.py +0 -37
- package/dist/packages/crucible/seeds/python/seed-022 3.py +0 -34
- package/dist/packages/crucible/seeds/python/seed-023 3.py +0 -37
- package/dist/packages/crucible/seeds/python/seed-024 3.py +0 -38
- package/dist/packages/crucible/seeds/python/seed-025 3.py +0 -40
- package/dist/packages/crucible/seeds/python/seed-026 3.py +0 -35
- package/dist/packages/crucible/seeds/python/seed-027 3.py +0 -35
- package/dist/packages/crucible/seeds/python/seed-028 3.py +0 -42
- package/dist/packages/crucible/seeds/python/seed-030 3.py +0 -37
- package/dist/packages/crucible/seeds/python/seed-031 3.py +0 -34
- package/dist/packages/crucible/seeds/python/seed-036 3.py +0 -33
- package/dist/packages/crucible/seeds/python/seed-037 3.py +0 -41
- package/dist/packages/crucible/seeds/python/seed-038 3.py +0 -33
- package/dist/packages/crucible/seeds/python/seed-039 3.py +0 -39
- package/dist/packages/crucible/seeds/python/seed-040 3.py +0 -39
- package/dist/packages/crucible/seeds/python/seed-041 3.py +0 -37
- package/dist/packages/crucible/seeds/python/seed-042 3.py +0 -38
- package/dist/packages/crucible/seeds/python/seed-043 3.py +0 -32
- package/dist/packages/crucible/seeds/python/seed-044 3.py +0 -38
- package/dist/packages/crucible/seeds/python/seed-045 3.py +0 -36
- package/dist/packages/crucible/seeds/python/seed-046 3.py +0 -33
- package/dist/packages/crucible/seeds/python/seed-047 3.py +0 -44
- package/dist/packages/crucible/seeds/python/seed-048 3.py +0 -35
- package/dist/packages/crucible/seeds/python/seed-049 3.py +0 -39
- package/dist/packages/crucible/seeds/python/seed-050 3.py +0 -39
- package/dist/packages/crucible/seeds/python/seed-051 3.py +0 -38
- package/dist/packages/crucible/seeds/python/seed-052 3.py +0 -41
- package/dist/packages/crucible/seeds/ruby/seed-001 3.rb +0 -15
- package/dist/packages/crucible/seeds/ruby/seed-002 3.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-003 3.rb +0 -25
- package/dist/packages/crucible/seeds/ruby/seed-004 3.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-005 3.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-006 3.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-007 3.rb +0 -16
- package/dist/packages/crucible/seeds/ruby/seed-008 3.rb +0 -18
- package/dist/packages/crucible/seeds/ruby/seed-009 3.rb +0 -20
- package/dist/packages/crucible/seeds/ruby/seed-010 3.rb +0 -24
- package/dist/packages/crucible/seeds/ruby/seed-011 3.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-012 3.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-013 3.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-014 3.rb +0 -16
- package/dist/packages/crucible/seeds/ruby/seed-015 3.rb +0 -18
- package/dist/packages/crucible/seeds/ruby/seed-016 3.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-017 3.rb +0 -25
- package/dist/packages/crucible/seeds/ruby/seed-018 3.rb +0 -23
- package/dist/packages/crucible/seeds/ruby/seed-019 3.rb +0 -20
- package/dist/packages/crucible/seeds/ruby/seed-020 3.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-021 3.rb +0 -20
- package/dist/packages/crucible/seeds/ruby/seed-022 3.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-023 3.rb +0 -19
- package/dist/packages/crucible/seeds/ruby/seed-024 3.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-025 3.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-026 3.rb +0 -18
- package/dist/packages/crucible/seeds/ruby/seed-027 3.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-028 3.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-029 3.rb +0 -19
- package/dist/packages/crucible/seeds/ruby/seed-030 3.rb +0 -20
- package/dist/packages/crucible/seeds/ruby/seed-031 3.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-032 3.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-033 3.rb +0 -18
- package/dist/packages/crucible/seeds/ruby/seed-034 3.rb +0 -19
- package/dist/packages/crucible/seeds/ruby/seed-035 3.rb +0 -19
- package/dist/packages/crucible/seeds/ruby/seed-036 3.rb +0 -18
- package/dist/packages/crucible/seeds/ruby/seed-037 3.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-038 3.rb +0 -24
- package/dist/packages/crucible/seeds/ruby/seed-039 3.rb +0 -24
- package/dist/packages/crucible/seeds/ruby/seed-040 3.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-041 3.rb +0 -23
- package/dist/packages/crucible/seeds/ruby/seed-042 3.rb +0 -25
- package/dist/packages/crucible/seeds/ruby/seed-043 3.rb +0 -23
- package/dist/packages/crucible/seeds/ruby/seed-044 3.rb +0 -16
- package/dist/packages/crucible/seeds/ruby/seed-045 3.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-046 3.rb +0 -27
- package/dist/packages/crucible/seeds/ruby/seed-047 3.rb +0 -26
- package/dist/packages/crucible/seeds/ruby/seed-048 3.rb +0 -24
- package/dist/packages/crucible/seeds/ruby/seed-049 3.rb +0 -20
- package/dist/packages/crucible/seeds/ruby/seed-050 3.rb +0 -27
- package/dist/packages/crucible/seeds/rust/seed-001 3.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-002 3.rs +0 -20
- package/dist/packages/crucible/seeds/rust/seed-003 3.rs +0 -23
- package/dist/packages/crucible/seeds/rust/seed-004 3.rs +0 -21
- package/dist/packages/crucible/seeds/rust/seed-005 3.rs +0 -21
- package/dist/packages/crucible/seeds/rust/seed-006 3.rs +0 -24
- package/dist/packages/crucible/seeds/rust/seed-007 3.rs +0 -20
- package/dist/packages/crucible/seeds/rust/seed-008 3.rs +0 -19
- package/dist/packages/crucible/seeds/rust/seed-009 3.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-010 3.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-011 3.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-012 3.rs +0 -31
- package/dist/packages/crucible/seeds/rust/seed-013 3.rs +0 -27
- package/dist/packages/crucible/seeds/rust/seed-014 3.rs +0 -30
- package/dist/packages/crucible/seeds/rust/seed-015 3.rs +0 -33
- package/dist/packages/crucible/seeds/rust/seed-016 3.rs +0 -22
- package/dist/packages/crucible/seeds/rust/seed-017 3.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-018 3.rs +0 -21
- package/dist/packages/crucible/seeds/rust/seed-019 3.rs +0 -36
- package/dist/packages/crucible/seeds/rust/seed-020 3.rs +0 -27
- package/dist/packages/crucible/seeds/rust/seed-021 3.rs +0 -26
- package/dist/packages/crucible/seeds/rust/seed-022 3.rs +0 -23
- package/dist/packages/crucible/seeds/rust/seed-023 3.rs +0 -22
- package/dist/packages/crucible/seeds/rust/seed-024 3.rs +0 -24
- package/dist/packages/crucible/seeds/rust/seed-025 3.rs +0 -29
- package/dist/packages/crucible/seeds/rust/seed-026 3.rs +0 -23
- package/dist/packages/crucible/seeds/rust/seed-027 3.rs +0 -24
- package/dist/packages/crucible/seeds/rust/seed-028 3.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-029 3.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-030 3.rs +0 -30
- package/dist/packages/crucible/seeds/rust/seed-031 3.rs +0 -22
- package/dist/packages/crucible/seeds/rust/seed-032 3.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-033 3.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-034 3.rs +0 -20
- package/dist/packages/crucible/seeds/rust/seed-035 3.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-036 3.rs +0 -26
- package/dist/packages/crucible/seeds/rust/seed-037 3.rs +0 -31
- package/dist/packages/crucible/seeds/rust/seed-038 3.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-039 3.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-040 3.rs +0 -27
- package/dist/packages/crucible/seeds/rust/seed-041 3.rs +0 -32
- package/dist/packages/crucible/seeds/rust/seed-042 3.rs +0 -27
- package/dist/packages/crucible/seeds/rust/seed-043 3.rs +0 -29
- package/dist/packages/crucible/seeds/rust/seed-044 3.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-045 3.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-046 3.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-047 3.rs +0 -34
- package/dist/packages/crucible/seeds/rust/seed-048 3.rs +0 -21
- package/dist/packages/crucible/seeds/rust/seed-049 3.rs +0 -26
- package/dist/packages/crucible/seeds/rust/seed-050 3.rs +0 -23
- package/dist/packages/crucible/seeds/ts/seed-001 3.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-002 3.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-003 3.ts +0 -28
- package/dist/packages/crucible/seeds/ts/seed-004 3.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-005 3.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-006 3.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-007 3.ts +0 -28
- package/dist/packages/crucible/seeds/ts/seed-008 3.ts +0 -40
- package/dist/packages/crucible/seeds/ts/seed-009 3.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-010 3.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-011 3.ts +0 -29
- package/dist/packages/crucible/seeds/ts/seed-012 3.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-013 3.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-014 3.ts +0 -36
- package/dist/packages/crucible/seeds/ts/seed-015 3.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-016 3.ts +0 -37
- package/dist/packages/crucible/seeds/ts/seed-017 3.ts +0 -44
- package/dist/packages/crucible/seeds/ts/seed-018 3.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-019 3.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-020 3.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-021 3.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-022 3.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-023 3.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-024 3.ts +0 -35
- package/dist/packages/crucible/seeds/ts/seed-025 3.ts +0 -29
- package/dist/packages/crucible/seeds/ts/seed-026 3.ts +0 -36
- package/dist/packages/crucible/seeds/ts/seed-027 3.ts +0 -30
- package/dist/packages/crucible/seeds/ts/seed-028 3.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-029 3.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-030 3.ts +0 -37
- package/dist/packages/crucible/seeds/ts/seed-031 3.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-032 3.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-033 3.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-034 3.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-035 3.ts +0 -29
- package/dist/packages/crucible/seeds/ts/seed-036 3.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-037 3.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-038 3.ts +0 -29
- package/dist/packages/crucible/seeds/ts/seed-039 3.ts +0 -35
- package/dist/packages/crucible/seeds/ts/seed-040 3.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-041 3.ts +0 -29
- package/dist/packages/crucible/seeds/ts/seed-042 3.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-043 3.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-044 3.ts +0 -36
- package/dist/packages/crucible/seeds/ts/seed-045 3.ts +0 -39
- package/dist/packages/crucible/seeds/ts/seed-046 3.ts +0 -41
- package/dist/packages/crucible/seeds/ts/seed-047 3.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-048 3.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-049 3.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-050 3.ts +0 -41
- package/dist/packages/crucible/seeds/ts/seed-051 3.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-052 3.ts +0 -36
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: insecure_crypto
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: AES-ECB mode with no IV and RC4 weak cipher usage
|
|
6
|
-
|
|
7
|
-
import * as crypto from 'crypto';
|
|
8
|
-
|
|
9
|
-
type CipherAlgorithm = 'aes-128-ecb' | 'aes-256-ecb' | 'rc4';
|
|
10
|
-
|
|
11
|
-
export class LegacyCrypto {
|
|
12
|
-
private key: Buffer;
|
|
13
|
-
|
|
14
|
-
constructor(hexKey: string) {
|
|
15
|
-
this.key = Buffer.from(hexKey, 'hex');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// ECB mode: identical plaintext blocks produce identical ciphertext
|
|
19
|
-
encryptECB(data: string): string {
|
|
20
|
-
const cipher = crypto.createCipheriv('aes-128-ecb', this.key.slice(0, 16), null);
|
|
21
|
-
return cipher.update(data, 'utf8', 'hex') + cipher.final('hex');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
decryptECB(data: string): string {
|
|
25
|
-
const decipher = crypto.createDecipheriv('aes-128-ecb', this.key.slice(0, 16), null);
|
|
26
|
-
return decipher.update(data, 'hex', 'utf8') + decipher.final('utf8');
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// RC4 is broken — key reuse leads to plaintext recovery
|
|
30
|
-
encryptRC4(data: string): string {
|
|
31
|
-
const cipher = crypto.createCipheriv('rc4', this.key.slice(0, 16), null);
|
|
32
|
-
return cipher.update(data, 'utf8', 'hex') + cipher.final('hex');
|
|
33
|
-
}
|
|
34
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: insecure_crypto
|
|
3
|
-
// severity: medium
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: timing attack via string comparison of HMAC signatures
|
|
6
|
-
|
|
7
|
-
import * as crypto from 'crypto';
|
|
8
|
-
|
|
9
|
-
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET ?? 'fallback-secret';
|
|
10
|
-
|
|
11
|
-
export function computeSignature(payload: string): string {
|
|
12
|
-
return crypto.createHmac('sha256', WEBHOOK_SECRET).update(payload).digest('hex');
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function verifyWebhookSignature(payload: string, receivedSig: string): boolean {
|
|
16
|
-
const expectedSig = computeSignature(payload);
|
|
17
|
-
// Timing attack: early exit on first mismatch leaks signature length/prefix
|
|
18
|
-
return expectedSig === receivedSig;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function verifyApiKey(providedKey: string, storedKey: string): boolean {
|
|
22
|
-
// Direct string comparison — vulnerable to timing oracle attack
|
|
23
|
-
return providedKey === storedKey;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function checkTokenMatch(a: string, b: string): boolean {
|
|
27
|
-
if (a.length !== b.length) return false;
|
|
28
|
-
// Character-by-character comparison — timing side-channel
|
|
29
|
-
for (let i = 0; i < a.length; i++) {
|
|
30
|
-
if (a[i] !== b[i]) return false;
|
|
31
|
-
}
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: insecure_crypto
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: PBKDF2 with low iteration count and SHA1, plus MD5 data integrity check
|
|
6
|
-
|
|
7
|
-
import * as crypto from 'crypto';
|
|
8
|
-
|
|
9
|
-
interface DerivedKey {
|
|
10
|
-
key: Buffer;
|
|
11
|
-
salt: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function deriveEncryptionKey(password: string): DerivedKey {
|
|
15
|
-
const salt = 'static-application-salt-2024'; // Should be random and stored per-key
|
|
16
|
-
// Only 1000 iterations — NIST recommends 600,000+ for SHA-1
|
|
17
|
-
const key = crypto.pbkdf2Sync(password, salt, 1000, 32, 'sha1');
|
|
18
|
-
return { key, salt };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function checksumFile(data: Buffer): string {
|
|
22
|
-
// MD5 is collision-prone — not suitable for integrity checks
|
|
23
|
-
return crypto.createHash('md5').update(data).digest('hex');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function fingerprintUser(userId: string, email: string): string {
|
|
27
|
-
// SHA1 fingerprint — deprecated for new security applications
|
|
28
|
-
return crypto.createHash('sha1').update(`${userId}:${email}`).digest('hex');
|
|
29
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: prototype_pollution
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: prototype pollution via typed recursive merge accepting user object
|
|
6
|
-
|
|
7
|
-
interface DeepObject {
|
|
8
|
-
[key: string]: unknown;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function deepMerge<T extends DeepObject>(target: T, source: DeepObject): T {
|
|
12
|
-
for (const key of Object.keys(source)) {
|
|
13
|
-
const srcVal = source[key];
|
|
14
|
-
// TypeScript types don't prevent __proto__ key at runtime
|
|
15
|
-
if (srcVal !== null && typeof srcVal === 'object' && !Array.isArray(srcVal)) {
|
|
16
|
-
if (!(target as DeepObject)[key]) (target as DeepObject)[key] = {};
|
|
17
|
-
deepMerge((target as DeepObject)[key] as DeepObject, srcVal as DeepObject);
|
|
18
|
-
} else {
|
|
19
|
-
(target as DeepObject)[key] = srcVal;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return target;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
import { Request, Response, Router } from 'express';
|
|
26
|
-
const router = Router();
|
|
27
|
-
|
|
28
|
-
router.post('/settings', (req: Request, res: Response) => {
|
|
29
|
-
const defaults = { theme: 'light', language: 'en', notifications: true };
|
|
30
|
-
// req.body directly passed — attacker sends {"__proto__": {"isAdmin": true}}
|
|
31
|
-
const merged = deepMerge(defaults, req.body as DeepObject);
|
|
32
|
-
res.json({ settings: merged });
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
export default router;
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: prototype_pollution
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: prototype pollution via bracket-notation assignment with user-controlled key path
|
|
6
|
-
|
|
7
|
-
import { Request, Response, Router } from 'express';
|
|
8
|
-
|
|
9
|
-
const router = Router();
|
|
10
|
-
|
|
11
|
-
function setProperty(obj: Record<string, unknown>, keyPath: string, value: unknown): void {
|
|
12
|
-
const parts = keyPath.split('.');
|
|
13
|
-
let cursor: Record<string, unknown> = obj;
|
|
14
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
15
|
-
const part = parts[i];
|
|
16
|
-
// Allows "__proto__" or "constructor" as path segment
|
|
17
|
-
if (typeof cursor[part] !== 'object') cursor[part] = {};
|
|
18
|
-
cursor = cursor[part] as Record<string, unknown>;
|
|
19
|
-
}
|
|
20
|
-
cursor[parts[parts.length - 1]] = value;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
router.patch('/user/preference', (req: Request, res: Response) => {
|
|
24
|
-
const { key, value } = req.body as { key: string; value: unknown };
|
|
25
|
-
const userPrefs: Record<string, unknown> = {};
|
|
26
|
-
// key = "__proto__.admin" → Object.prototype.admin = value
|
|
27
|
-
setProperty(userPrefs, key, value);
|
|
28
|
-
res.json({ updated: true });
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
export default router;
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: prototype_pollution
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: prototype pollution via lodash _.merge or _.set with user-controlled path
|
|
6
|
-
|
|
7
|
-
import _ from 'lodash';
|
|
8
|
-
import { Request, Response, Router } from 'express';
|
|
9
|
-
|
|
10
|
-
const router = Router();
|
|
11
|
-
const globalAppConfig: Record<string, unknown> = {};
|
|
12
|
-
|
|
13
|
-
router.post('/admin/config/set', (req: Request, res: Response) => {
|
|
14
|
-
const { path, value } = req.body as { path: string; value: unknown };
|
|
15
|
-
// _.set with user-controlled path — vulnerable to prototype pollution
|
|
16
|
-
// e.g. path = "__proto__.isAdmin", value = true
|
|
17
|
-
_.set(globalAppConfig, path, value);
|
|
18
|
-
res.json({ ok: true });
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
router.post('/ui/theme', (req: Request, res: Response) => {
|
|
22
|
-
const userTheme = req.body as Record<string, unknown>;
|
|
23
|
-
const defaultTheme = { primary: '#007bff', background: '#fff', text: '#333' };
|
|
24
|
-
// _.merge pollutes prototype when userTheme contains __proto__
|
|
25
|
-
const merged = _.merge({}, defaultTheme, userTheme);
|
|
26
|
-
res.json({ theme: merged });
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
export default router;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: prototype_pollution
|
|
3
|
-
// severity: medium
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: prototype pollution via constructor.prototype path in Object.assign chain
|
|
6
|
-
|
|
7
|
-
import { Request, Response, Router } from 'express';
|
|
8
|
-
|
|
9
|
-
const router = Router();
|
|
10
|
-
|
|
11
|
-
interface PluginConfig {
|
|
12
|
-
[key: string]: unknown;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function applyPluginOptions(baseOptions: PluginConfig, userOptions: PluginConfig): PluginConfig {
|
|
16
|
-
// Object.assign shallow-merges — __proto__ key from JSON payload pollutes Object.prototype
|
|
17
|
-
return Object.assign({}, baseOptions, userOptions);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
router.post('/plugins/:id/configure', (req: Request, res: Response) => {
|
|
21
|
-
const base: PluginConfig = { enabled: true, maxRetries: 3, timeout: 5000 };
|
|
22
|
-
// req.body passed directly — {"__proto__": {"polluted": true}}
|
|
23
|
-
const config = applyPluginOptions(base, req.body as PluginConfig);
|
|
24
|
-
res.json({ configured: true, config });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
router.patch('/plugins/:id/update', (req: Request, res: Response) => {
|
|
28
|
-
// Spread operator with user body — same pollution vector
|
|
29
|
-
const updated = { ...{}, ...req.body };
|
|
30
|
-
res.json({ updated });
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
export default router;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: prototype_pollution
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: prototype pollution via qs-parsed query params passed to deep clone utility
|
|
6
|
-
|
|
7
|
-
import * as qs from 'qs';
|
|
8
|
-
import { Request, Response, Router } from 'express';
|
|
9
|
-
|
|
10
|
-
const router = Router();
|
|
11
|
-
|
|
12
|
-
function cloneDeep<T extends object>(obj: T): T {
|
|
13
|
-
return JSON.parse(JSON.stringify(obj)) as T;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function applyFilter<T extends Record<string, unknown>>(base: T, filter: Record<string, unknown>): T {
|
|
17
|
-
// Merges filter keys directly onto base — __proto__ pollution via qs deep parsing
|
|
18
|
-
for (const [key, val] of Object.entries(filter)) {
|
|
19
|
-
(base as Record<string, unknown>)[key] = val;
|
|
20
|
-
}
|
|
21
|
-
return base;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
router.get('/items', (req: Request, res: Response) => {
|
|
25
|
-
// qs.parse supports nested objects — ?__proto__[admin]=true becomes {__proto__: {admin:true}}
|
|
26
|
-
const filter = qs.parse(req.query as Record<string, string>, { allowDots: true, allowPrototypes: true });
|
|
27
|
-
const baseQuery = { page: 1, limit: 20, active: true };
|
|
28
|
-
const merged = applyFilter(baseQuery, filter as Record<string, unknown>);
|
|
29
|
-
res.json({ query: merged });
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
export default router;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: eval_abuse
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: eval() called with user-controlled expression in typed handler
|
|
6
|
-
|
|
7
|
-
import { Request, Response, Router } from 'express';
|
|
8
|
-
|
|
9
|
-
const router = Router();
|
|
10
|
-
|
|
11
|
-
interface CalcRequest {
|
|
12
|
-
expression: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
router.post('/calc', (req: Request, res: Response) => {
|
|
16
|
-
const { expression } = req.body as CalcRequest;
|
|
17
|
-
try {
|
|
18
|
-
// TypeScript types don't prevent runtime eval injection
|
|
19
|
-
// Attacker: "process.mainModule.require('child_process').execSync('id').toString()"
|
|
20
|
-
// eslint-disable-next-line no-eval
|
|
21
|
-
const result = eval(expression);
|
|
22
|
-
res.json({ result });
|
|
23
|
-
} catch (err: any) {
|
|
24
|
-
res.status(400).json({ error: err.message });
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
router.get('/render-tpl', (req: Request, res: Response) => {
|
|
29
|
-
const tpl = req.query.tpl as string;
|
|
30
|
-
const user = req.session?.user ?? {};
|
|
31
|
-
// eval of backtick template with user-controlled string
|
|
32
|
-
const output = eval('`' + tpl + '`');
|
|
33
|
-
res.send(output);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
export default router;
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: eval_abuse
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: new Function() constructor with user-supplied code string in typed scripting service
|
|
6
|
-
|
|
7
|
-
import { Request, Response, Router } from 'express';
|
|
8
|
-
|
|
9
|
-
const router = Router();
|
|
10
|
-
|
|
11
|
-
interface ScriptRequest {
|
|
12
|
-
code: string;
|
|
13
|
-
context?: Record<string, unknown>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function executeUserScript(code: string, context: Record<string, unknown>): unknown {
|
|
17
|
-
// new Function() is not sandboxed — full access to global scope
|
|
18
|
-
const fn = new Function('ctx', 'Math', 'JSON', `
|
|
19
|
-
with (ctx) {
|
|
20
|
-
${code}
|
|
21
|
-
}
|
|
22
|
-
`);
|
|
23
|
-
return fn(context, Math, JSON);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
router.post('/scripting/execute', (req: Request, res: Response) => {
|
|
27
|
-
const { code, context = {} } = req.body as ScriptRequest;
|
|
28
|
-
const result = executeUserScript(code, context);
|
|
29
|
-
res.json({ result });
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
router.post('/hooks/transform', (req: Request, res: Response) => {
|
|
33
|
-
const { transformCode } = req.body;
|
|
34
|
-
const transform = new Function('data', 'helpers', transformCode);
|
|
35
|
-
const output = transform(req.body.data, { JSON, Math });
|
|
36
|
-
res.json({ output });
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
export default router;
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: eval_abuse
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: eval() inside JSON reviver executing serialized function strings from DB
|
|
6
|
-
|
|
7
|
-
interface SerializedConfig {
|
|
8
|
-
[key: string]: unknown;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function deserializeConfig(raw: string): SerializedConfig {
|
|
12
|
-
return JSON.parse(raw, (key, value) => {
|
|
13
|
-
if (typeof value === 'string' && value.startsWith('__function__:')) {
|
|
14
|
-
const fnBody = value.slice(13);
|
|
15
|
-
// eval executes arbitrary code stored in the config (from DB, user-uploaded file, etc.)
|
|
16
|
-
// eslint-disable-next-line no-eval
|
|
17
|
-
return eval(`(${fnBody})`);
|
|
18
|
-
}
|
|
19
|
-
return value;
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface PipelineStep {
|
|
24
|
-
name: string;
|
|
25
|
-
handler: ((input: unknown) => unknown) | string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function loadPipeline(configJson: string): PipelineStep[] {
|
|
29
|
-
const config = deserializeConfig(configJson);
|
|
30
|
-
return config.steps as PipelineStep[];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export async function executePipeline(steps: PipelineStep[], input: unknown): Promise<unknown> {
|
|
34
|
-
let result = input;
|
|
35
|
-
for (const step of steps) {
|
|
36
|
-
if (typeof step.handler === 'function') {
|
|
37
|
-
result = await step.handler(result);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return result;
|
|
41
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: eval_abuse
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: lodash template compilation with user-controlled template string (code execution)
|
|
6
|
-
|
|
7
|
-
import _ from 'lodash';
|
|
8
|
-
import { Request, Response, Router } from 'express';
|
|
9
|
-
|
|
10
|
-
const router = Router();
|
|
11
|
-
|
|
12
|
-
interface TemplateRequest {
|
|
13
|
-
template: string;
|
|
14
|
-
data: Record<string, unknown>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
router.post('/email/preview', async (req: Request, res: Response) => {
|
|
18
|
-
const { template, data } = req.body as TemplateRequest;
|
|
19
|
-
// _.template compiles and executes JS inside <%= %> delimiters
|
|
20
|
-
// Attacker template: "<%= process.mainModule.require('child_process').execSync('id') %>"
|
|
21
|
-
const compiled = _.template(template);
|
|
22
|
-
const output = compiled(data);
|
|
23
|
-
res.type('text/html').send(output);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
router.get('/report/preview', (req: Request, res: Response) => {
|
|
27
|
-
const tpl = req.query.template as string;
|
|
28
|
-
const vars = JSON.parse(req.query.vars as string ?? '{}');
|
|
29
|
-
const compiled = _.template(tpl);
|
|
30
|
-
res.send(compiled(vars));
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
export default router;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: ssrf
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: SSRF via fetch with unvalidated user-supplied URL
|
|
6
|
-
|
|
7
|
-
import { Request, Response, Router } from 'express';
|
|
8
|
-
|
|
9
|
-
const router = Router();
|
|
10
|
-
|
|
11
|
-
interface ProxyRequest {
|
|
12
|
-
url: string;
|
|
13
|
-
method?: 'GET' | 'POST';
|
|
14
|
-
headers?: Record<string, string>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
router.post('/proxy', async (req: Request, res: Response) => {
|
|
18
|
-
const { url, method = 'GET', headers = {} } = req.body as ProxyRequest;
|
|
19
|
-
// No URL validation — allows SSRF to internal services, cloud metadata, etc.
|
|
20
|
-
const response = await fetch(url, { method, headers });
|
|
21
|
-
const body = await response.text();
|
|
22
|
-
res.status(response.status).send(body);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
router.get('/preview', async (req: Request, res: Response) => {
|
|
26
|
-
const targetUrl = req.query.url as string;
|
|
27
|
-
// Attacker: ?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
|
|
28
|
-
const resp = await fetch(targetUrl, { signal: AbortSignal.timeout(5000) });
|
|
29
|
-
const data = await resp.text();
|
|
30
|
-
res.json({ preview: data.slice(0, 500) });
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
export default router;
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: ssrf
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: SSRF via axios with user-controlled webhook URL and redirect following
|
|
6
|
-
|
|
7
|
-
import axios from 'axios';
|
|
8
|
-
import { Request, Response, Router } from 'express';
|
|
9
|
-
|
|
10
|
-
const router = Router();
|
|
11
|
-
|
|
12
|
-
interface WebhookConfig {
|
|
13
|
-
url: string;
|
|
14
|
-
secret?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
router.post('/webhooks/test', async (req: Request, res: Response) => {
|
|
18
|
-
const { url, secret } = req.body as WebhookConfig;
|
|
19
|
-
// url user-controlled — attacker probes internal network
|
|
20
|
-
const response = await axios.post(url, { test: true, timestamp: Date.now() }, {
|
|
21
|
-
headers: { 'X-Webhook-Secret': secret ?? '' },
|
|
22
|
-
maxRedirects: 5, // follows redirects — can pivot to internal services
|
|
23
|
-
timeout: 10000,
|
|
24
|
-
});
|
|
25
|
-
res.json({ status: response.status, data: response.data });
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
router.get('/import', async (req: Request, res: Response) => {
|
|
29
|
-
const dataUrl = req.query.source as string;
|
|
30
|
-
const { data } = await axios.get(dataUrl, { responseType: 'arraybuffer' });
|
|
31
|
-
res.set('Content-Type', 'application/octet-stream').send(data);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
export default router;
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: ssrf
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: SSRF via got library with user-controlled URL in oEmbed/link-preview feature
|
|
6
|
-
|
|
7
|
-
import got from 'got';
|
|
8
|
-
import { Request, Response, Router } from 'express';
|
|
9
|
-
|
|
10
|
-
const router = Router();
|
|
11
|
-
|
|
12
|
-
interface LinkPreviewResult {
|
|
13
|
-
title: string;
|
|
14
|
-
description: string;
|
|
15
|
-
image?: string;
|
|
16
|
-
url: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
router.get('/link-preview', async (req: Request, res: Response) => {
|
|
20
|
-
const url = req.query.url as string;
|
|
21
|
-
|
|
22
|
-
// url is user-controlled — attacker can reach internal services
|
|
23
|
-
const response = await got(url, {
|
|
24
|
-
followRedirect: true,
|
|
25
|
-
timeout: { request: 8000 },
|
|
26
|
-
throwHttpErrors: false,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const titleMatch = response.body.match(/<title>(.*?)<\/title>/i);
|
|
30
|
-
const descMatch = response.body.match(/<meta name="description" content="(.*?)"/i);
|
|
31
|
-
|
|
32
|
-
const result: LinkPreviewResult = {
|
|
33
|
-
title: titleMatch?.[1] ?? '',
|
|
34
|
-
description: descMatch?.[1] ?? '',
|
|
35
|
-
url,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
res.json(result);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
export default router;
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: deserialization
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: unsafe deserialization via class-transformer plainToClass with user-controlled type
|
|
6
|
-
|
|
7
|
-
import { plainToClass } from 'class-transformer';
|
|
8
|
-
import { IsString, IsNumber } from 'class-validator';
|
|
9
|
-
import { Request, Response, Router } from 'express';
|
|
10
|
-
|
|
11
|
-
class UserDto {
|
|
12
|
-
@IsString() username!: string;
|
|
13
|
-
@IsString() email!: string;
|
|
14
|
-
@IsNumber() role!: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
class AdminDto extends UserDto {
|
|
18
|
-
@IsString() adminToken!: string;
|
|
19
|
-
canDeleteUsers: boolean = true;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const router = Router();
|
|
23
|
-
|
|
24
|
-
router.post('/users/import', (req: Request, res: Response) => {
|
|
25
|
-
const { type, data } = req.body;
|
|
26
|
-
|
|
27
|
-
// User controls which class to deserialize into — can escalate to AdminDto
|
|
28
|
-
const DtoClass = type === 'admin' ? AdminDto : UserDto;
|
|
29
|
-
const instance = plainToClass(DtoClass, data);
|
|
30
|
-
// No validation step — properties set without checking constraints
|
|
31
|
-
res.json({ created: instance });
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
export default router;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: deserialization
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: unsafe deserialization via JSON.parse used to dynamically dispatch method calls
|
|
6
|
-
|
|
7
|
-
import { Request, Response, Router } from 'express';
|
|
8
|
-
|
|
9
|
-
interface TaskDescriptor {
|
|
10
|
-
module: string;
|
|
11
|
-
method: string;
|
|
12
|
-
args: unknown[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const allowedModules: Record<string, Record<string, (...args: unknown[]) => unknown>> = {
|
|
16
|
-
reports: require('./reports'),
|
|
17
|
-
notifications: require('./notifications'),
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
router.post('/task/run', (req: Request, res: Response) => {
|
|
21
|
-
// task is JSON-parsed from user body without schema validation
|
|
22
|
-
const task = JSON.parse(req.body.taskJson as string) as TaskDescriptor;
|
|
23
|
-
|
|
24
|
-
const mod = allowedModules[task.module];
|
|
25
|
-
if (!mod) return res.status(400).json({ error: 'Unknown module' });
|
|
26
|
-
|
|
27
|
-
const fn = mod[task.method];
|
|
28
|
-
if (typeof fn !== 'function') return res.status(400).json({ error: 'Unknown method' });
|
|
29
|
-
|
|
30
|
-
// task.args passed directly — no sanitization or type validation
|
|
31
|
-
const result = fn(...task.args);
|
|
32
|
-
res.json({ result });
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const router2 = Router();
|
|
36
|
-
export default router2;
|