Without ever having developed a fully functional anti bot system myself, I want to investigate some of the obstacles that need to be overcome if I started such a project.
First of all, I have to define the functional specification that such an anti bot system must meet:
A bot detection system should be able to detect passively (1) that a website visitor (2) is in fact not a human but an automated program (3)
I deliberately keep my definition very broadly.
curl commands orchestrated with shell scriptsadb (Android Debug Bridge) and (optionally) controlled with frameworks such as appium.io or stf (DeviceFarmer)Our attacker model is as follows:
Some explanatory words regarding the graphic above:
Websites are served by web servers. Every message that the browser (client) sends to the web server has to be considered as potentially spoofed and tainted. This includes network messages created by JavaScript that are dynamically sent over WebSockets or the sendBeacon API. This is also the reason why in the traffic arrows from the browser to the web server are in red color.
A fundamental property of client/server security is: All input from clients cannot be trusted from the standpoint of the server. This will be important in the remainder of this blog post, that's why I repeat it so vehemently here.
So what are actually some methods to passively detect that a browser is not controlled by an organic human?
Actually there don't exist straightforward signals that fall into that exact binary category of whether a visitor is a human or not.
Rather, anti bot vendors rephrase the question to: On what level can we uniquely identify a browser user and then rate limit her? Because that's the overall goal of anti bot systems: Rate limiting unique users.
Some techniques to identify users:
It should be obvious that the examples from the list above fall into two categories:
Of course an attacker can alter her IP address, but then she will need to set up a proxy server.
Technically, spoofing IP addresses on a IP level will work, but the first router outside your home network will most likely drop the IP packet if it detects that the source IP does not belong to the correct network. And even if that does not happen: How are you ever going to receive a reply to your spoofed IP packet?
With TLS and TCP/IP fingerprinting it's way easier to spoof those signals on the client side.
On the other hand, all the data that is collected via JavaScript can be spoofed by the client! Usually, anti bot companies collect a wide range of different data with JavaScript:
navigator propertyLet's think about how bot detection services are implemented by following a typical browsing session of a user visiting a website. As discussed above, bot detection systems collect data from both the client side and the server side. In the following section, I will follow a typical browsing session on a very low level and I will explain the different checks that a bot detection system performs at each point along the way.
The very first event relevant for bot detection is the DNS lookup of the hostname to which a browser establishes a connection. The browser uses the operating systems local DNS resolver to lookup the A and AAAA DNS resource record of the hostname in the URL. Worded differently, it asks for the IPv4 or IPv6 address that corresponds to the hostname used in the URL that was entered in the browser's address bar. The DNS request will answered by the responsible name server.
During such a lookup, the DNS server can check that the resolvers client's IP address is the same or belongs to the same ISP as the IP address that communicates with the web server. Put differently, on the DNS server it can be checked that no DNS leak happens. A DNS leak occurs, if the DNS traffic is not routed through the proxy/VPN configured in the browser.
After the IP address of the domain name has been obtained, the browser is ready to establish a connection with the web server.
Before a browser is able to display anything, a TCP and TLS handshake has to occur to establish a TCP connection to the web server (In case we are using https, which almost always is the case nowadays).
As soon as the first incoming SYN packet arrives on the web server, an anti bot system is capable of performing the following lookups on the server side:
Obtain the source IP address of the client. As soon as we have the source IP address, we can do a wide range of things:
whois command. For example, whois 15.23.65.22.host command. Example: host 80.185.115.25. If the hostname belongs to a trustworthy company, give it a better reputation. If it belongs to a untrustworthy organization, block it.Generate a TCP/IP fingerprint from the SYN packet. A TCP/IP fingerprint gives us information about the assumed operating system of the client based on certain TCP and IP header fields/values such as TCP options header field. The TCP/IP fingerprint alone does not have enough entropy to reasonably exclude a client. However, when the inferred OS seems to be Linux system, then it's valid to raise the bot score, since most legit users do not use Linux. The TCP/IP fingerprint can be altered and spoofed by the client!
After the initial TCP/IP handshake passed, the TLS handshake comes next. After the TLS handshake completed, the server is able to compute a TLS handshake fingerprint.
I am not a specialist regarding TLS fingerprinting, but my guess is that a TLS fingerprint has slightly more entropy compared to a TCP/IP fingerprint and allows to differentiate between different TLS implementations and maybe operating systems. If that is the case, then at this point it would be already possible to see if there is a mismatch in TCP/IP fingerprint OS and TLS fingerprint OS. However, keep in mind that TLS and TCP/IP fingerprints can be easily forged on the client side.
After the TLS handshake has been established, the client sends an initial HTTP GET request to fetch the requested URL. Let's assume the client requests the index.html document. Based on this very first GET request, we can do a couple of things:
var proxy_headers = ['Forwarded', 'Proxy-Authorization', 'X-Forwarded-For', 'Proxy-Authenticate', 'X-Requested-With', 'From', 'X-Real-Ip', 'Via', 'True-Client-Ip', 'Proxy_Connection'];
curl or Python requests?If all checks pass until this point, the web server is going to serve the contents of the index.html document. After the index.html document has been downloaded, the browser parses the page and loads linked images, CSS and JavaScript files.
The executed JavaScript in turn dynamically fetches more content which results in more HTTP requests, WebSocket streams and other forms of networking requests.
At this point, the anti bot detection system is ready to serve it's JavaScript client library that collects signals from the browser side.
As soon as JavaScript is executed on the browser, there are a lot of different techniques to harvest data that help to make a decision whether the client is a bot or not.
Keep in mind that the JavaScript execution environment is controlled by the client! So is the data that is sent back to the web server!
Bot detection companies use many different techniques to camouflage their JavaScript signals collection libraries:
Those three obfuscation techniques remind me a bit of the old days of reverse engineering. However, it's much much harder to protect JavaScript code than to protect x86 assembly.
There are some reasons for that:
A well known JavaScript obfuscation tool is javascript-obfuscator. A good JavaScript de-obfuscation tool is de4js.
For example, when I look for apartments on the German real estate search engine immobilienscout24.de, I am sometimes presented a bot detection challenge that performs a check passively in the background.
This is the heavily obfuscated JavaScript file that does the work in the background. I spent around 30 minutes trying to understand what it does, but honestly I could not figure out much without spending more time. I only know that the script performs some checks and sends the following payload back to the server, where p is a 30KB long base64 encoded binary blob:
{ "solution":{ "interrogation":{ "p":"h6R2UwMDY2NnRfZ18oNUBwVTJKVWtNZnFqc2ZndX5NaHZyOGpf...[truncated]", "st":1626613924, "sr":113884550, "cr":994722218 }, "version":"stable" }, "old_token":null, "error":null, "performance":{ "interrogation":1006 } }
The next steps in reverse engineering would be to find out what exactly p contains. Look at the place in the obfuscated JavaScript where p becomes encrypted/encoded and dump the clear text contents. Then I know what data is sent and I can therefore spoof it.
I am heavily assuming that the above JavaScript is Imperva's bot detection client side solution.
Before I dive deeper into some of the JavaScript bot detection techniques, I want to explain why collecting signals with JavaScript and stopping bots is so difficult. Assuming the bot detection execution time for the script is 200ms and then the data is transmitted in 50ms to the web server, which in turn has to make an API request to the central bot detection API (again 50ms), 300ms already passed before the client can be banned.
This means that all signals recorded by JavaScript that are sent to the bot detection API incur a large delay before a decision can be made on the server side.
What if our goal is to only scrape a single page? Then an attacker can simply abort the execution of said script. Maybe the server then remembers: Hey, this specific client with IP XYZ never actually sent a JavaScript payload back home. Looks unusual: One of the following cases had to happen:
Only the last point indicates malicious behavior of the client. Therefore, every bot detection system needs to give each client some free attempts, let's say the IP address get blocked if a threshold of N=20 has surpassed.
But what if the user switches his IP address between requests? For example by using a mobile 4G or 5G proxy that sits between a Carrier-grade NAT?
You can't just block a whole mobile address range because you feel like it. Wikipedia says:
In cases of banning traffic based on IP addresses, the system might block the traffic of a spamming user by banning the user's IP address. If that user happens to be behind carrier-grade NAT, other users sharing the same public address with the spammer will be mistakenly blocked. This can create serious problems for forum and wiki administrators attempting to address disruptive actions from a single user sharing an IP address with legitimate users.
What I describe above is something very fundamental about bot detection systems: They must work without relying on JavaScript signals. Not only because every network message originating from the client is potentially spoofed, but also because some browsers simply cannot execute JavaScript or fail in the process of doing so! Some users don't have JavaScript enabled. Banning the IP address because they disabled JavaScript is too aggressive.
And there is another very important point here: Bot detection systems can only reliably ban clients by IP address! This is so important to understand. Every other signal from the client can be spoofed (if the client understands what he executes on his machine)!
If a bot detection system bans based on other ways of identification (Browser fingerprint, WebGL fingerprint, font fingerprint, TLS Fingerprint, TCP/IP fingerprint, ...), a client can change those fingerprints and evade a ban!
Having said that, let's look at some techniques to detect bots on the client side:
navigator.languages or navigator.platform as possible, while simultaneously trying to look for attributes that are static and don't change on browser/OS updates.K bits are all zeroes. This takes some time.adb (5037).onmousemove, onscroll, onkeydown.The Proof of Work kind of challenges cannot be spoofed by the client. They have to be solved somewhere. However, the solving of the proof of work challenge does not have to be on the browser/client that receives the challenge.
Crypto proof of work challenges can be solved by dedicated hardware for cryptography, it does not need to be solved by the browser. This speeds up things considerably. I would guess that solving crypto hashing challenges on dedicated hardware is between 20 to 200 times faster than with JavaScript.
Solving captchas such as Google's reCAPTCHA can be outsourced to captcha solving services, such as for example deathbycaptcha.com or 2captcha.com.
Even solving more complex challenges such as Google Picasso can be pre-computed. All what you need is a network of different devices and access to a browser with JavaScript enabled. If you have a website with 10k unique visitors per month, you probably will be fine.
Bot detection systems consists of IP reputation techniques and client side data gathering scripts. They need to reliably work even if their client side JavaScript libraries send spoofed data. Because of that potential spoofing, those bot detection client side JavaScript's are heavily obfuscated to make it harder for attackers to understand what is expected from them to send back to the server in order to appear legit. Proof of work challenges do not have to be solved by the client that received the challenge - they can be outsourced.
There are two fundamental approaches how to defeat bot detection: