While extremely similar to the previous one, this variation uses Python standard libraries instead of special bash syntax to open a network connection and set up the reverse shell:
- The Python interpreter opens a network socket to the remote host using functions in the standard socket library
- All three standard streams (STDIN/STDOUT/STDERR) are modified to reference the network socket with
- Finally, an interactive shell spawns with the existing streams set up and
/bin/sh -i as the command line
The basic workflow for this looks a lot like the first example, and the outcome is the same: a new reverse shell!
Detection opportunity 1
The following detection logic can be used to detect most variations of reverse shells regardless of if they use Bash, Python, Java, or any other similar utility:
When examining processes using network sockets in this way, the process stream will take the format
socket:[<number>] and link to the corresponding network socket; the number is the identifier for this socket.
Additional considerations for detection logic may include:
- non-standard file descriptors being used
- named pipes placed between a process and the network socket (commonly seen with variations on “netcat without the -e” reverse shells)
Malicious script download and execution without touching disk
A common method of evading detection involves downloading malicious scripts and piping them directly into a shell to execute. This tactic avoids writing anything to disk, where a good antivirus can detect and remediate a threat before a foothold can be established. In our experience, this tactic is most commonly used by groups attempting to deploy cryptominers (because of course).
Every version of this attack pattern we’ve seen goes something like:
- A system binary is used to download the shell script from a remote host—most often
- The downloader then pipes its output to a shell via STDOUT
- The shell reads the malicious script from STDIN and executes commands accordingly
Since the downloader process is piping directly into the shell, its STDOUT will align with the shell’s STDIN. Even when additional processes are placed between the download and execution of the shell script, this chain of STDIN/STDOUT can be followed to understand how malicious commands are being processed, such as when the malicious script needs to be Base64 decoded before execution.
Detection opportunity 2
A detection analytic for this technique is fairly straightforward, but data collection may pose a problem. Unfortunately, there is no single telemetry source that correlates the reader and writer of a pipe, but that data can be found scattered across various locations in the /proc/ filesystem, including an individual process’s file descriptors (
/proc/$PID/fd/), and the global net connections files (e.g.,
curl || standard_input_process ==
Detection opportunity 3
Or, to catch variations where Base64 encoding is used, focus on
base64 and examine both its STDIN and STDOUT pipes simultaneously:
curl || standard_input_process ==
Like the sockets in the previous example, the process stream will take the format
pipe:[<number>] and use the ID of the corresponding unnamed pipe as the number. Additional effort is required to resolve the process on the other end of that pipe.
You should also consider the following when writing detection logic:
- Scheduling services such as
atd initiating these commands
- The user account associated with these processes. This may be normal for a developer account, but a webserver user like
www-data probably should not be performing this activity
Appending a command to a startup profile
Lastly, many locations on Linux systems store scripts designed to execute commands based on some predefined event, such as system startup, a user logging in, or a specific time elapsing. Many malware families have used these locations to persist on systems by appending to existing scripts, which helps avoid suspicion from curious administrators.
Detection opportunity 4
Often when we see this activity, it takes the form of shell commands echoing data directly to a file. System services, such as
cron or web servers, will leverage the Bourne shell to execute commands as child processes using the
sh -c syntax with a redirection to the intended startup profile. When this happens, the shell will execute the command and overwrite the STDOUT stream with the startup profile, as seen with the following detection analytic:
/etc/rc.d/* || standard_output ==
.*rc || standard_output ==
Just a handful of common startup locations are used above, but there are many possible locations depending on what software is installed, what subsystems are used, etc. Surveying your environment ahead of time can help you understand which profiles are likely to be present on a given host.
Process streams are another building block of endpoint detection. On UNIX-based operating systems, every process opens with three standard streams that can be used in conjunction with other attributes to build robust detections. Many common attack techniques use them without even intending to, and, though it is possible for adversaries using process streams to evade scrutiny, it is much more difficult than other artifacts used for detection, such as command lines or process names.