On February 1st, Juniper Threat Labs observed an attack that attempted to inject malicious code into Secure Shell (SSH) servers on Linux. The attack begins with an exploit against the Control Web Panel (CWP, formerly known as Centos Web Panel) server administration web application, injects code via LD_PRELOAD, and uses a custom, encrypted binary command-and-control protocol to exfiltrate credentials and machine capabilities. As of this writing, the malware command-and-control server is still active.
The attack starts with a command injection against Control Web Panel:
CWP has been plagued by security issues, including 37 0-day vulnerabilities disclosed by the Zero Day Initiative in 2020. Among these is a failure to sanitize the service_restart parameter, which follows a similar set of vulnerabilities in 2018.
Because of the number of vulnerabilities in CWP, the intentional encryption and obfuscation of their source code ostensibly for security reasons, and CWP’s failure to respond to ZDI’s recent disclosures, it is difficult to ascertain which versions of CWP are or remain vulnerable to this attack. In 2020, there were over 215k CWP installations accessible from the open internet, so the number of computers compromised in this campaign may be substantial.
On successful exploitation of the web panel, the following commands are executed.
First, the “sshins” installer binary is retrieved, executed, and deleted. Then the CWP logs are wiped of any mention of sshins and the shell history is cleared.
The sshins binary is a 64-bit Linux ELF executable. It is packed with UPX and the packed file has garbage bytes appended to it in an attempt to hinder automated unpacking. It does 3 things:
- Drops a Linux shared library to an architecture-specific location (in this case, /lib64/libs.so).
- Writes the name of the dropped file to a text file at /etc/ld.so.preload
- Restarts the OpenSSH service.
Hijacking the OpenSSH server process
Injecting the malicious code
The file /etc/ld.so.preload contains a directive to the dynamic linker telling it to load the specified shared library first, and to give precedence to the exported functions from the ld-preloaded library. Because the malicious libs.so library exports its own version of the bind() function, applications will use the backdoored version of this function instead of the standard implementation from Linux system libraries.
When the Open-SSH server daemon (sshd) restarts, libs.so will first execute an initialization function as the library is loaded, and then has the ability to inject its own code whenever sshd calls bind(). The sshd server processes use this hook in order to periodically beacon to the command-and-control (C2) server and to exfiltrate data, including a listing of system information such as CPU and OS details, amount of RAM, available disk space, and OpenSSH configuration:
In addition to the continuously-running server processes, sshd forks() a pair of new processes to handle each login connection. From these session-specific processes, the malicious bind() function launches an additional temporary sshd process that exfiltrates the incoming user’s login credentials.
The C2 communication involves the server 176[.]111.174.26 on port 443. Port 443 is typically used for HTTPS but here the traffic is raw TCP, hiding in plain sight on a common port. The server has a Russian IP address that is associated with a Bulgarian webhosting provider.
The client initiates communication with a simple directive, padded out to 8 bytes. (As we’ll discuss below, the malware uses an encryption algorithm with an 8-byte block size, but even unencrypted messages are always a multiple of 8 in length.) Following is the first packet sent to the server after the TCP handshake, with the 8-byte message highlighted.
The C2 server replies with the following message (TCP packet omitted for clarity):
The response consists of a header with the payload length (24 bytes), a command (0x0201), and the CRC32 checksum of the payload. The 24-byte payload is used to encrypt the exfiltrated data that is then sent back to the C2 server, as we’ll see in the next section.
Data sent back to the C2 server is encrypted using a variant of the Blowfish encryption algorithm that was used to secure game assets on the Nintendo and, more recently, incorporated into a reverse-engineering challenge from Kaspersky Lab. Below is publicly available encryption code that was reverse-engineered from the DS:
Then we have the decompiled encryption routine from the preloaded library:
Note, in particular, the use of the constants 0x12, 0x112, 0x212, and 0x312, which differs from the standard Blowfish implementation. (The decompiled code is functionally identical to the Gameboy routine, differing only due to loop-unrolling and other compiler optimizations.)
While the underlying encryption routine is taken directly from publicly available code, the malware authors incorporate some additional tricks to thwart analysis and decryption. Both Blowfish and the Nintendo variant require an S-box lookup table that remains constant throughout the encryption and decryption processes. But unlike the Nintendo implementation, the malware mutates its S-box prior to use. First, as the table is loaded from program memory, it is subject to several static transformations that make it harder to correlate the stored table with the one used for encryption. Then the encryption algorithm is run against portions of its own S-box, transforming it at each step. This process is initialized using part of the 24-byte payload received from the C2 server.
Once the table has been fixed, the actual encryption begins. The malware improves upon the Nintendo implementation by adding cipher-block chaining (CBC). With CBC, each 8-byte plaintext block is first XORed against the encrypted output from the previous block, and then that value is encrypted. The result is a chain where the encrypted value of each block depends on the value of the previous block. To start this process, the first block is XORed against an initialization vector (IV). Here, the IV is itself the XOR of the first and last 8 bytes of the payload from the C2 server.
Without CBC, a symmetric encryption algorithm is vulnerable to frequency analysis when the block size is small as well as other attacks in the general case. It appears that the authors of this malware went to a surprising amount of trouble to strengthen the Nintendo DS encryption, in stark contrast to the noisy behavior of their sshins installer.
Without allowing our compromised test machine to remain connected to the internet and be used for malicious purposes, it’s difficult to ascertain the exact motivations of the authors. But because the malware catalogs detailed system information and credentials but does not immediately begin mining cryptocurrency or amplifying the attack by attempting to spread further, we suspect that access to the compromised machines will be sold or rented as part of a botnet.
The malware and C2 server used in this campaign are detected and blocked by Juniper ATP and Juniper ATP Cloud, and the malicious traffic is detected by the IDP rule SSL:VULN:CWP-LINUX-C2-BACKDOR.
176[.]111.174.26 C2 server
ab9cc4ee82aa6f57ba2a113aab905c33e278c969399db4188d0ea5942ad3bb7d sshins (as delivered)
936ca431d17d738beab9735a3d6e658ff29f8337f52353fd60e286c94dd2c06b sshins (unpacked by UPX, after deleting appended data)
c8df513e9e4848e35af5246a2ba797540b68a9379a1df17e34550cb0258960e8 sshins (manually unpacked)