By the end of this article, you’ll have a solid understanding of SSJI attacks and the tools & techniques required to detect and exploit SSJI vulnerabilities. So, let’s dive into the world of SSJI!
They are exposed if the input is not properly validated. For instance, using eval() to perform DoS (Denial of Service) will consume the entire CPU power. In essence, an attacker can also carry out or perform anything virtually on the system (within user permission limits). Once the attacker has successfully injected malicious code, it can then be used to perform a range of attacks, including but not limited to the following:
That was just the tip of the iceberg as both these attacks can have severe consequences. Now that we have developed a basic understanding of what SSJI is, let’s see a few examples along with some code snippets to understand how this vulnerability can be carried out.
For example, an attacker could supply the following value for userInput and in the background server, this payload will use the child_process module of Node.js to execute the rm -rf /* command that deletes all files that are present on the application server:
Let us take another example, Let’s consider a situation where a web application allows users to submit feedback that is later displayed in an administrator’s dashboard.
In this example, an attacker could identify that the application processes user feedback without proper validation which they can leverage to provide the following input as the feedback parameter:
In this example, the attacker has injected a URL that points to a local server that is running on port 8080 internally, which is accessible from the server that is vulnerable to SSJI. If the local server has any vulnerabilities, such as a weak authentication mechanism, the attacker could exploit it to gain access to sensitive information.
It should also be noted that SSRF may not be possible in every case, and the attacker might not be presented with the details every single time as the server will process the attacker’s input locally on the available services running on the target server.
As we have seen in the previous examples it must now be clear that SSJI can be used as part of a larger attack, such as remote command execution (RCE), in which an attacker can execute arbitrary commands on the server by injecting malicious code into the web application’s server-side code. RCE attacks are typically carried out by exploiting vulnerabilities in the server-side code, such as unvalidated user input or poorly secured APIs, to inject malicious code. The attacker can then use the injected code to execute arbitrary commands on the server, such as reading or modifying files, creating or deleting user accounts, or even installing backdoors to maintain persistence on the server. Below is an example of how SSJI can be used to carry out an RCE attack:
Let us try to see how SSJI can be used to achieve RCE on an application. Consider the following Node.js code, which takes user-supplied input and uses the exec() function from the child_process module in the backend to execute a shell command on the server:
In this example, the userInput variable is not properly validated or sanitized, which means an attacker could potentially inject a malicious shell command into the userInput value to execute arbitrary commands on the server. For example, an attacker could supply the ’; ls /’ value for userInput to execute a command that lists all files on the server. This value would append a semicolon to the end of the user input, effectively terminating the current command and allowing the attacker to execute any additional commands they choose. The second command in this example lists all files in the root directory of the server.
An attacker could also supply the following value for userInput to execute a command that downloads and executes a malicious script on the server:
This value would use the wget command to download a malicious script from the attacker’s server, and then pipe the output to the sh command, which would execute the script. This could allow the attacker to take control of the server or access sensitive information.
To prevent this type of attack, developers should properly validate and sanitize all user input to ensure that it does not contain any untrusted or malicious code. Additionally, developers should avoid using unsafe functions like exec() to execute shell commands on the server, and should instead use safer alternatives like the spawn() function from the child_process module, which can help prevent injection attacks by providing separate arguments for the command and its arguments.
There have been several CVEs (Common Vulnerabilities and Exposures) in various web frameworks and libraries related to SSJI. The following are a few interesting CVEs associated with SSJI, along with details on how the CVE can be exploited in a real-world scenario:
The following code snippet indicates the use of the eval function which is known to cause the SSJI vulnerabilities and is often time a potential attack vector.
The security researcher crafted the below-mentioned payload that leverages the vulnerability to execute arbitrary commands. By sending specific input like /etc/passwd to the demo application, they managed to exfiltrate sensitive information. The payload uses Node.js’s child_process.exec() to run the curl command and send the contents of the /etc/passwd file to an external server.
The security researcher was able to demonstrate, using the above-mentioned exploit code, that the vulnerable library fast-json-stringify, which incorrectly handled the input, could be used by an adversary to perform RCE, which he was able to achieve successfully, as shown in the screenshot below.
This vulnerability was marked as a High-risk issue by the team and was patched shortly after that and appropriate mitigations were put in place to effectively handle this weakness by Fastify.
The application features a search functionality that sends a GET request to the server with two parameters: q and SearchBy. The q parameter holds the search string, while the SearchBy parameter specifies the function to call, either firstName or lastName:
One SSJI payload to fetch the listing of the current directory would be as follows: res.end(require(‘fs’).readdirSync(‘.’).toString())
This payload leverages the fs module in Node.js, allowing the attacker to execute file system operations. readdirSync retrieves the contents of the current directory (denoted by the dot ‘.‘), and toString() converts the resulting array to a string. The res.end() method is commonly used to send a response back to the client, in this case, containing the directory listing:
To retrieve the source code of the app.js file, attackers can use the following SSJI payload: res.end(require(‘fs’).readFileSync(“<PATH>”))
In this payload, the <PATH> placeholder should be replaced with the appropriate path to the app.js file on the server. By executing this payload, the attacker can obtain the source code of app.js, which contains the source code of the application and the flag for this lab:
In this example, the userInput variable is first validated using a regular expression to ensure that it only contains alphanumeric characters. If the input fails the validation check, the server returns an error response and does not perform any further processing. If the input is valid, the userInput variable is then sanitised using a regular expression to remove any potentially malicious characters, such as quotes or backticks. This helps prevent injection attacks by ensuring that the input does not contain any code that could be executed on the server.
Finally, the sanitised user input is used to perform a safe operation, such as querying a database, and the results are returned to the client.