DEF CON 2014 CTF Quals -nonameyet

nonameyet was a 3 point problem for DEF CON 2014 CTF Qualification rounds written by HJ; who according to legitbs “is a French-language enthusiast and beard connoisseur. He lives in the Cabin in the Woods from that horror movie.”

The problem statement was simple:

I claim no responsibility for the things posted here.

The web page appeared to be a simple image upload site.. with a few “features”. First index.html took a GET parameter named “page” and simple returned that file to you. So requesting “/index.php?page=index.php” gave you the source of the page:

  if ( isset( $_GET['background'] ) ) {
    $back = $_GET['background'];
  } else {
    $back = 'back.png';

  if ( isset( $_GET['page'] ) ) {
	$a = $_GET['page'];
	if ( strpos( $a, "169" ) !== FALSE ) {
		include( "home.php" );
	} else if ( strpos( $a, "https" ) !== FALSE ) {
		include ("home.php");
	} else {
  } else {

The other primary page was the upload page:

<!DOCTYPE html>
<html lang="en">
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">
    <!-- <link rel="shortcut icon" href="../../assets/ico/favicon.ico"> -->

    <title>Upload a File</title>

    <!-- Bootstrap core CSS -->
    <link href="./css/bootstrap.min.css" rel="stylesheet">



    <!-- Fixed navbar -->
    <div class="navbar navbar-default navbar-fixed-top" role="navigation">
      <div class="container">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          <a class="navbar-brand" href="index.php">nonameyet</a>
        <div class="collapse navbar-collapse">
          <ul class="nav navbar-nav">
            <li class="active"><a href="index.php">Home</a></li>
            <li><a href="index.php?page=download.html">Download Photo</a></li>
            <li><a href="">Contact</a></li>
        </div><!--/.nav-collapse -->

    <!-- Begin page content -->
    <div class="container">
    <div class="container" style="width: 200px">
        <br /><br /><br />
        <form style="form-inline" action="/cgi-bin/nonameyet.cgi" method="post"
<input type="file" class=
"form-control" placeholder="Local File" name="photo" />
<input type="text" placeholder="Name" class="form-control" name="base"/>
<input type="text" name="genre" class="form-control" placeholder="Genre"/>
<input type="text" name="date"  class="form-control" placeholder="Date"/>
<input type="text" name="time" class="form-control" placeholder="Time"/>
<input class="btn btn-lg btn-primary btn-block" type="submit" name="Upload" value="Upload" /></p>

    <!-- Bootstrap core JavaScript
    ================================================== -->
    <!-- Placed at the end of the document so the pages load faster -->
    <script src=""></script>
    <script src="./js/bootstrap.min.js"></script>

The upload had a few fields, mainly file to upload, a “Name” (called base in the post) and some “meta-data” like Genre, Date, and Time. Without putting a path in for name, the CGI would place the file in /var/www/cgi-bin/photos; if you put a path in one could write the file anywhere. I uploaded php-shell to play around, and quickly found that the flag was not readable by the web user (it existed in /var/www/cgi-bin and /home/nonameyet); but for some reason the uploaded files were owned by nonameyet. A quick look at the apache config revealed that SuExec was being used to run the CGI binary as nonameyet. Which mean that we had to pwn the CGI. Using index.php we downloaded the binary and got to work.
The CGI starts in a pretty straightforward fashion by parsing the CGI parameters:

  1. Reads the “CONTENT_TYPE” environment variable, and checks if it’s “multipart/form-data”; if so goes into a multi-part parsing routine
  2. Checks the “REQUEST_METHOD” to environment variable to determine if it the request is GET or POST
  3. If a GET request, it uses the “QUERY_STRING” environment variable for parameters
  4. If a post, it tries to parse the “CONTENT_LENGTH” env variable; then allocates that size buffer and reads stdin
  5. If neither GET nor POST it goes into offline mode; where it reads name/value pairs from stdin
  6. It then replaces ‘+’ with spaces, and counts parameters to allocate an array to store them in..
  7. Params are grouped into an array pointers to a struct with two char* pointers, one for the name, the other for the value. During this process parameters are URL decoded

Multi-part works similar, but it also handle files and puts them into a different area. Cookies are also parsed.

After parsing parameters, the program changes to the photos directory, and and then tries to retrieve all the necessary parameters:

  • the file – named “photo”
  • base – what to name the file
  • time
  • date
  • pixy – this along with pixx are missing from the html file
  • pixx
  • genr – note this is DIFFERENT than the name in the html file

Once retrieved, the values are then url decoded AGAIN; but this time the length is also returned; the string pointers and lengths are placed into an array of ( char*; int) structures on the stack in the order above.
The program then reads the file into an allocated buffer. It then does some substitution on the “base” name, replacing %id% where id is “Genr”, “PixX”, “PixY”, “Date”, “Time” with the value read from the parameter… this is where the fun is..

Show Comments