Why is this an issue?
Code injections occur when applications allow the dynamic execution of code instructions from untrusted data.
An attacker can influence the
behavior of the targeted application and modify it to get access to sensitive data.
What is the potential impact?
An attacker exploiting a dynamic code injection vulnerability will be able to execute arbitrary code in the context of the vulnerable
application.
The impact depends on the access control measures taken on the target system OS. In the worst-case scenario, the process that executes the code
runs with root privileges, and therefore any OS commands or programs may be affected.
Below are some real-world scenarios that illustrate some impacts of an attacker exploiting the vulnerability.
Denial of service and data leaks
In this scenario, the attack aims to disrupt the organization’s activities and profit from data leaks.
An attacker could, for example:
- download the internal server’s data, most likely to sell it
- modify data, send malware
- stop services or exhaust resources (with fork bombs for example)
This threat is particularly insidious if the attacked organization does not maintain a disaster recovery plan (DRP).
Root privilege escalation and pivot
In this scenario, the attacker can do everything described in the previous section. The difference is that the attacker also manages to elevate
their privileges to an administrative level and attacks other servers.
Here, the impact depends on how much the target company focuses on its Defense In Depth. For example, the entire infrastructure can be compromised
by a combination of code injections and misconfiguration of:
- Docker or Kubernetes clusters
- cloud services
- network firewalls and routing
- OS access control
How to fix it in Commons Compiler
Code examples
The following code is vulnerable to arbitrary code execution because it compiles and runs HTTP data.
Noncompliant code example
import org.codehaus.janino.ScriptEvaluator;
@Controller
public class ExampleController
{
@GetMapping(value = "/")
public void exec(@RequestParam("message") String message) throws IOException, InvocationTargetException {
ScriptEvaluator se = new ScriptEvaluator();
se.cook("System.out.println(\" + message \");");
se.evaluate(null);
}
}
Compliant solution
import org.codehaus.janino.ScriptEvaluator;
@Controller
public class ExampleController
{
@GetMapping(value = "/")
public void exec(@RequestParam("message") String message) throws IOException, InvocationTargetException {
ScriptEvaluator se = new ScriptEvaluator();
se.setParameters(new String[] { "input" }, new Class[] { String.class });
se.cook("System.out.println(input);");
se.evaluate(new Object[] { message });
}
}
How does this work?
Allowing users to execute code dynamically generally creates more problems than it solves.
Anything that can be done via dynamic code execution can usually be done via a language’s native SDK and static code.
Therefore, our suggestion
is to avoid executing code dynamically.
If the application requires the execution of dynamic code, additional security measures must be taken.
Dynamic parameters
When the untrusted values are only expected to be values used in standard processing, it is generally possible to provide them as parameters of the
dynamic code. In that case, care should be taken to ensure that only the name of the untrusted parameter is passed to the dynamic
code and not that its value is expanded into it. After that, the dynamic code will be able to safely access the untrusted parameter content and
perform the processing.
The compliant code example uses such an approach.
Allow list
When the untrusted parameters are expected to contain operators, function names or other reflection-related values, best practices would encourage
using an allow list. This one would contain a list of accepted safe values that can be used as part of the dynamic code.
When receiving an untrusted parameter, the application would verify its value is contained in the configured allow list. If it is present, the
parameter is accepted. Otherwise, it is rejected and an error is raised.
Another similar approach is using a binding between identifiers and accepted values. That way, users are only allowed to provide identifiers, where
only valid ones can be converted to a safe value.
Resources
Articles & blog posts
Standards