Contents

๐ŸŒ Art Contest

A detailed write-up of the Web challenge 'Art Contest' from WolvCTF - 2025

/images/WolvCTF-2025/Web/ArtContest/challenge_presentation.png
Challenge Presentation

๐Ÿ“Š Challenge Overview

Category Details Additional Info
๐Ÿ† Event WolvCTF - 2025 Event Link
๐Ÿ”ฐ Category Web ๐ŸŒ
๐Ÿ’Ž Points 498 Out of 500 total
โญ Difficulty ๐ŸŸข Easy Personal Rating: 2/10
๐Ÿ‘ค Author dree Profile
๐ŸŽฎ Solves (At the time of flag submission) 9 solve rate
๐Ÿ“… Date 22-03-2025 WolvCTF - 2025
๐Ÿฆพ Solved By mH4ck3r0n3 Team: QnQSec

๐Ÿ“ Challenge Information

Art Contest Submit your best ascii art to win! https://art-contest-974780027560.us-east5.run.app

๐ŸŽฏ Challenge Files & Infrastructure

Provided Files

Files:

๐Ÿ” Initial Analysis

First Steps

Initially, the website appears as follows:

/images/WolvCTF-2025/Web/ArtContest/site_presentation.png
Site Presentation

Doing a couple of upload tests, I realized it was a PHP web application that only allows .txt files to be uploaded, so some filter is applied on the extension. Since I didnโ€™t find anything else interesting on the page, I started analyzing the attached files. I noticed there was also a C file, namely get_flag.c, which performs a fopen on the file flag.txt and reads its content. So I thought, “this file will surely be useful for something.” After that, I analyzed the Dockerfile to see the operations performed with get_flag.c, and I found this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Create the uploads directory and make it unreadable but writable
RUN mkdir -p /var/www/html/uploads && \
    chown root:www-data /var/www/html/uploads && \
    chmod 730 /var/www/html/uploads  
# Make /var/www/html/ only readable
RUN chown root:www-data /var/www/html/ && \
    chmod 750 /var/www/html/  
# Copy flag.txt over
COPY flag.txt /var/www/html/flag.txt
RUN chown root:root /var/www/html/flag.txt && \
    chmod 400 /var/www/html/flag.txt  
# Copy get_flag over and compile
COPY get_flag.c /var/www/html/get_flag.c
RUN gcc /var/www/html/get_flag.c -o /var/www/html/get_flag && \
    chown root:www-data /var/www/html/get_flag && \
    chmod 4755 /var/www/html/get_flag && \
    rm /var/www/html/get_flag.c

From this, it is already clear that the uploaded files will be placed in the uploads folder. Then it makes /var/www/html/ only readable and sets the permissions on the flag.txt file so that the owner is root. After that, it compiles get_flag.c and sets the permissions with root as the owner and the group as www-data. Then it executes chmod 4755, which sets 4 -> Setuid (making the file run with the ownerโ€™s permissions), 7 -> The owner has full permissions, 5 -> the group has read and execute permissions, 5 -> others have read and execute permissions. So even though we are www-data, we can read the flag by executing the get_flag file, as it will run with root permissions and will be able to open the flag.txt file since the owner is root. Most likely, we will need to find a way to bypass the filter by obtaining a web shell and then executing the get_flag file to get the flag. Next, I started analyzing the index.php file, where I found the full path in which the uploaded files are placed, and another interesting thing I had already seen previously in the challenge Submission which made me think it implied some sort of Bash Glob Injection:

1
2
3
4
    $target_file = basename($_FILES["fileToUpload"]["name"]);
    $session_id = session_id();
    $target_dir = "/var/www/html/uploads/$session_id/";
    $target_file_path = $target_dir . $target_file;

As we can see, the upload path is built as /uploads/session_id/filename. So we will need to use the PHPSESSID cookie to access the uploaded file:

1
2
3
4
5
    $old_path = getcwd();
    chdir($target_dir);
    // make unreadable - the proper way
    shell_exec('chmod -- 000 *');
    chdir($old_path);

This is the part of the code also present in the Submission challenge, which made me think of a Bash Glob Injection. From that challenge, I learned that any filename starting with . bypasses the chmod 000 * that sets all permissions to none on the file. Therefore, accessing the route /uploads/session_id/filename will only result in Forbidden. So, to bypass this, we can simply upload a file like .foo.txt and access /uploads/session_id/foo.txt to read it. However, to execute get_flag, we need an RCE which we can only get by uploading a file with a .php extension. So I tried the usual techniques (HackTricks) for extension bypass, but I didn’t get anywhere. Moving on to the next phase with all the information gathered.

๐Ÿ”ฌ Vulnerability Analysis

Potential Vulnerabilities

  • Unrestricted File Upload
  • RCE (Remote Code Execution)

๐ŸŽฏ Solution Path

Exploitation Steps

Initial setup

I tried all the bypass methods for uploading a .php file, but nothing worked. So I took another look at the code, focusing on the part where the extension is checked:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    $lastDotPosition = strrpos($target_file, '.');
    // If the file contains no dot, evaluate just the filename
    if ($lastDotPosition == false) {
        $filename = substr($target_file, 0, $lastDotPosition);
        $extension = '';
    } else {
        $filename = substr($target_file, 0, $lastDotPosition);
        $extension = substr($target_file, $lastDotPosition + 1);
    }  
    // Ensure that the extension is a txt file
    if ($extension !== '' && $extension !== 'txt') 
    {
        echo "Sorry, only .txt extensions are allowed.\n";
        $uploadOk = 0;
    }

I noticed that in the if statement, when $lastDotPosition == false, the extension is assigned to '', so it will bypass the next check. I thought, “How can I get that specific condition?” If I were to send a file like 1.txt:

/images/WolvCTF-2025/Web/ArtContest/1txt.png
1.txt

As we can see, $lastDotPosition = 1, but I need it to be equal to 0 since 0 == false:

/images/WolvCTF-2025/Web/ArtContest/true.png
True

It evaluates to True. The only way to achieve this is if the dot is at position 0, so by sending a filename=.php, we can bypass both the extension check and the chmod because the filename starts with a .. Let’s move on to the exploitation.

Exploitation

The first thing I did was write a PHP web shell taken from (revshell), specifically php cmd, and I saved it as .php. I uploaded it and extracted the PHPSESSID to reconstruct the upload path:

/images/WolvCTF-2025/Web/ArtContest/upload.png
Upload

As we can see, the upload was successful, so I reconstructed the path (https://art-contest-974780027560.us-east5.run.app/uploads/7c2fa3bdc5c51f329206c594cf9a809c/.php) and visited the page:

/images/WolvCTF-2025/Web/ArtContest/web_shell.png
Web Shell

I obtained my web shell. By sending cd ../../;./get_flag, I obtained the flag. I encountered problems since it seems executing executables that are two directories up (../../get_flag) doesn’t work.

Flag capture

/images/WolvCTF-2025/Web/ArtContest/manual_flag.png
Manual Flag

๐Ÿ› ๏ธ Exploitation Process

Approach

The automatic exploit performs a POST request to upload the PHP web shell with filname=.php as seen previously. Then, it extracts the session ID and uses it to construct the upload path for the files. Next, it sends a request to the uploaded web shell with the parameter cmd=cd ../../;./get_flag to execute the get_flag file and print the flag. Finally, it extracts the flag from the response using a regex.

๐Ÿšฉ Flag Capture

Flag

wctf{m1ss3d_m3_chm0d_:3}

Proof of Execution

/images/WolvCTF-2025/Web/ArtContest/automated_flag.png
Automated Flag
Screenshot of successful exploitation

๐Ÿ”ง Tools Used

Tool Purpose
Python Exploit

๐Ÿ’ก Key Learnings

Skills Improved

  • Binary Exploitation
  • Reverse Engineering
  • Web Exploitation
  • Cryptography
  • Forensics
  • OSINT
  • Miscellaneous

๐Ÿ“š References & Resources

Similar Challenges

Learning Resources


๐Ÿ“Š Final Statistics

Metric Value Notes
Time to Solve 00:15 From start to flag
Global Ranking (At the time of flag submission) 16/325 Challenge ranking
Points Earned 498 Team contribution

Created: 22-03-2025 โ€ข Last Modified: 22-03-2025 Author: mH4ck3r0n3 โ€ข Team: QnQSec