From 6efeb12274d6ebecaa84c80785ebe957a0f69626 Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Tue, 5 Dec 2017 19:58:20 +0100 Subject: [PATCH] Failing TLS proxy code --- .gitignore | 1 + README.md | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 14 ++++++ proxy.js | 58 +++++++++++++++++++++++++ test-ca.key | 28 ++++++++++++ test-ca.pem | 19 ++++++++ 6 files changed, 239 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 package.json create mode 100644 proxy.js create mode 100644 test-ca.key create mode 100644 test-ca.pem diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/README.md b/README.md new file mode 100644 index 0000000..41fc255 --- /dev/null +++ b/README.md @@ -0,0 +1,119 @@ +# Node TLS crash demo + +## Repro: + +* Checkout +* `npm install` +* `npm start` +* Start chrome using this as a proxy +* Node will segfault with a minute or two + +This module is a minimal POC implementation of an HTTPS intercepting proxy. It's using no native modules at all, but it still reliably +crashes with SIGSEGV/SIGABRT in recent Node versions. + +## Details + +Running `npm start` starts an intercepting HTTPS proxy server on port 8000. You'll need to disable cert validation or trust `test-ca.pem` as a CA to use it. + +E.g. with curl: + +```bash +curl -k --proxy-insecure -x https://localhost:8000 https://example.com +``` + +You can start a fresh Chrome session using this proxy & trusting the cert with: + +``` +google-chrome --user-data-dir=/tmp/chrome --proxy-server="https://localhost:80" --ignore-certificate-errors-spki-list=AvVrqB/anBbJ+KRCMH/anWgZbeE0Y28JtqYB0+2MDmE= +``` + +After a minute of heavy use the proxy will crash with SIGSEGV/SIGABRT, I think always in CRYPTO_free, and typically inside DestroySSL. + +The easiest repro for me is just to open `https://cnn.com` in a few tabs. This fails in at least Node 8.9.1 and 6.12.0 (though v6 seems to take longer). + +Note that this code doesn't use any native modules - it's pure JS all the way down (according to `find node_modules/ -name \*.node`), so this +is a crash coming directly from Node itself. + +Failures typically look something like: + +``` +*** Error in `node': free(): invalid next size (fast): 0x0000000003ea2660 *** +======= Backtrace: ========= +/lib/x86_64-linux-gnu/libc.so.6(+0x7908b)[0x7f3ba3ff908b] +/lib/x86_64-linux-gnu/libc.so.6(+0x82c3a)[0x7f3ba4002c3a] +/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7f3ba4006d2c] +node(CRYPTO_free+0x25)[0x997995] +node(ASN1_primitive_free+0x6f)[0x90ed0f] +node(asn1_item_combine_free+0x93)[0x90eea3] +node(ASN1_item_free+0x2b2)[0x90f502] +node(sk_pop_free+0x37)[0x9d2767] +node[0x913a3d] +node(asn1_item_combine_free+0x5e)[0x90ee6e] +node(asn1_item_combine_free+0x2e2)[0x90f0f2] +node(ASN1_item_free+0x2b2)[0x90f502] +node(sk_pop_free+0x37)[0x9d2767] +node(ssl_sess_cert_free+0x53)[0x8d3793] +node(SSL_SESSION_free+0x8f)[0x8e0fcf] +node(SSL_free+0xf5)[0x8db195] +node(_ZN4node6crypto7SSLWrapINS_7TLSWrapEE10DestroySSLEv+0x1a)[0x12dd7ea] +node(_ZN4node7TLSWrap10DestroySSLERKN2v820FunctionCallbackInfoINS1_5ValueEEE+0xb8)[0x12e33b8] +node(_ZN2v88internal25FunctionCallbackArguments4CallEPFvRKNS_20FunctionCallbackInfoINS_5ValueEEEE+0x193)[0xb1a073] +node[0xb8fe3c] +node(_ZN2v88internal21Builtin_HandleApiCallEiPPNS0_6ObjectEPNS0_7IsolateE+0xaf)[0xb90a8f] +[0x15bab148463d] +``` + +``` +*** Error in `node': free(): invalid pointer: 0x0000000002ff6580 *** +======= Backtrace: ========= +/lib/x86_64-linux-gnu/libc.so.6(+0x7908b)[0x7f78f945b08b] +/lib/x86_64-linux-gnu/libc.so.6(+0x82c3a)[0x7f78f9464c3a] +/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7f78f9468d2c] +node(CRYPTO_free+0x25)[0x997995] +node(sk_free+0x1f)[0x9d27df] +node(asn1_item_combine_free+0x1f4)[0x90f004] +node(ASN1_item_free+0x2b2)[0x90f502] +node(_ZN4node6crypto13SecureContextD0Ev+0x3f)[0x12d76ef] +node(_ZN2v88internal13GlobalHandles31DispatchPendingPhantomCallbacksEb+0xee)[0xe7b01e] +node(_ZN2v88internal13GlobalHandles31PostGarbageCollectionProcessingENS0_16GarbageCollectorENS_15GCCallbackFlagsE+0x2a)[0xe7b28a] +node(_ZN2v88internal4Heap24PerformGarbageCollectionENS0_16GarbageCollectorENS_15GCCallbackFlagsE+0x2be)[0xea660e] +node[0xea7933] +node(_ZN2v88internal4Heap36FinalizeIncrementalMarkingIfCompleteENS0_23GarbageCollectionReasonE+0x4a)[0xea86ca] +node(_ZN2v88internal21IncrementalMarkingJob4Task11RunInternalEv+0x119)[0xeaa0d9] +node(_ZN2v88internal14CancelableTask3RunEv+0x36)[0xbc83e6] +node(_ZN4node12NodePlatform28FlushForegroundTasksInternalEv+0x1f4)[0x1272174] +node[0x145796b] +node[0x14694c8] +node(uv_run+0x156)[0x14582f6] +node(_ZN4node5StartEP9uv_loop_siPKPKciS5_+0xc75)[0x122af15] +node(_ZN4node5StartEiPPc+0x163)[0x1223b73] +/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf1)[0x7f78f94023f1] +node[0x8ae7c1] +``` + +``` +*** Error in `node': corrupted size vs. prev_size: 0x0000000003fd6060 *** +======= Backtrace: ========= +/lib/x86_64-linux-gnu/libc.so.6(+0x7908b)[0x7f66633b608b] +/lib/x86_64-linux-gnu/libc.so.6(+0x814a7)[0x7f66633be4a7] +/lib/x86_64-linux-gnu/libc.so.6(+0x82f20)[0x7f66633bff20] +/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7f66633c3d2c] +node(CRYPTO_free+0x25)[0x997995] +node(SSL_CTX_free+0x1e3)[0x8da9b3] +node(_ZN4node6crypto13SecureContextD0Ev+0x31)[0x12d76e1] +node(_ZN2v88internal13GlobalHandles31DispatchPendingPhantomCallbacksEb+0xee)[0xe7b01e] +node(_ZN2v88internal13GlobalHandles31PostGarbageCollectionProcessingENS0_16GarbageCollectorENS_15GCCallbackFlagsE+0x2a)[0xe7b28a] +node(_ZN2v88internal4Heap24PerformGarbageCollectionENS0_16GarbageCollectorENS_15GCCallbackFlagsE+0x2be)[0xea660e] +node[0xea7933] +node(_ZN2v88internal4Heap36FinalizeIncrementalMarkingIfCompleteENS0_23GarbageCollectionReasonE+0x4a)[0xea86ca] +node(_ZN2v88internal21IncrementalMarkingJob4Task11RunInternalEv+0x119)[0xeaa0d9] +node(_ZN2v88internal14CancelableTask3RunEv+0x36)[0xbc83e6] +node(_ZN4node12NodePlatform28FlushForegroundTasksInternalEv+0x1f4)[0x1272174] +node[0x145796b] +node[0x14694c8] +node(uv_run+0x156)[0x14582f6] +node(_ZN4node5StartEP9uv_loop_siPKPKciS5_+0xc75)[0x122af15] +node(_ZN4node5StartEiPPc+0x163)[0x1223b73] +/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf1)[0x7f666335d3f1] +node[0x8ae7c1] +``` \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..252e1c0 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "tls-crash-demo", + "version": "1.0.0", + "description": "", + "main": "proxy.js", + "scripts": { + "start": "node proxy.js" + }, + "author": "", + "license": "MIT", + "dependencies": { + "mockttp": "^0.4.0" + } +} diff --git a/proxy.js b/proxy.js new file mode 100644 index 0000000..fb52f2c --- /dev/null +++ b/proxy.js @@ -0,0 +1,58 @@ +const https = require('https'); +const tls = require('tls'); +const fs = require('fs'); + +const { CA } = require('mockttp/dist/util/tls'); + +const port = 8000; + +const caKey = fs.readFileSync('./test-ca.key'); +const caCert = fs.readFileSync('./test-ca.pem'); + +const ca = new CA(caKey, caCert); + +const serverCert = ca.generateCertificate('localhost'); +const server = https.createServer({ + key: caKey, + cert: caCert, + ca: [caCert], + SNICallback: (domain, cb) => { + const generatedCert = ca.generateCertificate(domain); + cb(null, tls.createSecureContext(generatedCert)); + } +}); + +server.addListener('connect', (req, socket, head) => { + console.log(`Proxying request to ${req.url}`); + let [ targetHost, port ] = req.url.split(':'); + port = parseInt(port, 10); + + // Note that the CA caches generated certificates by domain, so this is typically fairly cheap. + // This is pure JS with node-forge, source at https://unpkg.com/mockttp@0.4.0/dist/util/tls.js + const { key, cert } = ca.generateCertificate(targetHost); + + socket.write('HTTP/' + req.httpVersion + ' 200 OK\r\n\r\n', 'UTF-8', () => { + let tlsSocket = new tls.TLSSocket(socket, { + isServer: true, + secureContext: tls.createSecureContext({ + key, + cert, + ca: caCert + }) + }); + + let upstream = tls.connect(port, targetHost, {}, () => { + upstream.write(head); + + tlsSocket.pipe(upstream); + upstream.pipe(tlsSocket); + }); + + upstream.on('error', (e) => { + console.log(`Upstream error for ${targetHost}`, e); + socket.destroy(e); + }); + }); +}); + +server.listen(port, (err) => console.log(err || `Listening on ${port}`)); \ No newline at end of file diff --git a/test-ca.key b/test-ca.key new file mode 100644 index 0000000..74420d3 --- /dev/null +++ b/test-ca.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDNfC2Wrf1+skkr +eS53j4BNHjL6579cu/mK/VX1oENG/FTqEw7QC4bO6y56h1dLaCtgaUutmtfAlM9T +AQBBAzRMriNmXqzu/lHp3AoH57/1WbQ5skRzwn2zUU9ktPm63dWsFnJI/k9thL1a +D+vnakJAzjj7J4agFs91Ll3FLDzX44g4ptVERAP9aQ9HlooU991e1EDchNuRynTM +E+oHV+s7oV0jWq5m3m0KeJrVtug9NdtcUTTQCzlC9OXBBiGlspAr8lrLg8MnIAUY +A33nmbaINdqu1u8LxLVjTfmQJI4e19FGfiyMJLX9iFhT+ATnpt6itAoXkIR/nKYi +CIxcTqLlAgMBAAECggEBALYMRB9wulsa/PRFAkPuoM6x6Jyd4M35w5W96AAbIj+r +oldW1xK+g4qilaT8bvxhp7xczS1bN7ZooF17T4xJmHYu+THiwvdh3ZSA22oxicGG +3cMYCsk8ED1j1iD0rSM/EeTKZioBFMGEjnVSEcAORfBAQ/9O/1ipnNbGY1EiZi0k +0lIKcCwLmyXWTK07o40W2l2cxpJddcM1ZSWr5jiw6SNfuhIDMelM+ctvFcdBp2HJ +6RP40e4nCliz+bBsjJ9rI9JnxJ4Zx11CI0a7Fi4B3va1Pips58wvZhm1V6PzRVyx +WPm1Sjj0hLmzjpwGX5XdTfGAaw71ekJpr1ZXnbVItSkCgYEA+dXGKsKvgI+WamM1 +EpDwvBUGwWyA7MSUDX10iT/vawUP7jmvwASvcSbsOy8n5kvm2zyCA/5ZPGJvEvNK +mHASenbBXlPmSbUg31Ju1ndTf4KLkmPHldzR0Wg7lzYypP1mKh1ZeZzr3jLLc35g +CRyHorAudlxipDOKFiLdQl3Jgp8CgYEA0o498Y+O0gcX/OP9yG2Osap9Ixj64EHs +sOPUfYPElvHzPyngPwF6zDLNVOiZrv4dnW6S1vLveYFWjm9HuzVlqLVg2wC+nYrS +baURB7phwjJUBzLyRh9e4qKwmjwygbhhmvpqLXPw3tq00KkFui9u+AR7l6q0W0aU +PBC9TwjDz/sCgYEAvZqAh2kzGkVeqSTbXVjEamkmtFqtSYMyvT4t2A++mqC/41LC +T97+ashYVd5G0J9f95hnL7KzdIFbvK0JOsErNOe1fun7horBBNshpP/wTX/8fr/c +854fWmZZezu1mcN56pJVmlb+Jqa0AtWw+pk9UrmUuV0Ju4Yl0QDqnHhi+w8CgYBe +SDdQjXnkbiy9ntGrB/PXdoImTy83cA2uf7ZMpc5H0PudEwFI9T6pZS4wkR8QDtXw +Au1Zttqoy9OYYBf8qkJlMyK1rjWskdb8JefkT/8qWCLsPlHiOHXigfsWdVIgaBG8 +tdkJlVsMT16b+LOJ1WeZQ1icZ2HvZUFHTC+YzlHwNQKBgQCUp1NT93fXNs9mYU5l +Az0gsAdjECz6N7kNQWGrl883Y3YZ/wqrfir3UCKGxJ6270IkA+VPdSmplCPf1zwQ +TmppzR3WuKqlj29W0qavT+4f/67aKTZGormo+QfXjL6kkXRGJ0xLsentcuoM7zCC +gIBTGAb9wqjI1WPO6ImlPYVxjA== +-----END PRIVATE KEY----- diff --git a/test-ca.pem b/test-ca.pem new file mode 100644 index 0000000..e3bbf89 --- /dev/null +++ b/test-ca.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDKzCCAhOgAwIBAgIJAP4Z15C7V0rSMA0GCSqGSIb3DQEBCwUAMCwxKjAoBgNV +BAMMIU1vY2t0dHAgVGVzdGluZyBDQSAtIERPIE5PVCBUUlVTVDAeFw0xNzExMTQy +MTU2MDlaFw0xNzEyMTQyMTU2MDlaMCwxKjAoBgNVBAMMIU1vY2t0dHAgVGVzdGlu +ZyBDQSAtIERPIE5PVCBUUlVTVDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAM18LZat/X6ySSt5LnePgE0eMvrnv1y7+Yr9VfWgQ0b8VOoTDtALhs7rLnqH +V0toK2BpS62a18CUz1MBAEEDNEyuI2ZerO7+UencCgfnv/VZtDmyRHPCfbNRT2S0 ++brd1awWckj+T22EvVoP6+dqQkDOOPsnhqAWz3UuXcUsPNfjiDim1UREA/1pD0eW +ihT33V7UQNyE25HKdMwT6gdX6zuhXSNarmbebQp4mtW26D0121xRNNALOUL05cEG +IaWykCvyWsuDwycgBRgDfeeZtog12q7W7wvEtWNN+ZAkjh7X0UZ+LIwktf2IWFP4 +BOem3qK0CheQhH+cpiIIjFxOouUCAwEAAaNQME4wHQYDVR0OBBYEFGZVh4J+GvDD +iFJlD0ubhr4iD188MB8GA1UdIwQYMBaAFGZVh4J+GvDDiFJlD0ubhr4iD188MAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAL1PwkVVCuJoSn4PLdw3MmUP +YBtrTnC4n6v1p2uHNktWA5Mq0AyLggdp4URlBunsfPCEwx3/dbNimOmav/rHIsiC +uQgT6G3KT0Npr95EpO8GXka1vkYP1sI81j4ANR3h3smGXilFLSBQQfFkzRMg427z +ABP9PDiuwlYModoE5aXr0mhgW+gi8HffIWrzickbOdyZgrljB1ehHh1oiGTNs3gi +JAKhQc7bEejuyLI8pYovZVSKzdajGsr4+CZqqkz4OOxo3z+C55hCDyJILHJUlWS7 +mSfkXJTIhnyW3gyMp6o3F0c2XvAqLAawnHo1lUXHcu7JChJAWDXRxdu4H1+jwQc= +-----END CERTIFICATE-----