---
title: "GHSA-56w8-8ppj-2p4f: Bot protection bypass in Anubis"
desc: "Update your copy of Anubis, but it's very minor"
date: 2025-01-26
---

Hey all. I screwed up with part of how I made [Anubis](/blog/2025/anubis/), and as a result I have both fixed it and am filing this CVE to explain what went wrong and how it was fixed. This is [GHSA-56w8-8ppj-2p4f](https://github.com/Xe/x/security/advisories/GHSA-56w8-8ppj-2p4f).

This requires a sophisticated attacker to target a server running Anubis. I suspect that the only instances of this in the wild were the ones done by the reporter as a proof of concept and in my testing.

## Vulnerability details

These details have been copied from [GHSA-56w8-8ppj-2p4f](https://github.com/Xe/x/security/advisories/GHSA-56w8-8ppj-2p4f).

CVSS score: 2.3 (CVSS:4.0/AV:N/AC:H/AT:N/PR:L/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N)

Weakness: [CWE-807](https://cwe.mitre.org/data/definitions/807.html): Reliance on Untrusted Inputs in a Security Decision

Vulnerable version: anything older than `v1.11.0-37-gd98d70a`

Patched version: `v1.11.0-37-gd98d70a` and newer

### Context

Anubis is a tool that allows administrators to protect bots against AI scrapers through bot-checking heuristics and a proof-of-work challenge to discourage scraping from multiple IP addresses. For more information about Anubis, see [Anubis' README.md](https://github.com/Xe/x/blob/master/cmd/anubis/README.md).

### Impact

A sophisticated attacker (or scraper runner) that is targeting a website that uses Anubis can easily bypass the bot protection mechanisms.

This requires a targeted attack.

### Patches

Pull the most recent Docker image in order to be sure you have upgraded past commit [e09d0226a628f04b1d80fd83bee777894a45cd02](https://github.com/Xe/x/commit/e09d0226a628f04b1d80fd83bee777894a45cd02).

### Workarounds

There are no known workarounds at this time. Users must upgrade to fix this issue.

### Details

Anubis works by having a client request a challenge value with a given difficulty, then the client performs proof-of-work to create a sha-256 hash matching that difficulty. Before commit [e09d0226a628f04b1d80fd83bee777894a45cd02](https://github.com/Xe/x/commit/e09d0226a628f04b1d80fd83bee777894a45cd02), the client sent the difficulty it used back to the server and the server used that untrusted value to make an allow/deny decision.

This has been fixed by using the difficulty value set by the administrator in Anubis' configuration flags when making said allow/deny decisions.

### GReeTZ

Thank you Coral Pink for reporting this issue.

## The Techaro security issue reporting policy

At [Techaro](https://techaro.lol), we believe in total honesty in how we handle security issues. We try our best to not make vulnerable code, but inevitably we will mess up and do it by accident. When we do, we will be: transparent, honest, high-signal, and handle the situation like professional adults. We [will value the time of security researchers](https://soatok.blog/2025/01/21/too-many-people-dont-value-the-time-of-security-researchers/).

At times, we will fail at this mission. The real thing we are measuring is not the number of times that it happens, but how we react when it does happen. This is why we are openly and honestly reporting this issue.

When things do fail, we will create regression tests to ensure that those failures do not repeat themselves. The testing for Anubis is currently private, but in the interest of transparency here is the test that we added to that repo to handle this regression:

```go
func TestFakeChallengeDifficulty(t *testing.T) {
	cli, err := anubis.New(*testServerURL)
	if err != nil {
		t.Fatal(err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	chall, err := cli.MakeChallenge(ctx)
	if err != nil {
		t.Fatal(err)
	}

	nonce := 42069

	response, err := sha256sum(fmt.Sprintf("%s%d", chall.Challenge, nonce))
	if err != nil {
		t.Fatal(err)
	}

	if err := cli.PassChallenge(ctx, anubis.PassChallengeRequest{
		Response:    response,
		Nonce:       nonce,
		Redir:       "https://xeiaso.net",
		ElapsedTime: 420,
		Difficulty:  0,
	}); err != nil {
		sce, ok := err.(*anubis.StatusCodeErr)
		if !ok {
			t.Fatal(err)
		}
		if sce.Got != http.StatusForbidden {
			t.Fatalf("wrong status code, should have forbidden auth bypas: want: %d, got: %d", sce.Want, sce.Got)
		}
	}
	return
}
```

Thank you for following the development of Anubis.
