Solution to SquareCTF 2019 - Talk To Me

19/10/19 — capitol
Name:

Talk To Me

Category:

general - ruby

Points:

100

Writeup

ruubies

We got a telnet interface to a small chat bot, but it didn’t understand anything we said to it at first.

Sending it characters that wasn’t in [A-Za-z] gave a more interesting result:

(eval):1: syntax error, unexpected end-of-input, expecting ')'
(%=;10-90*1.500/1.1002+9,|'")
                             ^
/talk_to_me.rb:16:in `eval'
/talk_to_me.rb:16:in `receive_data'
/var/lib/gems/2.5.0/gems/eventmachine-1.2.7/lib/eventmachine.rb:195:in `run_machine'
/var/lib/gems/2.5.0/gems/eventmachine-1.2.7/lib/eventmachine.rb:195:in `run'
/talk_to_me.rb:31:in `<main>'

So obviously they ran the input through eval. This led us down a rabbit hole of trying to get a reverse shell working, but that was blocked.

And then one of us noticed that the chat bot asked us to greet it as it had greeted us, so the problem was reduced to how to send it something that could pass the first check for [A-Za-z] and then be evaluated to the string ‘Hello!’.

Since none of us where ruby programmers this took quite a while to figure out. The solution we came up with in the end was:

$ (sleep 1; echo "('' << 72)+('' << 101)+( '' << 108)+( '' << 108)+('' << 111)+('' << 33)") | nc -v talk-to-me-dd00922915bfc3f1.squarectf.com 5678

The flag was flag-2b8f1139b0726726.

Release of Ripasso version 0.2.0

03/10/19 — capitol

ripasso-cursive

I have just released the first version of ripasso, a password manager that lets you control the level of risk that you expose your passwords too.

Ripasso aims to be filesystem compatible with pass, and this enables you to use the same password store across all your devices.

Passwords in ripasso are encrypted with one or more public pgp keys and optionally added to a git repository.

This gives you a lot of flexibility on how securely you want to manage the passwords. Here are some examples use cases:

  • You can encrypt the passwords with all the people on your team’s gpg keys and have a common passwords in a shared git repository.
  • You can store your passwords locally without using git, that way there’s no history of your old passwords to leak.
  • You can use your password store on your phone (if you use git as a backend), but have a separate gpg key for your computer and your phone. That way if you lose your phone, your gpg identity isn’t lost.

Ripasso is written in rust and so far packaged in nix and arch.

Install instructions

Arch linux

yay install ripasso-git

Nix

nix-env -iA nixpkgs.ripasso-cursive

General

git clone git@github.com:cortex/ripasso.git
cd ripasso
cargo build -p ripasso-cursive

Credits

  • Joakim Lundborg - developer
  • Alexander Kjäll - developer
  • Stig Palmquist - NixOS packager
  • David Plassman - Arch packager

Also a big thanks to everyone who contributed with bug reports and patches.

Mojolicious: Executing code with url_escape()

08/04/19 — sgo

failraptor

Let’s look at the url_escape function in the Mojolicious web framework for Perl 5, and how it can be used to evaluate code through the second argument of the function.

TLDR;

use Mojo::Util qw(url_escape);
url_escape('some-stuff', '\w](?{die()})|[a'); # Dies!

The url_escape function

This function is a part of the very useful Mojo::Util library.

It accepts a list of two arguments:

  1. A string to escape $str
  2. An optional pattern that determines what characters to escape $pattern

Some usage examples:

url_escape('foo/bar.baz'); # Returns "foo%2Fbar.baz"
url_escape('foo/bar.baz', '/.'); # Returns "foo%2Fbar%2Ebaz"

This is the implementation in Mojolicious v8.13:

sub url_escape {
  my ($str, $pattern) = @_;

  if ($pattern) {
    unless (exists $PATTERN{$pattern}) {
      (my $quoted = $pattern) =~ s!([/\$\[])!\\$1!g;
      $PATTERN{$pattern}
        = eval "sub { \$_[0] =~ s/([$quoted])/sprintf '%%%02X', ord \$1/ge }"
        or croak $@;
    }
    $PATTERN{$pattern}->($str);
  }
  else { $str =~ s/([^A-Za-z0-9\-._~])/sprintf '%%%02X', ord $1/ge }

  return $str;
}

When url_escape is called with a $pattern it hasn’t seen before, it will generate, string eval and cache a function to handle this specific pattern. Subsequent calls with the same $pattern will re-use the generated code.

So, an input parameter to the function is interpolated into a string which is evaled… Interesting! Let’s try to inject some code. :D

Quoting

(my $quoted = $pattern) =~ s!([/\$\[])!\\$1!g;

The $pattern input value is quoted with the expression s!([/\$\[])!\\$1!g; and stored in $quoted before it’s interpolated into the string of code to be evaled, preventing us from ending the pattern part of the string substitution. Damn… So close!

Code Subpattern

It doesn’t appear that we can (easily) break out of the substitution expression, so let’s try something inside the expression instead, like (?{ code }).

code subpattern: A regular expression subpattern whose real purpose is to execute some Perl code—for example, the (?{…}) and (??{…}) subpatterns.

Very nice, but this can be a bit dangerous, as the perlre doc points out:

[..] for reasons of security, use re ‘eval’ must be in scope. This is to stop user-supplied patterns containing code snippets from being executable. In situations where you need to enable this with use re ‘eval’ , you should also have taint checking enabled. Better yet, use the carefully constrained evaluation within a Safe compartment. [..]

Normally, adding (?{ code }) to a pattern through string interpolation would result in a fatal error, unless use re 'eval' is set. But since the code we want to tamper with is string evaled, these restrictions do not apply.

Crafting the $pattern argument

This is what we want to execute:

die()

Let’s wrap die() in a code subpattern and add some stuff to both sides of it to make it match and compile.

'\w](?{die()})|[a'

Passing this $pattern to url_escape makes it generate code that looks like this:

sub { $_[0] =~ s/([\w](?{die()})|[a])/sprintf '%%%02X', ord $1/ge }

So we’re left with:

url_escape('some-stuff', '\w](?{die()})|[a'); # Dies!

Is this a vulnerability?

The Mojolicious team reviewed a PoC before this post and concluded that it was not a security vulnerability.

This behaviour is not exploitable unless the function is used incorrectly. It could for example allow for vulnerability chaining if a bug is found that allows an attacker to control the $pattern argument.

How to create vulnerable code:

  1. The most obvious way to create a vulnerability is to expose the second $pattern argument of url_escape directly to user input. Game Over.

  2. A more subtle way is by having another function return a list an attacker can control as arguments to url_escape.

    use Mojo::Util qw(url_escape);
       
    sub good { return 'some-stuff' }; # returns 1 string
    sub evil { return ('some-stuff', '\w](?{die()})|[a') }; # returns 2 strings
       
    url_escape( good() ); 
    url_escape( evil() ); # Dies!
    

    Variations of this belong to a class of vulnerabilities in Perl web applications that are generally well known.

Takeaways

Regular expressions in Perl are very powerful, functions that return lists can be scary, and library functions can have hidden features.

Solution to UTCTF 2019 - Jacobi's Chance Encryption

16/03/19 — capitol
Name:

Jacobi’s Chance Encryption

Category:

crypto

Points:

750

Writeup

red herrings

We got this challenge text and an implementation of a strange crypto system that we needed to break:

Public Key 569581432115411077780908947843367646738369018797567841

Can you decrypt Jacobi’s encryption?

def encrypt(m, pub_key):
    bin_m = ''.join(format(ord(x), '08b') for x in m)
    n, y = pub_key

    def encrypt_bit(bit):
        x = randint(0, n)
        if bit == '1':
            return (y * pow(x, 2, n)) % n
        return pow(x, 2, n)

    return map(encrypt_bit, bin_m)

And also the encrypted flag.

Looking at the implementation it does some really strange things. It loops over every bit in the plaintext and gets a large random number x. Then it checks if the bit is 1 or 0 and encodes that information as either y * x2 or x2 in the congruence class of y.

There is also a public key involved that’s just two primes multiplied together, but it’s more of a red herring.

This is actually a kind of neat algebra problem. We want to know if a number was a square of itself before it got reduced by modulo n or not, and we know that n and the number are coprime.

As with all math problems, someone has solved this in sage already. The name of the function is kronecker, so all we had to do was to write a small sage program to reverse the crypto function.

file = open("flag.enc", "r")
data = file.readline()

data = data.split(",")

pubkey = list(factor(569581432115411077780908947843367646738369018797567841))

i = 0
str = ""
sol = ""
for b in data:
    if b == "":
        continue

    if kronecker(int(b, 16), pubkey[1][0]) == 0:
        str += "1"
    else:
        str += "0"

    i += 1

    if i % 8 == 0:
        sol += chr(int(str, 2))
        str = ""

print(sol)

The flag was utflag{did_u_pay_attention_in_number_theory}.

Solution to UTCTF 2019 - Super Secure Authentication

12/03/19 — fR30n
name:

Super Secure Authentication

category:

reverse

points:

750

credits:

Big thanks to capitol and karltk for the help around the Java tools!

Writeup

The challenge consisted of the following set of Java (compiled) classes:

Authenticator.class
jBaseZ85.class
Verifier0.class
Verifier1.class
Verifier2.class
Verifier3.class
Verifier4.class
Verifier5.class
Verifier6.class
Verifier7.class

The text description tells us to run: java Authenticator [password], so that’s our starting point. After decompiling Authenticator.class in Ghidra we get the main and checkFlag methods:

void main_java.lang.String[]_void(String[] param1)
{
  boolean bVar2;
  StringBuilder objectRef;
  String pSVar1;
  PrintStream[] objectRef_00;
  
  if (param1.length != (dword)0x1) {
    objectRef_00 = System.out;
    objectRef_00.println("usage: java Authenticator [password]");
    return;
  }
  pSVar1 = param1[0];
  bVar2 = Authenticator.checkFlag(pSVar1);
  if (bVar2 == false) {
    objectRef_00 = System.out;
    objectRef_00.println("Oops, try again!");
  }
  else {
    objectRef_00 = System.out;
    objectRef = new StringBuilder();
    objectRef = objectRef.append("You got it! The flag is: ");
    objectRef = objectRef.append(pSVar1);
    pSVar1 = objectRef.toString();
    objectRef_00.println(pSVar1);
  }
  return;
}
boolean checkFlag_java.lang.String_boolean(String param1)
{
  String objectRef;
  boolean bVar3;
  int iVar1;
  char cVar2;
  StringTokenizer objectRef_00;
  int iVar4;
  StringTokenizer objectRef_01;
  
  objectRef = param1.substring(0,7);
  bVar3 = objectRef.equals("utflag{");
  if (bVar3 == false) {
    return false;
  }
  objectRef = param1;
  iVar1 = param1.length();
  cVar2 = objectRef.charAt(iVar1 + -1);
  if (cVar2 != '}') {
    return false;
  }
  objectRef_01 = new(StringTokenizer);
  iVar4 = 7;
  objectRef_00 = objectRef_01;
  iVar1 = param1.length();
  objectRef = param1.substring(iVar4,iVar1 + -1);
  objectRef_01.<init>(objectRef,"_");
  objectRef = objectRef_00.nextToken();
  bVar3 = Verifier0.verifyFlag(objectRef);
  if (bVar3 == false) {
    return false;
  }
  objectRef = objectRef_00.nextToken();
  bVar3 = Verifier1.verifyFlag(objectRef);
  if (bVar3 == false) {
    return false;
  }
  objectRef = objectRef_00.nextToken();
  bVar3 = Verifier2.verifyFlag(objectRef);
  if (bVar3 == false) {
    return false;
  }
  objectRef = objectRef_00.nextToken();
  bVar3 = Verifier3.verifyFlag(objectRef);
  if (bVar3 == false) {
    return false;
  }
  objectRef = objectRef_00.nextToken();
  bVar3 = Verifier4.verifyFlag(objectRef);
  if (bVar3 == false) {
    return false;
  }
  objectRef = objectRef_00.nextToken();
  bVar3 = Verifier5.verifyFlag(objectRef);
  if (bVar3 == false) {
    return false;
  }
  objectRef = objectRef_00.nextToken();
  bVar3 = Verifier6.verifyFlag(objectRef);
  if (bVar3 == false) {
    return false;
  }
  objectRef = objectRef_00.nextToken();
  bVar3 = Verifier7.verifyFlag(objectRef);
  if (bVar3 == false) {
    return false;
  }
  return true;
}

From the code above we understand that the flag is composed by 8 tokens and has the form utflag{token0_token1_token2_token3_token4_token5_token6_token7}, where each tokenX is validated by each VerifierX.class. So we continue to decompile the first one (Verifier0) of these classes. The decompiled class looks gigantic, but from the code we could spot two important things.

(a) There is a static initializer, which allocates a big string and calls jBaseZ85.decode with it and this is stored in a static member of the class. The code looks something like the following:

void <clinit>_void(void)
{
  StringBuilder objectRef;
  String objectRef_00;
  byte[] pbVar1;
  
  objectRef = new StringBuilder();
  objectRef_00 = new String(<big_string_here>);
  ..
  more allocations of big strings
  ..
  objectRef = objectRef.append(objectRef_00);
  objectRef_00 = objectRef.toString();
  pbVar1 = jBaseZ85.decode(objectRef_00);
  Verifier0.arr = pbVar1;
  return;

(b) The verifyFlag method uses the bytes from above to define a new Verifier0 class and then calls the verifyFlag method defined for that class. It looks like a basic way to obfuscate the real method.

boolean verifyFlag_java.lang.String_boolean(String param1)
{
  Class[] ppCVar1;
  Object[] ppOVar2;
  Class objectRef;
  Method objectRef_00;
  Object objectRef_01;
  boolean bVar3;
  Verifier0 objectRef_02;
  
  objectRef_02 = new Verifier0();
  objectRef = objectRef_02.defineClass("Verifier0",Verifier0.arr,0,Verifier0.arr.length);
  ppCVar1 = new Class[1];
  ppCVar1[0] = String.class;
  objectRef_00 = objectRef.getMethod("verifyFlag",ppCVar1);
  ppOVar2 = new Object[1];
  ppOVar2[0] = param1;
  objectRef_01 = objectRef_00.invoke(null,ppOVar2);
  throwExceptionOp(objectRef_01);
  bVar3 = objectRef_01.booleanValue();
  return bVar3;
}

So from (a) and (b) we have an idea of what we need to do: in order to get to the last state of the Verifier0 class we need to decode those strings with Z85, dump the class, and reverse the verifyFlag method. To automate part of that job we wrote a simple Python 3 script that uses javap to dump the stored strings and pyzmq to decode the new verifier class. After playing a bit with the script we also realized that this obfuscation is done several times.

#!python3
import os
import io
import sys

# install pyzmq with pip!
import zmq.utils.z85

def get_class_bytes_from_verifier(java_bytecode):
    bytes = ""
    lines = java_bytecode.split('\n')
    if len(lines) is 0:
        return ""

    for line in lines:
        str_index = line.find("String")
        if str_index > 0:
            java_str = line[str_index + len("String") :]
            java_str_len = len(java_str)

            if java_str_len > 200 or len(bytes) > 0:

                # complete with padding to match the size as it is expected in z85
                if java_str_len != 10001:
                    java_str += '0' * (10001 - java_str_len)

                print("adding line : {} {}".format(java_str[:10],java_str[-10:]))
                bytes += java_str.strip()

    return bytes.strip()

def dump_byte_code(classname, path):
    javap_ret = os.popen('cd {} && javap -cp . -c {} | grep ldc'.format(path, classname)).read()
    byte_code = get_class_bytes_from_verifier(javap_ret)

    if len(byte_code) is 0:
        print("Finished!")
        return False

    assert(len(byte_code) % 5 == 0)
    print ("Found byte code! dumping...\n")
    with io.open('work/dump.class'.format(classname), 'wb') as f:
        decoded_byte_code = zmq.utils.z85.decode(byte_code)
        f.write(decoded_byte_code)
    return True

def extract_class(classname):
    # cleanup previous job
    os.system("mkdir -p work")
    os.system("rm -fr work")
    os.system("mkdir -p work")

    # dump from the initial directory
    dump_byte_code(classname, ".")

    while dump_byte_code("*", "./work/"):
        pass

if len(sys.argv) > 1:
    extract_class(sys.argv[1])

After running the script with the name of the class we want to dump we get the deobfuscated version of the Verifier0 class!

$ python3 dump.py Verifier0

-- some output we added to see progress --

$ file work/dump.class 
work/dump.class: compiled Java class data, version 52.0 (Java 1.8)

Now is only a matter of dumping and reversing each class. We wrote another Python 3 script with the logic of each token verifier:

#!python3
import string
import hashlib

# calculates the hashCode() of a string like Java does:
def java_hashcode(strval):
    h = 0
    if len(strval):
        for i in strval:
            h = 31 * h + ord(i)
    return h

print("flag: utflag{", end="")
verifier0 = [50, 48, 45, 50, 42, 39, 54, 49]
for i in verifier0:
    print(chr(i ^ 66), end="")
print("_", end="")

verifier1 = [ 0x73, 0x75, 0x6f, 0x69, 0x78, 0x6e, 0x61 ]
# iterate in reverse!
for i in verifier1[::-1]:
    print(chr(i), end="")
print("_", end="")

verifier2 = [ 0x2f01e2, 0x2f7641, 0x331939, 0x3401f7, 0x32a4da, 0x3147bd, 0x3647d2, 0x3147bd, 0x3401f7, 0x338d98 ]
for hashcode in verifier2:
    for i in string.ascii_lowercase:
        if java_hashcode(i + 'foo') == hashcode:
            print(i, end="")
print("_", end="")


verifier3 = "obwaohfcbwq"
for i in verifier3:
    x = ((ord(i) - 0x55) % 0x1a) + 0x61
    c = chr(x)
    print(c, end="")

print("_", end="")


verifier4 = [
        0xd30,
        0xcdf,
        0xe3e,
        0xc73,
        0xd9c,
        0xcc4 ]

for i in verifier4:
    x = int((i - 0x238) / 0x1b)
    print(chr(x), end ="")
print("_", end="")


verifier5 = "8FA14CDD754F91CC6554C9E71929CCE7865C0C0B4AB0E063E5CAA3387C1A8741FBADE9E36A3F36D3D676C1B808451DD7FBADE9E36A3F36D3D676C1B808451DD7"
verifier5 = verifier5.lower()

def verify_flag_verifier5(flag):
    flaghash = ""
    for i in flag:
        m = hashlib.md5()
        m.update(i.encode()) #   md.update((byte)flagbytes[i]);
        tmp = ""  #   ss = new StringBuilder();
        tmp += flaghash #   ss.append(flag_hash);
        # print(len(flaghash))
        dg_bytes = m.digest() #   digest_bytes = md.digest();
        dg_str = dg_bytes.hex() #   digest_str = DatatypeConverter.printHexBinary(digest_bytes);
        tmp += dg_str  #   ss.apend(digest_str)

        flaghash = tmp #   flag_hash = ss.toString();
    return flaghash == verifier5

for i in string.ascii_lowercase:
    for j in string.ascii_lowercase:
        for k in string.ascii_lowercase:
            for l in string.ascii_lowercase:
                    if verify_flag_verifier5(i + j + k +l):
                        print((i + j + k + l), end="")

print("_", end="")


verifier6 = "1B480158E1F30E0B6CEE7813E9ECF094BD6B3745"
verifier6 = bytes.fromhex(verifier6)

def verify_flag_verifier6(flag):
    m = hashlib.sha1()
    m.update(flag.encode())
    dg = m.digest()
    return  dg == verifier6

for i in string.ascii_lowercase:
    for j in string.ascii_lowercase:
        for k in string.ascii_lowercase:
            for l in string.ascii_lowercase:
                if verify_flag_verifier6(i + j + k + l):
                    print(i + j + k + l, end="")
print("_", end="")

verifier7 = "goodbye"
print(verifier7, end="")

print("}")

flag was: utflag{prophets_anxious_demolition_animatronic_herald_fizz_stop_goodbye}