mitmproxy
mitmproxy copied to clipboard
Add TLS config mirroring
Description
Modify tlsconfig.py to mirror client's ciphersuites to server
Checklist
- [ ] I have updated tests where applicable.
- [ ] I have added an entry to the CHANGELOG.
That question is slightly confusing to me - when do you see SSL_MODE_SEND_FALLBACK_SCSV? I thought this would only show up on retries if the application sets it explicitly.
Not OP, but on my end SCSV suites are sent by pyOpenSSL on the client hello. Specifically,
TLS_EMPTY_RENEGOTIATION_INFO_SCSV
(0x00ff) is sent even when not included in the cipher list.

That's exactly like @LicketySpliket says - it always appears at the end of the ciphersuites' list, even if I comment it out in map (i.e. it's not copied from client request). Tested it by visiting https://howsmyssl.com with Chrome and Firefox with and without mitmproxy. Also, all requests using curl always send this CS (even without mitmproxy). I suppose this is from OpenSSL itself.
Thanks. I'm as smart as anyone here, but https://www.openssl.org/docs/manmaster/man3/SSL_get_secure_renegotiation_support.html#SECURE-RENEGOTIATION has two options that could at least be tried out.
SSL._lib.SSL_OP_LEGACY_SERVER_CONNECT
and SSL._lib.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
are both available via cryptography.
I am also interested in this feature. I hope this will defeat TLS fingerprinting like cloudflare does which is annoyance for me. One question though: Is it enough to just clone the cipher list or are the extensions also part of the fingerprint?
I checked the current openssl source and in ssl_cipher_list_to_bytes()
the addition of SSL3_CK_SCSV
(openssl name for TLS_EMPTY_RENEGOTIATION_INFO_SCSV
) is hard-coded if renegotiation is off. And renegotiation has to be off for HTTP/2 per RFC7540. Can we just turn on renegotiation and get away with it?
I think the best option is to post-process the client hello generated by the code above and remove that signaling cipher from the list and to additional corrections if needed. In the same process one could also verify if the mirroring could be successfully done and issue a warning if not. Any opinions?
One question though: Is it enough to just clone the cipher list or are the extensions also part of the fingerprint?
That depends on the fingerprint being used. https://github.com/salesforce/ja3 is common, although I'd suspect that Cloudflare uses their own thingy.
Can we just turn on renegotiation and get away with it?
(I know as much as you do here)
I think the best option is to post-process the client hello generated by the code above and remove that signaling cipher from the list and to additional corrections if needed. In the same process one could also verify if the mirroring could be successfully done and issue a warning if not. Any opinions?
I'm afraid we kind of need to get our TLS library (OpenSSL curently) to emit the right thing, otherwise the crypto operations will fail later (you can't just modify the ClientHello and expect to get away with it). I'm afraid the options are 1) coerce OpenSSL to do what we want (not sure if possible), or 2) switch to rustls as an alternative TLS backend and modify it to our needs. I refuse to ship a custom patched version of OpenSSL (too much pain), but PyO3 is actually very awesome to use.
One question though: Is it enough to just clone the cipher list or are the extensions also part of the fingerprint?
That depends on the fingerprint being used. https://github.com/salesforce/ja3 is common, although I'd suspect that Cloudflare uses their own thingy.
Thanks for the link. That implementation checks more than the cipher list so that is a no then and this PR alone will not solve anything.
Can we just turn on renegotiation and get away with it?
(I know as much as you do here)
With the answer from above this question became irrelevant.
I think the best option is to post-process the client hello generated by the code above and remove that signaling cipher from the list and to additional corrections if needed. In the same process one could also verify if the mirroring could be successfully done and issue a warning if not. Any opinions?
I'm afraid we kind of need to get our TLS library (OpenSSL curently) to emit the right thing, otherwise the crypto operations will fail later (you can't just modify the ClientHello and expect to get away with it).
Are you sure? As long as the modification of the client_helo does not advertise a feature that openssl does not support, this should work? The frame is AFAIK not cryptographically secured so any functional modification such as these should pass:
- Remove or reorder ciphers, extensions, curves, etc.
- Add GREASE
- Add ciphers to the list that are unlikely to be picked because better ones are at the head of the list.
I'm afraid the options are 1) coerce OpenSSL to do what we want (not sure if possible), or 2) switch to rustls as an alternative TLS backend and modify it to our needs. I refuse to ship a custom patched version of OpenSSL (too much pain), but PyO3 is actually very awesome to use.
- sounds like a larger project. If 1) is somehow feasible that would be the best.
Are you sure? As long as the modification of the client_helo does not advertise a feature that openssl does not support, this should work? The frame is AFAIK not cryptographically secured so any functional modification such as these should pass:
The entire ClientHello directly feeds into the transcript hash (https://datatracker.ietf.org/doc/html/rfc8446#section-4.4.1), so modifications will be detected.
Are you sure? As long as the modification of the client_helo does not advertise a feature that openssl does not support, this should work? The frame is AFAIK not cryptographically secured so any functional modification such as these should pass:
The
Are you sure? As long as the modification of the client_helo does not advertise a feature that openssl does not support, this should work? The frame is AFAIK not cryptographically secured so any functional modification such as these should pass:
The entire ClientHello directly feeds into the transcript hash (https://datatracker.ietf.org/doc/html/rfc8446#section-4.4.1), so modifications will be detected.
You are right, my memory was a little rusty from the times where cipher downgrade attacks were still possible. :)
However, I did look at this a little more. Maybe it is possible to alter the client hello message by calling ssl3_init_finished_mac()
and then ssl3_finish_mac(altered client hello)
to restart the MAC. This is internally also done in response to a HelloRetryRequest
. I have not checked if the two functions are accessible from python though.
If that does not work, I think it will get tough, openssl just does not give enough control over the client hello construction process. And the use of TLS_EMPTY_RENEGOTIATION_INFO_SCSV
is the way openssl solves the renegotiation vulnerability. iOS uses a regegotiation_info
extension and you cannot get openssl to send that: https://github.com/openssl/openssl/issues/8143
Talking about fingerprinting, there's also Cloudflare's MALCOLM (https://github.com/cloudflare/mitmengine & https://malcolm.cloudflare.com/) which, as I get it, uses technique other than JA3 Anyway, I can say from my experience that ciphersuite list matters most in fingerprinting, but not everything (I've seen websites that also check extensions present).
Thanks for the pointer @fedosgad! I think it's not completely unintentional that OpenSSL does not support spoofing browser ClientHellos. Making rustls work would be a very fun undertaking, but also a considerable effort.
Talking about fingerprinting, there's also Cloudflare's MALCOLM (https://github.com/cloudflare/mitmengine & https://malcolm.cloudflare.com/) which, as I get it, uses technique other than JA3 Anyway, I can say from my experience that ciphersuite list matters most in fingerprinting, but not everything (I've seen websites that also check extensions present).
From my testings, the one actually matters the most is Extensions, I tried to match browser's ciphersuites, signature algorithms, ec point formats, supported groups and websites that use Cloudflare always return 403 status error because OpenSSL can't really spoof Extensions.
You can easily test with curl-impersonate, try to modify User-Agent, even ciphersuite of Chrome and it still passes Cloudflare's checks, because Extensions are still matched with Chrome. In fact I changed heavily chrome_104.config to:
--ciphers TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-CHACHA20-POLY1305
--http2
--false-start
--compressed
--tlsv1.2
--no-npn
--alps
--cert-compression brotli
--location
And chrome_104.headers to:
sec-ch-ua: "Chromium";v="104", " Not A;Brand";v="99", "Google Chrome";v="104"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:102.0) Gecko/20100101 Goanna/5.2 Firefox/102.0 PaleMoon/31.3.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Test cmd (this website uses Cloudflare's anti bot settings):
curl_chrome104.bat https://alternativeto.net
And it stills works, even with heavily utterly modified Ciphersuite and User-agent.
Now, change chrome_104.config again to:
--ciphers TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-CHACHA20-POLY1305
--http2
--false-start
--compressed
--tlsv1.2
--alps
--cert-compression brotli
--location
Run above cmd again, and Cloudflare will return 403 because I removed --no-npn to add NPN extension to Chrome, and that's a death sentence because I changed Extensions.
So my conclusions, for Cloudflare:
- Ciphersuite doesn't really matter
- User-agent doesn't matter
- Extension matters
Link: https://github.com/depler/curl-impersonate-win
Closing this PR because:
- I don't see a way to make OpenSSL fully mimic some given fingerprint.
- I don't want this old PR to consume developers' time.
- I made a tool specifically for mimic tasks, albeit not as comfortable as mitmproxy - https://github.com/fedosgad/mirror_proxy/.
Thanks everyone who participated in discussion and special thanks to @mhils for code review and valuable hints about it!