Fix regression in --fail-http-to-httpsuri - #735
Merged
Conversation
…erver has accomodated HTTP just enough to instruct the user to do so. It's not quite the same as a handshake error, tls error or application error. It seems to me like something that deserves its own sort of exception type
phillip-stephens
self-requested a review
June 12, 2026 03:10
phillip-stephens
approved these changes
Jun 12, 2026
phillip-stephens
left a comment
Contributor
There was a problem hiding this comment.
Added an integration test, but otherwise this looks good! Seems reasonable and like you say, the code is already there. Thanks for this!
phillip-stephens
enabled auto-merge (squash)
June 12, 2026 03:22
Contributor
Author
Damn, you’re fast! Thanks 🙏 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Unless I missed a discussion, I think there was an unintentional regression in the code that supported the
--fail-http-to-httpsflag. The flag is still implemented, and 99% of the logic is still there, it's just one small piece means the condition gets logged and a short-circuit occurs rather than a retry.Refresher
The purpose of this was to provide some intelligent ways to deal with the small number of widely used implementations which, in certain configurations, responded to HTTP requests made on HTTPS ports. They provided HTTP 400 with a helpful error message - we identified a few in specific and supported those explicitly:
zgrab2/modules/http/scanner.go
Lines 569 to 576 in e91fc98
Rather than treat it as a handshake failure, or any other sort of failure, it's treated as a special failure that just means "even though a valid HTTP response came back, retry using HTTPS"
Without this, HTTPS endpoints were just returning blank 400 pages to zgrab2, a very quiet data loss behavior.
How to Test (Server Side)
You can either set up nginx, apache, etc. with the configuration that emits one of the supported warnings, or you can simulate it with this hacky nonsense, which I have named
friendly_server.py:How To Test: zgrab Command
Expected Output
{ "ip": "2.2.2.1", "data": { "http": { "status": "success", "protocol": "http", "port": 8443, "result": { "response": { "status_line": "200 OK", "status_code": 200, "protocol": "HTTP/1.1", "protocol_major": 1, "protocol_minor": 1, "headers": { "content_length": [ "15" ], "content_type": [ "text/plain" ] }, "body": "hello over tls\n", "body_sha256": "033aef276ffec11a316130c1434baea9657f0ab764e9adc999a860b9435168f2", "content_length": 15, "request": { "url": { "scheme": "https", "host": "2.2.2.1:8443", "path": "/" }, "method": "GET", "protocol": "HTTP/1.1", "protocol_major": 1, "protocol_minor": 1, "headers": { "accept": [ "*/*" ], "user_agent": [ "Mozilla/5.0 zgrab/0.x" ] }, "host": "2.2.2.1:8443", "tls_log": { "handshake_log": { "server_hello": { "version": { "name": "TLSv1.2", "value": 771 }, "random": "ndhxIy7DkJ6VaLzVhsekF5uhT8PA7+EVl7/0hTY7Crg=", "session_id": "HHHC6OMOb/kIVtLokHkP+UNpJ867Jns4goAUEysM9xY=", "cipher_suite": { "hex": "0x1302", "name": "TLS_AES_256_GCM_SHA384", "value": 4866 }, "compression_method": { "hex": "0x00", "name": "NULL", "value": 0 }, "ocsp_stapling": false, "ticket": false, "secure_renegotiation": false, "heartbeat": false, "extended_master_secret": false, "supported_versions": { "selected_version": { "name": "TLSv1.3", "value": 772 } }, "key_share": { "hex": "0x001D", "name": "x25519", "value": 29 }, "extension_identifiers": [ 43, 51 ] }, "server_certificates": { "certificate": { "raw": "MIIDCTCCAfGgAwIBAgIUQ4qPGqiRya3PLzXEbPlPQdZTlBMwDQYJKoZIhvcNAQELBQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDYxMjAwNDY0NloXDTI3MDYxMjAwNDY0NlowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp92H0VIpJFaWkI7f27Gb8QLKK9QWgGHVYNcUiUTrxdXHf1g5az2BtDd/Ix5rSrjvW5sVFVdBLRT8iRbLRQGvCqCzktCuVc6t476DujGKH+BdquO0GJFB7X5U6eYhyUqpqyWI6YlewI48GM7CaGsE9q76nZ1mLgLCKV0sptSDoNA8wtWMU3xndIX++6FQmrJFk15mOeF0zG6dgZh4ejdT9MrpFuFz2qjJ+U5jEQpxZKiHjunGUz/ivsLb+6ftf/nvLc+L7sPQAKErw5W42RKtiBQ0E/3HdDBaLOS2usu6jduGeiiMZm8+38ef3MXMiIX7BTFpfODwVzDn9zzEBjkv8QIDAQABo1MwUTAdBgNVHQ4EFgQUfRTTkEOPTZXW6j+Y96CxjaBvuT0wHwYDVR0jBBgwFoAUfRTTkEOPTZXW6j+Y96CxjaBvuT0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAUSz8NlyMv7to1MLULCZa3sdPHSHCxPd/nM0eJmyKP+NyE4pzxK/YfMcPkgiPdWL3zH3ESYKcULlTYv2tUjQyt7nrzna9823/ERgVtg/eA4xqeFYJ5WdCXCBl94mWZouC0AyOCxnpzSEzbrc7LLOjRMFJk952I/HMnA7l4mE+BA1eGGy4VCyKoWXJU/L3Crm4JLng7hDvTJnQPwWKsJHSmPqgKikHlHuwAIrtumFaK8PbMYvZiMKabUxDGwHxtTFp+j+ztm/Wy97jHmw05ROT8PQoRd/ccdbFJ/XG+pb/r/TthXB4Y5avS5y5vSjQAWly60K5UWZZGOQM6MCPuPvI/Q==", "parsed": { "version": 3, "serial_number": "385592350610891532648099422262299092915500323859", "signature_algorithm": { "name": "SHA256-RSA", "oid": "1.2.840.113549.1.1.11" }, "issuer": { "common_name": [ "localhost" ] }, "issuer_dn": "CN=localhost", "validity": { "start": "2026-06-12T00:46:46Z", "end": "2027-06-12T00:46:46Z", "length": 31536000 }, "subject": { "common_name": [ "localhost" ] }, "subject_dn": "CN=localhost", "subject_key_info": { "key_algorithm": { "name": "RSA" }, "rsa_public_key": { "exponent": 65537, "modulus": "p92H0VIpJFaWkI7f27Gb8QLKK9QWgGHVYNcUiUTrxdXHf1g5az2BtDd/Ix5rSrjvW5sVFVdBLRT8iRbLRQGvCqCzktCuVc6t476DujGKH+BdquO0GJFB7X5U6eYhyUqpqyWI6YlewI48GM7CaGsE9q76nZ1mLgLCKV0sptSDoNA8wtWMU3xndIX++6FQmrJFk15mOeF0zG6dgZh4ejdT9MrpFuFz2qjJ+U5jEQpxZKiHjunGUz/ivsLb+6ftf/nvLc+L7sPQAKErw5W42RKtiBQ0E/3HdDBaLOS2usu6jduGeiiMZm8+38ef3MXMiIX7BTFpfODwVzDn9zzEBjkv8Q==", "length": 2048 }, "fingerprint_sha256": "50e2c65e7440bd6d5e1560abc2c461bf5f47a8b3a837db7fbafb1d4885f29888" }, "extensions": { "basic_constraints": { "is_ca": true }, "authority_key_id": "7d14d390438f4d95d6ea3f98f7a0b18da06fb93d", "subject_key_id": "7d14d390438f4d95d6ea3f98f7a0b18da06fb93d" }, "signature": { "signature_algorithm": { "name": "SHA256-RSA", "oid": "1.2.840.113549.1.1.11" }, "value": "USz8NlyMv7to1MLULCZa3sdPHSHCxPd/nM0eJmyKP+NyE4pzxK/YfMcPkgiPdWL3zH3ESYKcULlTYv2tUjQyt7nrzna9823/ERgVtg/eA4xqeFYJ5WdCXCBl94mWZouC0AyOCxnpzSEzbrc7LLOjRMFJk952I/HMnA7l4mE+BA1eGGy4VCyKoWXJU/L3Crm4JLng7hDvTJnQPwWKsJHSmPqgKikHlHuwAIrtumFaK8PbMYvZiMKabUxDGwHxtTFp+j+ztm/Wy97jHmw05ROT8PQoRd/ccdbFJ/XG+pb/r/TthXB4Y5avS5y5vSjQAWly60K5UWZZGOQM6MCPuPvI/Q==", "valid": true, "self_signed": true }, "fingerprint_md5": "8928f54c4edf0ec3abd948f428ed70f4", "fingerprint_sha1": "1f732f323862ca587e4cb10604d565c6281e7eb2", "fingerprint_sha256": "4061fd5a466185b94329cc3a3bc503440878429393be058210b5ab9e049434dd", "tbs_noct_fingerprint": "362c588e6ac9e80a72e63c5d5eb9af9a37dc37c720a30dab25bc493c547a7642", "spki_subject_fingerprint": "8fa3bb1a409b639928481b58c1cd0f7990a73cda1396eb05e5feaa567e1f9d1a", "tbs_fingerprint": "362c588e6ac9e80a72e63c5d5eb9af9a37dc37c720a30dab25bc493c547a7642", "validation_level": "unknown", "redacted": false } }, "validation": { "browser_trusted": false, "browser_error": "x509: certificate is self-signed and not a trusted root" } } }, "ja3s": "15af977ce25de452b96affa2addb1036", "handshake_completed_successfully": true } } } }, "timestamp": "2026-06-11T20:56:39-04:00" } } }Actual Output
{ "ip": "2.2.2.1", "data": { "http": { "status": "protocol-error", "protocol": "http", "port": 8443, "result": { "response": { "status_line": "400 Bad Request", "status_code": 400, "protocol": "HTTP/1.1", "protocol_major": 1, "protocol_minor": 1, "headers": { "content_length": [ "45" ], "content_type": [ "text/plain" ] }, "content_length": 45, "request": { "url": { "scheme": "http", "host": "2.2.2.1:8443", "path": "/" }, "method": "GET", "protocol": "HTTP/1.1", "protocol_major": 1, "protocol_minor": 1, "headers": { "accept": [ "*/*" ], "user_agent": [ "Mozilla/5.0 zgrab/0.x" ] }, "host": "2.2.2.1:8443" } } }, "timestamp": "2026-06-11T20:56:59-04:00", "error": "NGINX or Apache HTTP over HTTPS failure" } } }It detected the unique situation, but it didn't perform the retry, which means it ultimately didn't return any data
Notes & Caveats
Issue Tracking
--retry-http#273Screenshot (Regression)
Screenshot (Fixed)