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.
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, httpx
and 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 -proxy
flag 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)
}