Introduction

Honeypots are computers that masquerade as vulnerable systems to attract potential attackers. By mimicking weak or outdated software, honeypots entice attackers looking for an easy target. This provides security professionals with insight into attackers’ tactics, techniques, and procedures (TTPs), helping them understand and mitigate potential threats more effectively. In addition, honeypots are a fundamental detection and intelligence-gathering tool for many threat intelligence providers.

In this blog series we will detail how to evade honeypots using JA3 hash randomization to enumerate and analyze the honeynets of threat intelligence providers. These providers frequently supply their intelligence to different security tools and services, like Web Application Firewalls (WAFs) and Content Delivery Networks (CDNs). By accurately mapping and excluding their honeynets, scans can be conducted more discreetly, making detection considerably more challenging. While honeynets are a valuable tool and an important part of threat feed intelligence, it’s good to remember that savvy attackers can evade these kinds of routine detection mechanisms and organizations should seek to include security measures that focus on application/API usage and behavior patterns like the Ghost platform.

In part 1 of this blog we’ll define and describe the JA3 hash and discuss the tools we used and how to randomize the JA3 hash.

In part 2 we’ll show how to enumerate a honeynet and answer the following questions:

  • What are the IP’s of the honeypots so we can avoid them?
  • Who hosts the honeypots? In what cloud services are the honeypots deployed, and thus what networks can they effectively protect?
  • Can we easily and accurately fingerprint the honeypots?
  • Can we build a Censys/Shodan search string using these fingerprints?

In part 3 we’ll describe how to analyze a honeynet and present some of our findings including:

  • The specific vulnerabilities flagged by the honeypots (the CVEs they can “see”)
  • The vulnerability categories prioritized by the honeynet
  • The services the honeypots mimic

JA3 Hash

The JA3 hash is a fingerprinting technique used in network security to identify and classify the cryptographic properties of a TLS (Transport Layer Security) handshake. It has been a staple for incident response teams and threat intelligence platforms since its fingerprinting technique was published by Salesforce in 2017. The hash allows for quick identification of hacking tools like Cobalt Strike, Sliver beacons, and SQLmap, and is reliable over time because threat actors often reuse such tools without modification.

When a client and server establish a secure connection over TLS, they exchange a series of messages called the handshake. The JA3 hash is generated by analyzing the specific characteristics of this handshake, such as the TLS version, supported cipher suites, and extensions used. The combination of these attributes is unique to the cryptographic configuration of each TLS client or server.

TLS Handshake

The TLS handshake

The JA3 hash is valuable in network security monitoring. By comparing the JA3 hash of a network connection against known hashes associated with malicious activity, security professionals can identify potentially malicious TLS connections, and even what specific tools are being employed by the threat actor.

Since the JA3 hash is generated purely from the handshake parameters and does not involve decrypting the actual TLS traffic, it provides a useful tool for identifying and flagging suspicious TLS connections without violating the privacy and confidentiality of the communication.

Fingerprinting Countermeasures

For better coverage of internet-wide reconnaissance, like scanning for devices with specific vulnerabilities (as Shodan and Censys do regularly), it’s necessary to bypass the protection of cloud-based Web Application Firewalls (WAFs) and Content Delivery Networks (CDNs) and to avoid honeynets deployed randomly throughout the internet by threat intelligence providers and other research institutions.

Scans performed without sufficient fingerprinting countermeasures will be blocked by WAFs and CDNs or detected by honeynets and end up on threat intelligence feeds. This will result in scans being blind to significant portions of cloud infrastructure where those feeds are a source of Indicators of Compromise (IOCs). A typical way to mask scan traffic is to use a rotating proxy service to randomize the source IP (with high reputation IPs) and the User-Agent header in each request. This by itself is enough to bypass most WAFs, but not to scan targets behind cloud-based WAFs or CDNs like CloudFlare. Scanning targets behind those technologies requires more advanced techniques, such as cloning the header order for a common browser like Chrome, mimicking User-Agent strings, or cloning or randomizing the JA3 hash of the browser configuration being spoofed.

While these countermeasures can be successful at hiding scans from threat intelligence honeynets, they aren’t guaranteed to work. The only guarantee of evading detection by honeynets is to avoid scanning them, but this requires knowing the IP address of each honeypot.

Our approach to determining the IP addresses of a threat intelligence company’s honeypots is founded in JA3 hash randomization. Since AWS is a likely place to deploy a honeypot, we started with aggressive scans against all AWS’ published IPv4 prefixes. We rotated the JA3 hash for each request so it was unique and used with only one target IP address. We then searched for the IP of our scanner box using the webapp and API for one of our target feeds and found that over 200 of our JA3 hashes had been recorded. By correlating those JA3 hashes back to our scan targets we were able to eventually confirm the IPs of 200+ honeypots. At this point we could have followed the same process for other large IP blocks, but we opted for an optimized approach of using additional fingerprints from the confirmed honeypots to generate smaller target IP blocks. We will provide more details on this process in part 2 of this blog.

JA3 Randomization

Most reconnaissance tools have an option to customize HTTP headers. Changing the headers can help users evade detection techniques like User-Agent string analysis and header order analysis. What these tools don’t provide is a way to avoid JA3 detection. This requires a feature that allows users to customize or randomize the TLS attributes that are used in calculating the JA3 hash.

When we started this research we intended to use nuclei, Project Discovery’s vulnerability scanner, for our scanning. However, we learned that its JA3 hash was the same across most builds. We found this was also true for BurpSuite****************and httpx, another Project Discovery tool that shares common libraries with nuclei. This makes it easy to detect and block these tools using their JA3 hashes. We’ve noticed this happening more often with BurpSuite.

We submitted a feature request to Project Discovery to include a new flag (-tlsi, -tls-impersonate) in nuclei and httpx for randomizing the JA3 hash, and this feature is now part of the latest build. While this does randomize the JA3 hash, nuclei does not include the hashes in any of its logs making it difficult to correlate the hashes with the target IP addresses.

Looking for an alternative that would log the hashes and target IP’s, we created a setup using an nginx proxy to capture the requests and an nginx server running on the same machine to capture the responses. Using the proxy, we performed tests with BurpSuite , nuclei, and httpx to see what impact the proxy and various flags has on their JA3 hashes. We learned that although we were able to proxy nuclei and httpx (using the -proxy flag) and to log the hashes and IP’s, the JA3 hash randomization doesn’t work correctly when used in conjunction with the -proxy flag. The findings from our testing are described in the BurpSuite and httpx/nuclei sections below. Although this wasn’t our final setup, we’ve included our nginx build script and configuration files at the end of the blog.

JA3 randomization can be done with Python's aiohttp and requests libraries (we’ve included two example python scripts at the end of the blog), but in the end we decided to scan with httpx and nuclei, use tcpdump to capture the traffic during scanning, and then process the resulting pcap files with ja3.py, a JA3 python script from SalesForce. One advantage of the nginx proxy and python options is that you can map the JA3 hashes and target IP addresses in realtime, whereas the ja3.py script is run offline after scanning is complete.

BurpSuite

We sent requests from BurpSuite’s built in Chromium browser through our nginx proxy. The json below shows a portion of the metadata for one of those requests. Depending on configuration options, Burp produces one of two JA3 hashes:62f6a6727fda5a1104d5b147cd82e520 or 8bd06f4341a65d44a68bd2cef7cbedc6. In fact, if you proxy a tool through BurpSuite, such as proxying python’s requests library through Burp, you will assume BurpSuites’s JA3 hash shown below. Organizations are increasingly tracking these JA3 hashes which is why we’ve seen more websites blocking Burp.

	
		{
  "proxy_addr": "<redacted-ip>",
  "request": "GET / HTTP/1.1",
  "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.5790.171 Safari/537.36",
  "http_ssl_ja3_hash": "8bd06f4341a65d44a68bd2cef7cbedc6",
}

httpx/nuclei

nuclei and httpx are both built on the golang net/http library which uses crypto/tls. We wanted to see whether nuclei, httpx, and a basic net/http golang program share the same JA3 hashes. In order to make this comparison we wrote a basic golang net/http program and then issued requests from all three programs through our nginx proxy.

The golang program included at the end of the blog issues an http request using net/http. When run, it yields a JA3 hash of 3fed133de60c35724739b913924b6c24, which we’ve concluded is the default hash when using the standard values for the net/http library and, as we’ll show below, is a different hash than the ones shared by nuclei and httpx.

When httpx is run without the -tlsi flag it yields the same JA3 hash for both requests as shown below. The first request is the proxy/TLS connection which tells the proxy to initiate a connection to our proxy. The second request is the one forwarded by the proxy once the connection has been established. Note the JA3 hash is the same and the User-Agent has been randomized in the second request.

	
		{
  "proxy_addr": "<httpx_scan_box>",
  "request": "CONNECT <proxy_box>:443 HTTP/1.1",
  "user_agent": "Go-http-client/1.1",
  "remote_addr": "<proxy_box>:443",
  "http_ssl_ja3_hash": "473cd7cb9faa642487833865d516e578",
}

{
  "proxy_addr": "<proxy_box>",
  "request": "GET /AAAAAAAA HTTP/1.1",
  "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36",
  "remote_addr": "",
  "http_ssl_ja3_hash": "473cd7cb9faa642487833865d516e578",
}

When httpx is run with the -tlsi flag the JA3 hash is randomized as expected for the proxy CONNECT request, but the second request has the same default hash we saw in the last test.

	
		{
  "proxy_addr": "<httpx_box>",
  "request": "CONNECT droplet.blackcastle.io:443 HTTP/1.1",
  "user_agent": "Go-http-client/1.1",
  "remote_addr": "<proxy_box>:443",
  "http_ssl_ja3_hash": "c3f33ece9f68f8298b5544f3ac2aeddd",
}

{
  "proxy_addr": "<proxy_box>",
  "request": "GET /AAAAAAAA HTTP/1.1",
  "user_agent": "Mozilla/5.0 (Linux; Android 10; ONE A2003) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.61 Mobile Safari/537.36",
  "remote_addr": "",
  "http_ssl_ja3_hash": "473cd7cb9faa642487833865d516e578",
}

When we run httpx with the -tlsi flag, but without the -proxy flag, the request has a random JA3 hash, showing that JA3 randomization does work, but not with the -proxy flag. We confirmed this theory using a different proxy service (OxyLabs). We again saw the same default hash 473cd7cb9faa642487833865d516e578 which confirms the JA3 is randomized only to the proxy, and subsequent requests use the standard nuclei/httpx JA3 hash.

	
		{
  "proxy_addr": "<OxyLabs_Proxy_IP>",
  "request": "GET /AAAAAAAA HTTP/1.1",
  "user_agent": "Java/1.6.0_13",
  "remote_addr": "",
  "http_ssl_ja3_hash": "473cd7cb9faa642487833865d516e578",
}

JA3 randomization is also affected by the -unsafe flag which allows httpx to make intentionally broken HTTP requests such as the following:

GET / HTTP/1.1\r\nHost:\r\nGET /metadata HTTP/1.1\r\nHost: 169.254.169.254

This is an example of request smuggling; a single request that actually contains two requests. When -unsafe is specified, both nuclei and httpx forward the request as-is allowing the user to potentially exploit a vulnerability in the receiver. Without -unsafe, both nuclei and httpx strip everything after the first Host header.

The output below shows the impact of the -unsafe flag on the JA3 hash for the same httpx command, with and without -proxy.

	
		httpx -u https://server.io -tlsi -path "http://icanhazip.com/bar" -unsafe -proxy <proxy URL>
# Request recv: GET /bar HTTP/1.1
# JA3 recv: 19e29534fd49dd27d09234e639c4057e
httpx -u https://server.io -tlsi -path "http://icanhazip.com/bar" -unsafe
# Request recv: GET http://icanhazip.com/bar HTTP/1.1
# JA3 recv: 19e29534fd49dd27d09234e639c4057e

Notice that both requests have the same JA3 hash: 19e29534fd49dd27d09234e639c4057e. When -unsafe is used, httpxand nuclei ignore -tlsi and use a static JA3 hash. Depending on the scan configuration, that hash will be either 19e29534fd49dd27d09234e639c4057e or 473cd7cb9faa642487833865d516e578. As an aside, it appears that the -proxyflag interferes with the -unsafe flag. When -unsafe is supplied the -path value should be forwarded as-is, but the output above shows that when -proxy is used, -unsafe is ignored and the path is truncated anyway.

Conclusion

In part 1 of this series we’ve defined the JA3 hash, discussed its usefulness for detecting malicious tools and evading honeypot detection, and given examples of tools and configurations that can be used for JA3 randomization. Although we’ve focused on randomizing JA3 hashes, there is significant value in being able to specify custom JA3 hashes. This would allow pentesters and attackers to impersonate the JA3 hash of common tools or browsers and decrease the likelihood of being detected. For example, we asked multiple iPhone owners, all with different hardware, software patch levels and so on, to make a request to our proxy. As we expected, the JA3 was the same across all devices. This would be a good impersonation candidate.

Tool JA3 hashes observed Notes
nuclei 473cd7cb9faa642487833865d516e578 19e29534fd49dd27d09234e639c4057e The “19e” hash value is observed only when sending ‘unsafe’ raw requests.
httpx 473cd7cb9faa642487833865d516e578 19e29534fd49dd27d09234e639c4057e The “19e” hash value is observed only when sending ‘unsafe’ raw requests.
golang net/http 3fed133de60c35724739b913924b6c24  
BurpSuite 8bd06f4341a65d44a68bd2cef7cbedc6 62f6a6727fda5a1104d5b147cd82e520 The 62f hash value was observed when performing the first request to a page, all subsequent requests were the 8bd value. If you proxy a tool through Burp, it will always have the 8bd value.
python requests 6776eeb3122863bfadbffae2afb4dd8d  
python aiohttp 047bfa6321a7921f57d5b91d04dcedaf  

Ready for more? Continue on to Part 2 now: An Attacker's Guide to Evading Honeypots Part 2

Additional resources

nginx proxy build script

	
		#!/bin/bash
mkdir build
cd build
git clone -b OpenSSL_1_1_1-stable --depth=1 https://github.com/openssl/openssl
git clone -b release-1.21.3 --depth=1 https://github.com/nginx/nginx
git clone https://github.com/phuslu/nginx-ssl-fingerprint
git clone https://github.com/chobits/ngx_http_proxy_connect_module.git

patch -p1 -d openssl < nginx-ssl-fingerprint/patches/openssl.1_1_1.patch
patch -p1 -d nginx < nginx-ssl-fingerprint/patches/nginx.patch
patch -p1 -d nginx < ngx_http_proxy_connect_module/patch/proxy_connect_rewrite_102101.patch

cd nginx
ASAN_OPTIONS=symbolize=1 ./configure --with-openssl=$(pwd)/../openssl --add-module=$(pwd)/../nginx-ssl-fingerprint --with-http_ssl_module --with-stream_ssl_module --with-debug --with-stream --with-cc-opt="-fsanitize=address -O -fno-omit-frame-pointer" --with-ld-opt="-L/usr/local/lib -Wl,-E -lasan" --add-module=$(pwd)/../ngx_http_proxy_connect_module
make

nginx proxy configuration:

	
		

worker_processes auto;
#daemon on;
pid /run/nginx.pid;
#include /etc/nginx/modules-enabled/*.conf;
error_log /var/log/nginx/error.log;
events {
    worker_connections  1024;
}

http {
    log_format custom escape=json '{"proxy_addr": "$remote_addr",'
                                    '"time_local": "$time_local",'
                                    '"request": "$request", "request_method": "$request_method", "request_uri": "$request_uri",'
                                    '"status": $status,'
                                    '"user_agent": "$http_user_agent",'
                                    '"remote_addr": "$connect_addr",'
                                    '"http_ssl_ja3": "$http_ssl_ja3", "http_ssl_ja3_hash": "$http_ssl_ja3_hash",'
                                    '"request_body": "$request_body"}';
    server {
        resolver                       8.8.8.8;
        access_log /var/log/nginx/access.log custom;
        proxy_busy_buffers_size 512k;
        proxy_buffers 4 512k;
        proxy_buffer_size 256k;
        proxy_connect;
        proxy_connect_allow    443 563 8443; # Or whatever port you want to forward CONNECTs on.
        proxy_connect_timeout    3s;
        proxy_connect_read_timeout    5s;
        proxy_connect_send_timeout    3s;

        listen                 0.0.0.0:1337 ssl;
        ssl_protocols          TLSv1.2 TLSv1.1 TLSv1;
        ssl_dhparam            /etc/nginx/ssl/dhparam.pem;
        ssl_prefer_server_ciphers   on;
        ssl_ciphers            "ECDHE-ECDSA-AES128-GCM-SHA256:EECDH:!DSS:DHE-RSA-AES128-GCM-SHA256:EECDH+ECDSA+SHA384:!MD5:ECDHE-RSA-AES256-GCM-SHA384:!LOW:ECDHE-ECDSA-AES256-GCM-SHA384:!3DES:!eNULL:EECDH+aRSA+RC4:!aNULL:EECDH+ECDSA+SHA256:ECDHE-RSA-AES128-GCM-SHA256:EECDH+ECDSA+AESGCM:EECDH+aRSA+SHA256:EECDH+aRSA+SHA384:RC4:EDH+aRSA:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:!PSK:!SRP:!EXP:EECDH+aRSA+AESGCM";
        ssl_ecdh_curve         "prime256v1";
        ssl_certificate_key    /etc/nginx/ssl/server.key;
        ssl_certificate        /etc/nginx/ssl/server.crt;
        location ^~ / {
            proxy_pass             $scheme://$hostname;
        }
    }
}

nginx server configuration

	
		worker_processes auto;
#daemon on;
pid /run/nginx.pid;
#include /etc/nginx/modules-enabled/*.conf;
error_log /var/log/nginx/error.log;
events {
    worker_connections  1024;
}

http {
    log_format custom escape=json '{"proxy_addr": "$remote_addr",'
                                    '"time_local": "$time_local",'
                                    '"request": "$request", "request_method": "$request_method", "request_uri": "$request_uri",'
                                    '"status": $status,'
                                    '"user_agent": "$http_user_agent",'
                                    '"remote_addr": "$connect_addr",'
                                    '"http_ssl_ja3": "$http_ssl_ja3", "http_ssl_ja3_hash": "$http_ssl_ja3_hash",'
                                    '"request_body": "$request_body"}';
    server {
        resolver                       8.8.8.8;
        access_log /var/log/nginx/access.log custom;
        listen                 0.0.0.0:443 ssl;
        ssl_protocols          TLSv1.2 TLSv1.1 TLSv1;
        ssl_dhparam            /etc/nginx/ssl/dhparam.pem;
        ssl_prefer_server_ciphers   on;
        ssl_ciphers            "ECDHE-ECDSA-AES128-GCM-SHA256:EECDH:!DSS:DHE-RSA-AES128-GCM-SHA256:EECDH+ECDSA+SHA384:!MD5:ECDHE-RSA-AES256-GCM-SHA384:!LOW:ECDHE-ECDSA-AES256-GCM-SHA384:!3DES:!eNULL:EECDH+aRSA+RC4:!aNULL:EECDH+ECDSA+SHA256:ECDHE-RSA-AES128-GCM-SHA256:EECDH+ECDSA+AESGCM:EECDH+aRSA+SHA256:EECDH+aRSA+SHA384:RC4:EDH+aRSA:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:!PSK:!SRP:!EXP:EECDH+aRSA+AESGCM";
        ssl_ecdh_curve         "prime256v1";
        ssl_certificate_key    /etc/nginx/ssl/server.key;
        ssl_certificate        /etc/nginx/ssl/server.crt;
        location ^~ / {
            return                  200 '{"ua": "$http_user_agent"}\\n';
        }
    }
}

python asyncio script 1

	
		import sys
import ssl
import random
import asyncio
from fake_useragent import UserAgent
from aiohttp import ClientSession, ClientTimeout

def get_randomized_ciphers():
    ciphers = [
        "DHE-RSA-AES128-SHA",
        "DHE-RSA-AES256-SHA",
        "ECDHE-ECDSA-AES128-GCM-SHA256",
        "ECDH+AESGCM",
        "DH+AESGCM",
        "ECDH+AES256",
        "DH+AES256",
        "ECDH+AES128",
        "DH+AES",
        "ECDH+HIGH",
        "DH+HIGH",
        "ECDH+3DES",
        "DH+3DES",
        "RSA+AESGCM",
        "RSA+AES",
        "RSA+HIGH",
        "RSA+3DES",
        "!aNULL",
        "!eNULL",
        "!MD5"
    ]
    random.shuffle(ciphers)
    ciphers = ciphers[:-random.randint(1, 5)]
    ciphers = ':'.join(ciphers)
    print(f"[-] Using CIPHERS: {ciphers}")

    return ciphers


async def fetch(url, session, proxy_url):
    # create the SSL context to use for the request
    ssl_context = ssl.create_default_context()
    ssl_context.set_ciphers(get_randomized_ciphers())
    ssl_context.check_hostname = False
    ssl_context.verify_mode = ssl.CERT_NONE

    ua = UserAgent()

    headers = {
        "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",
        # "accept-encoding": "gzip, deflate, br",
        "accept-language": "en-US,en;q=0.9",
        "cache-control": "max-age=0",
        "referer": f"<https://google.com/>",
        f"sec-ch-ua": '"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"',
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": '"macOS"',
        "sec-fetch-dest": "document",
        "sec-fetch-mode": "navigate",
        "sec-fetch-site": "same-origin",
        "sec-fetch-user": "?1",
        "upgrade-insecure-requests": "1",
        "user-agent": ua.chrome
                # NOTE: Censys is known to randomize JA3 hashes!
        #"user-agent": "Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)"
    }

    async with session.get(url, proxy=proxy_url, ssl=ssl_context, headers=headers) as response:
        print(f"{response.text()}")
        return await response.text()


async def bound_fetch(sem, url, session, proxy_url):
    # Getter function with semaphore.
    async with sem:
        await fetch(url, session, proxy_url)


async def run(r):
        proxy_url = sys.argv[1]
    url = "https://icanhazip.com/{}"
    tasks = []
    # create instance of Semaphore
    sem = asyncio.Semaphore(1000)
    # Create client session that will ensure we dont open new connection
    # per each request.
    t_out = 10
    timeout = ClientTimeout(total=None, sock_connect=t_out, sock_read=t_out)
    async with ClientSession(timeout=timeout) as session:
        for i in range(r):
            # pass Semaphore and session to every GET request
            task = asyncio.ensure_future(bound_fetch(sem, url.format(i), session, proxy_url))
            tasks.append(task)

        responses = asyncio.gather(*tasks)
        await responses

if __name__ == "__main__":
    number = 10
    asyncio.run(run(number))

python requests script 2

	
		import sys
import random
from requests import sessions
from fake_useragent import UserAgent
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.ssl_ import create_urllib3_context
#from urllib3.util.ssl_ import create_urllib3_context

class DESAdapter(HTTPAdapter):
    """
    A TransportAdapter that randomizes JA3 in Requests.
    """

    def get_randomized_ciphers(self):
        ciphers = [
            "DHE-RSA-AES128-SHA",
            "DHE-RSA-AES256-SHA",
            "ECDHE-ECDSA-AES128-GCM-SHA256",
            "ECDH+AESGCM",
            "DH+AESGCM",
            "ECDH+AES256",
            "DH+AES256",
            "ECDH+AES128",
            "DH+AES",
            "ECDH+HIGH",
            "DH+HIGH",
            "ECDH+3DES",
            "DH+3DES",
            "RSA+AESGCM",
            "RSA+AES",
            "RSA+HIGH",
            "RSA+3DES",
            "!aNULL",
            "!eNULL",
            "!MD5"
        ]
        random.shuffle(ciphers)
        ciphers = ciphers[:-random.randint(1, 5)]
        ciphers = ':'.join(ciphers)
        print(f"[-] Using CIPHERS: {ciphers}")

        return ciphers

    def init_poolmanager(self, *args, **kwargs):
        context = create_urllib3_context(ciphers=self.get_randomized_ciphers())
        kwargs['ssl_context'] = context
        context.check_hostname = False
        return super(DESAdapter, self).init_poolmanager(*args, **kwargs)

    def proxy_manager_for(self, *args, **kwargs):
        context = create_urllib3_context(ciphers=self.get_randomized_ciphers())
        kwargs['ssl_context'] = context
        context.check_hostname = False
        return super(DESAdapter, self).proxy_manager_for(*args, **kwargs)


def main():
    ua = UserAgent()

    headers = {
        "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",
        # "accept-encoding": "gzip, deflate, br",
        "accept-language": "en-US,en;q=0.9",
        "cache-control": "max-age=0",
        "referer": f"<https://google.com/>",
        f"sec-ch-ua": '"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"',
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": '"macOS"',
        "sec-fetch-dest": "document",
        "sec-fetch-mode": "navigate",
        "sec-fetch-site": "same-origin",
        "sec-fetch-user": "?1",
        "upgrade-insecure-requests": "1",
        "user-agent": ua.chrome
                # NOTE: Censys is known to randomize JA3 hashes!
        #"user-agent": "Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)"
    }

    base_url = sys.argv[1]
    proxy_url = sys.argv[2]
    proxies = {
        "http": proxy_url,
        "https": proxy_url,
    }

    s = sessions.Session()
    s.mount(base_url, DESAdapter())  # Patch in the new ciphers for this domain.
    resp = s.get(f"{base_url}", headers=headers, verify=False, proxies=proxies)
    print(f"Response: {resp.content}")


if __name__ == "__main__":
    main()

 golang program

	
		package main

import (
        "errors"
        "fmt"
        "net/http"
        "os"
        "time"
        "crypto/tls"
)

const serverPort = 443

func main() {
        go func() {
                mux := http.NewServeMux()
                mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
                        fmt.Printf("server: %s /\n", r.Method)
                })
                server := http.Server{
                        Addr:    fmt.Sprintf(":%d", serverPort),
                        Handler: mux,
                }
                if err := server.ListenAndServe(); err != nil {
                        if !errors.Is(err, http.ErrServerClosed) {
                                fmt.Printf("error running http server: %s\n", err)
                        }
                }
        }()

        time.Sleep(100 * time.Millisecond)
    http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
        requestURL := fmt.Sprintf("https://droplet.blackcastle.io:%d", serverPort)
        res, err := http.Get(requestURL)
        if err != nil {
                fmt.Printf("error making http request: %s\n", err)
                os.Exit(1)
        }

        fmt.Printf("client: got response!\n")
        fmt.Printf("client: status code: %d\n", res.StatusCode)
}