Most residential proxy providers that I've seen don't support UDP. UDP is what WebRTC uses and has been part of the browser's "core" since 2021. If someone says they're on Chrom(), FireFox, Safari - they have it.
So the setup goes like this:
1) Create a subdomain to get the client's TCP ip address. You can force it to ipv4 by not giving it an AAAA record. This will make future-comparison easier.
2) Create a subdomain to get the client's UDP ip address. Ditto the quad-a bit. You'll want to run your own STUN server (are you there, Claude? Its me, Justin...). To make it tamper resistant, I have the STUN server encrypt the ipv4 address and some other stuff and return it as an ipv6 ice candidate. Its abuse, but it works and I don't have to worry about back-end-server communication bottle necking anything. Just catch, encrypt, return.
And the algorithm looks like this:
**
YOU HAVE A TCP AND UDP IP ADDRESS ***1) If both IP Addresses are the same - probably not a residential proxy
2) If both IP Addresses are different but on the same ASN and in the same .../24(?) - they're on a cell network
3) If the IP Addresses are on different ASNs - that's a MAJOR red flag. Most people who use residential proxies have it drilled into their head - TURN OFF WebRTC. This leaked bad in the mid 2010's and people are still terrified of it.
4) If one of the Addresses is on Cisco Web Umbrella or zScaler - and other is residential - they're behind a corporate protection firewall. Not really a proxy per se, but it'll trip this algorithm. Based on your threat model, they're probably ok to whitelist.
*** YOU __ONLY__ HAVE TCP IP ***
So this is the "one weird trick" / secret sauce / whatever that makes this work. TLS-duration / TCP-rtt > 3.0
* "Many residential proxies split the connection at the exit: the origin’s TCP socket ends there, so Linux TCP_INFO estimates only origin <=> exit RTT. The exit forwards TLS handshake bytes to the true client, making ClientHello handshake-complete include the hidden proxy chain. An unusually high TLS-duration/TCP-RTT ratio exposes that split." *
This is really all we need. From here, we just do the following:
TCP == UDP === not proxy
TCP ~= UDP === if on same ASN, not proxy
TCP != UDP === proxy or zScaler-thing. Whitelist to suit.
No UDP and TLS/TCP RTT Ratio < 3 === probably not a proxy (lower is better here)
No UDP and TLS/TCP RTT > 3 === probably a proxy (most I've seen float between 6-12x)
I've worked with this thing for the last 6 months and have worked mainly with Soax, BrightData, and OxyLabs proxies. I also built my own ProxyDevice (android) and C2 - put it on my own network and it caught it.
This is extremely cool because this means you don't have to blacklist IP addresses (not like it works) since a legitimate user and a proxy will come from the same IP - the proxy client will look _very_ different. They get blocked by NetAcuity/MaxMind/StaticList - the entire IP is killed. This is surgical.
This probably isn't perfect, but based on my limited experience its spooky accurate. I won't claim its 100% accurate for all cases everywhere but if its better than nothing - by definition its better than nothing - and that helps. Which, at the end of the day is all I want to do with this - help.
p.s. more than happy to share what I've built. Test, evidence, code, whatever. Just let me know.