Citrix ADC 远程代码执行漏洞(CVE-2019-19781)

0nise  1404天前

1.png

很多人都知道我在去年年底公布了一个 CVE-2019-19781 漏洞。这是 Citrix ADC中的一个严重漏洞,它允许未经授权的用户执行任意操作系统命令。

Citrix 发布漏洞解决方案时引发了极大的轰动,因为在全球大约有 80,000 家公司在受影响范围内。该漏洞受到很多关注的另外一个原因是 Citrix ADC 大部分安装至公司外部和内部组织网络之间的边界上。因此,当黑客利用 CVE-2019-19781 漏洞时,可以同时获取目标公司内部网络的访问权限,并且可以针对内网发起攻击。

我是如何发现 CVE-2019-19781 漏洞的?

在 Citrix ADC 进行黑盒分析时,发现未经授权的用户可以使用路径遍历来访问静态文件,直接访问需要授权(/vpn/../vpns/style.css):

2.png

这种情况引起了我的兴趣,所以我决定本地搭建 Citrix ADC 环境,并且进行研究分析。

首先,我分析了Apache Web服务器配置文件(/etc/httpd.conf),该文件负责应用程序的Web界面:

Alias /vpns/portal/scripts/ /netscaler/portal/scripts/
<LocationMatch "/vpns/portal/sc ripts/.*\.pl$">
  SetHandler perl-sc ript
  PerlResponseHandler ModPerl::Registry
  Options +ExecCGI
  PerlSendHeader On
  allow from all
</LocationMatch>

通过配置文件,可以看到 /vpns/portal/scripts/.*\.pl$ 是由函数 ModPerl::Registry 进行处理。在实际情况下,可以在 /netscaler/portal/scripts/ 未经授权的情况下从该文件夹运行 Perl 脚本。

接下来,我开始分析可以在 URL 处调用的脚本/vpn/../vpns/portal/scripts/[scriptName].pl

3.png

几乎每一个脚本都从 NetScaler::Portal::UserPrefs 模块(/netscaler/portal/modules/NetScaler/Portal/UserPrefs.pm)中调用了 csd 函数。csd 函数与 HTTP 标头 NSC_USER 和 NSC_NONCE 一起使用。从我们当前的目的来说,第二个(NSC_NONCE)是无关。但第一个标头 NSC_USER 被用作文件名称。如果 NSC_USER 的值不存在,则使用已经定义好的结构进行创建文件。如果存在,则根据 $doc 解析的文件进行分配变量值。

sub csd {
    my $self = shift;
    my $skip_read = shift || "";
    my $cgi = new CGI;
    my $username = Encode::decode('utf8', $ENV{'HTTP_NSC_USER'}) || errorpage("Missing NSC_USER header.");

    $self->{username} = $username;  

    my %session;
    my $nsc_nonce = Encode::decode('utf8', $ENV{'HTTP_NSC_NONCE'}) || errorpage("Missing NSC_NONCE header.");
    my $id = unpack ('H*',$nsc_nonce);
...
    $self->{session} = %session;
    $self->{filename} = NetScaler::Portal::Config::c->{bookmark_dir} . Encode::encode('utf8', $username) . '.xm l';
    if($skip_read eq 1) {
        return;
    }

    my $doc = $session{doc} || "";
    if ($doc eq "" || $doc->{timestamp} < $self->timestamp()){
      $doc = $self->fileread($username);
        $session{doc}= $doc;
    }
    if ($doc->{style}->{theme} ne "custom" && ! -e NetScaler::Portal::Config::c->{theme_dir} . $doc->{style}->{theme}){
      delete $doc->{'style'};
      $self->filewrite($doc);
    }

    $self->{doc} = $doc;
    return $self->{doc};
}

sub filewrite {
    my $self = shift;
    my $doc = shift;
    $doc = $self->{doc};
    my $parser = xml::Simple->new();
    $datafile = NetScaler::Portal::Config::c->{bookmark_dir} . Encode::encode('utf8', $self->{username}) . ".xml";
...
    open(FILEWRITE, "> $datafile") || die "can't open $datafile $!";
...
    print FILEWRITE $parser->xmlout($doc,  xmldecl => '<?xml version="1.0" encoding="UTF-8"?>', RootName => 'user', valueattr => [ 'username', 'theme' ]);
...
}

如果在文件名中使用路径遍历,那么我们可以在任何具有写许可权的系统目录中创建一个扩展名为.xml的文件。为了确认这一点,我们将输入字符串../../../../tmp/myTestFile作为NSC_USER标头值,并检查目录中是否存在该名称的/tmp/文件:

如果在文件名中使用路径便利,那么我们可以在可写的系统目录中创建扩展名为 .xml 的文件。为了验证这一猜想,将 ../../../../tmp/myTestFile 作为 NSC_USER标头值,并检查目录中是否存在该名称的/tmp/文件:

4.png

我们可以看到成功创建了文件,但无法控制写入到文件内容信息。

后面我发现 newbm.pl 中接收 POST 参数值,并且将某些参数值,如 url、title 以及 desc 写入该文件(NSC_USER 头中的文件名)。

...
my $user = NetScaler::Portal::UserPrefs->new();
my $doc = $user->csd();
...
my $newurl = Encode::decode('utf8', $cgi->param('url'));
my $newtitle = Encode::decode('utf8', $cgi->param('title'));
my $newdesc = Encode::decode('utf8', $cgi->param('desc'));
my $UI_inuse = Encode::decode('utf8', $cgi->param('UI_inuse'));
...
my $newBM = {   url => $newurl,
    title => $newtitle,
    descr => $newdesc,
    UI_inuse => $UI_inuse,
};
...
if ($newBM->{url} =~ /^\\/){
  push @{$doc->{filesystems}->{filesystem}}, $newBM;
} else { # bookmark
  push @{$doc->{bookmarks}->{bookmark}}, $newBM;
}
undef(@{$doc->{escbk}->{bookmark}});
undef(@{$doc->{escbk}->{filesystem}});
$user->filewrite($doc);

现在我们不仅可以创建文件,还可以控制文件写入的内容。

继续审计,可以发现在另一个路径(/vpns/portal/)由 Perl 函数 NetScaler::Portal::Handler(/netscaler/portal/modules/NetScaler/Portal/Handler.pm)处理:

<Location /vpns/portal/>
  SetHandler perl-script
  PerlResponseHandler NetScaler::Portal::Handler
  PerlSendHeader On
</Location>

handler 函数采用路径名称的最后一个“/”符号的信息,并且用来进行在 /netscaler/portal/templates/文件夹中搜索,然后尝试使用 Template Toolkit 进行呈现文件:

sub handler {
  my $r = shift;
...
  my $doc = $user->csd();

  my $tmplfile = $r->path_info();
  $tmplfile =~ s[^/][];
  my $template = Template->new({INCLUDE_PATH =>  NetScaler::Portal::Config::c->{template_dir},CACHE_SIZE => 64, COMPILE_DIR=> NetScaler::Portal::Config::c->{template_compile_dir}, COMPILE_EXT => '.ttc2'});
...
  $template->process($tmplfile, $doc) || do {
    my $error = $template->error();
    my $lcError = lc($error);
    if ( $error->type() eq "file" && $lcError  =~ /^file error/ && $lcError =~ /.not found$/ ) {
      return NOT_FOUND;
    }
    print NetScaler::Portal::UserPrefs::html_escape_string($error), "\n";
  };

  return OK;
}

因此,如果我们能够将文件加载到包含模板的文件夹中,那么我们也可以调用其渲染器。

模版工具包库不允许使用标准的 perl 执行代码,这样加大了利用的难度,也不能使用伪指令(如[% PERL %]):

5.png

考虑到不能使用标准的 perl 代码,后面我决定在标准库插件中搜索漏洞。之后我发现将注意力转向文件插件(/usr/local/lib/perl5/site_perl/5.14.2/mach/Template/Plugin/Datafile.pm),该文件非常小,在文件中发现 open 函数使用的两个参数,可能会触发 RCE。

sub new {
    my ($class, $context, $filename, $params) = @_;
    my ($delim, $line, @fields, @data, @results);
    my $self = [ ];
    local *FD;
    local $/ = "\n";
...
    open(FD, $filename)
...
}

后来在本地进行利用该漏洞,首先在 /tmp/ 目录中创建了名称为 testRCE 的文件:

6.png

现在,我们任意文件写入,并且配合 Template Toolkit库中的漏洞,进行触发未授权命令执行。

开发

我们可以创建一个payload 文件,以便稍后我们可以利用Template Toolkit库中的漏洞:

7.png

接下来进行渲染文件:

8.png

然后进行之前我们写入的 webshell,执行命令:

9.png

CVE-2019-19781 漏洞是我第一个在网络上引起关注的漏洞。在不久的将来,我打算分享有关流行产品的几个关键漏洞信息。当大部分用户安装补丁之后,文章就会进行发布。

时间线

  • 2019年12月05日 向 Citrix 报告
  • 2019年12月19日 Citrix发布的缓解措施
  • 2020年01月24日 Citrix发布的最新补丁

Citrix的建议:https://support.citrix.com/article/CTX267027

本文由白帽汇整理并翻译,不代表白帽汇任何观点和立场
来源:https://swarm.ptsecurity.com/remote-code-execution-in-citrix-adc/

最新评论

昵称
邮箱
提交评论