Skip to content

command execution

1 post with the tag “command execution”

Command Execution Principles and Detection

Java command execution vulnerabilities account for a significant proportion of disclosed vulnerabilities. This chapter will analyze the general principles of command execution, selection of Hook points, vulnerability cases, and detection algorithms.

9.1 Command Execution Principles

9.1.1 Command Execution APIs

Java command execution methods include:

  • java.lang.Runtime.exec()
  • java.lang.ProcessBuilder.start()
  • java.lang.ProcessImpl.start()
  • Calling dynamic link libraries via JNI (this method belongs to JNI injection and won’t be analyzed here)

The most commonly used API for command execution in Java is Runtime.getRuntime().exec(), with usage as follows:

Runtime.getRuntime().exec("touch /tmp/1.txt");

In fact, the Runtime class has 6 overloaded exec methods, as shown below:

public Process exec(String command)
public Process exec(String command, String[] envp)
public Process exec(String command, String[] envp, File dir)
public Process exec(String cmdarray[])
public Process exec(String[] cmdarray, String[] envp)
public Process exec(String[] cmdarray, String[] envp, File dir)

The first 5 methods ultimately call the last method, so only the third and sixth methods are introduced here.

public Process exec(String command, String[] envp, File dir)
throws IOException {
if (command.length() == 0)
throw new IllegalArgumentException("Empty command");
// Parse the string into token stream
StringTokenizer st = new StringTokenizer(command);
String[] cmdarray = new String[st.countTokens()];
for (int i = 0; st.hasMoreTokens(); i++)
cmdarray[i] = st.nextToken();
// Call overloaded exec method
return exec(cmdarray, envp, dir);
}
  • command: The string of the command to be executed, which will be parsed into a token stream;

  • envp: Environment variables for the child process, represented as a string array where each element follows the name=value format. If the child process shares the same environment variables as the current process, this parameter is null;

  • dir: The working directory of the child process. If it’s the same as the current process’s working directory, this parameter is null;All exec methods ultimately call the following overloaded method:

public Process exec(String[] cmdarray, String[] envp, File dir)
throws IOException {
return new ProcessBuilder(cmdarray)
.environment(envp)
.directory(dir)
.start();
}

From the source code, we can see that the exec method actually executes commands by creating a ProcessBuilder object and then calling its start method.

9.1.2 Underlying Call Chain

The commonly used command execution APIs are java.lang.Runtime.exec() and java.lang.ProcessBuilder.start(). In addition, there are more low-level methods such as java.lang.ProcessImpl.start(). Below are the common ways to execute commands in Java:

import java.lang.reflect.Method;
import java.util.Map;
public class Main {
public static void main(String[] args) throws Exception {
// Command definition methods
String command = "touch /tmp/1.txt /tmp/2.txt /tmp/3.txt";
String[] commandarray = {"touch", "/tmp/1.txt", "/tmp/2.txt", "/tmp/3.txt"};
// Command execution method 1
Runtime.getRuntime().exec(command);
// Command execution method 2
new ProcessBuilder(commandarray).start();```
// Command execution method 3
Class clazz = Class.forName("java.lang.ProcessImpl");
Method method = clazz.getDeclaredMethod("start", new String[]{}.getClass(), Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
method.setAccessible(true);
method.invoke(null, commandarray, null, ".", null, true);
}
}

Tracing the source code reveals that all command executions ultimately call the method java.lang.UNIXProcess.forkAndExec. Let’s examine its code:

Code location: jdk11/src/java.base/unix/classes/java/lang/ProcessImpl.java

private native int forkAndExec(int mode, byte[] helperpath,
byte[] prog,
byte[] argBlock, int argc,
byte[] envBlock, int envc,
byte[] dir,
int[] fds,
boolean redirectErrorStream)
throws IOException;

This is a native method. In IDEA, debug the above command execution code and throw an exception within the forkAndExec method, as shown in Figure 9-1 below.

Figure 9-1 Debugging the underlying command execution code

Figure 9-1 Debugging the underlying command execution codeThe exception call stack is shown below:

Exception in thread "main" java.lang.SecurityException: rce block by rasp!
at java.lang.UNIXProcess.forkAndExec(Native Method)
at java.lang.UNIXProcess.<init>(UNIXProcess.java:247)
at java.lang.ProcessImpl.start(ProcessImpl.java:134)
at java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
at java.lang.Runtime.exec(Runtime.java:621)
at java.lang.Runtime.exec(Runtime.java:451)
at java.lang.Runtime.exec(Runtime.java:348)
at Main.main(Main.java:12)

The above test code runs on Unix systems, where the final class for command execution is java.lang.UNIXProcess. If executed on Windows systems, the call stack differs slightly. Figure 9-2 summarizes the command execution call process across different operating systems.

Figure 9-2 Command execution method call process in Windows and Linux operating systems

Figure 9-2 Command execution method call process in Windows and Linux operating systems

9.1.3 Selection of Hook Points

Traditional RASP typically selects classes at the first level shown in the diagram above, namely java.lang.ProcessImpl (JDK9 and above) and java.lang.UNIXProcess (JDK8 and below), targeting their <init> and start methods. Since these are Java-level methods, their bytecode can be directly modified to add detection logic. However, the final method for command execution is forkAndExec, meaning that only hooking start methods leaves potential bypass possibilities (refer to command execution bypass cases for details, not expanded here).

Therefore, when selecting hook points for command execution, theoretically the lower the level the better if performance is not a concern. However, third-level methods are implemented in C/C++ and cannot be hooked via Java Agent. We must settle for second-level methods as hook points.

Thus, RASP’s command execution hook points should at least include:

  • For Unix versions of JDK:
    • JDK 8 and below: Hook point is java.lang.UNIXProcess.forkAndExec
    • JDK above 8: Hook point is java.lang.ProcessImpl.forkAndExec
  • For Windows versions of JDK: Hook point is java.lang.ProcessImpl.create (no JDK version differences on Windows systems)

9.2 Native Command Execution in Java

From previous chapters, we’ve learned that RASP fundamentally works by modifying target method bytecode to insert detection logic at method entry, return, and exception throwing points.

Among Java methods, there’s a special category that lacks method bodies, including interface methods, abstract methods, and native methods. Native methods serve as interfaces for Java to call non-Java code, typically core JVM methods like the native command execution methods discussed earlier. This section explains RASP’s principles for modifying native methods and provides a demo implementation.### 9.2.1 Basic Principles

  • Native methods and C++ implementation parsing rules

Here we use command execution methods as an example. First, let’s examine the native method for command execution and its local implementation.

Source location: jdk11/src/java.base/unix/classes/java/lang/ProcessImpl.java

private native int forkAndExec(int mode, byte[] helperpath,
byte[] prog,
byte[] argBlock, int argc,
byte[] envBlock, int envc,
byte[] dir,
int[] fds,
boolean redirectErrorStream)
throws IOException;

The corresponding native method implementation (hotspot) is as follows:

Source location: jdk11/src/java.base/unix/native/libjava/ProcessImpl_md.c

JNIEXPORT jint JNICALL
Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,
jobject process,
jint mode,
jbyteArray helperpath,
jbyteArray prog,
jbyteArray argBlock, jint argc,
jbyteArray envBlock, jint envc,
jbyteArray dir,
jintArray std_fds,
jboolean redirectErrorStream){
// Not the focus of this article, code omitted...
}

We can see that the implementation name of the native method is composed of the Java class’s package name and method name. This rule is called standard resolution.

  • Setting Prefix for native method resolution

Modifying bytecode mainly relies on methods like addTransformer and retransformClasses in the java.lang.instrument.Instrumentation API interface. If we delve deeper into the Instrumentation API, we also notice the following method exists:

Source location: jdk11/src/java.instrument/share/classes/java/lang/instrument/Instrumentation.java```java void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix);

As indicated by the method's documentation, when standard resolution fails, the corresponding native method implementation can be located by adding a prefix to the Java name.
This feature is disabled by default and must be enabled in the `MANIFEST.MF` configuration file of the JavaAgent package with:
`Can-Set-Native-Method-Prefix: true`. An example MANIFEST.MF is shown below:
```java
Manifest-Version: 1.0
Premain-Class: com.jrasp.example.agent.Agent
Agent-Class: com.jrasp.example.agent.Agent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Can-Set-Native-Method-Prefix: true

The above MANIFEST.MF file not only configures the JavaAgent entry class but also enables three switches: Can-Redefine-Classes, Can-Retransform-Classes, and Set-Native-Method-Prefix.
You can use the isNativeMethodPrefixSupported method in the Instrumentation API to check whether this feature is enabled for the Java Agent.

  • Example of Native Method Resolution

Consider the following native method, whose implementation corresponds to standard resolution:

native boolean foo(int x); ====> Java_somePackage_someClass_foo(JNIEnv* env, jint x);

If a ClassTransformer is added to the JVM with setNativeMethodPrefix set to wrapped_, the resolution rule when standard resolution fails is as follows:

native boolean wrapped_foo(int x); ====> Java_somePackage_someClass_foo(JNIEnv* env, jint x);

Figure 9-3 Two Resolution Rules for Native Methods

Figure 9-3 Native Method Resolution Rules

Method linking can occur in two ways: explicit resolution using the JNI function RegisterNatives and normal automatic resolution.
For RegisterNatives, the JVM will attempt this association:

method(foo) -> nativeImplementation(foo)

When this fails, the specified prefix will be prepended to the method name to achieve correct resolution:

method(wrapped_foo) -> nativeImplementation(foo)
```For automatic resolution, the JVM will attempt:
```java
method(wrapped_foo) -> nativeImplementation(wrapped_foo)

If this fails, it will remove the specified prefix from the implementation name and retry resolution, resulting in correct resolution:

method(wrapped_foo) -> nativeImplementation(foo)

If one of the above mappings is found, execution proceeds. Otherwise, since no suitable resolution method exists, this process is declared failed.

  • Multiple Transformer Scenarios

The virtual machine resolves transformers in the order they were added to the JVM (i.e., the sequence of addTransformer calls). Suppose three transformers need to be added with their respective orders and prefixes: transformer1 with prefix1_, transformer2 with prefix2_, and transformer3 with prefix3_. The virtual machine’s resolution rule is:

native boolean prefix3_prefix2_prefix1_foo(int x); ====> Java_somePackage_someClass_foo(JNIEnv* env, jint x);

9.2.2 Modifying Native Methods Using ASM

Since native methods cannot have instructions directly inserted (they lack bytecode), they must be wrapped with non-native methods that can contain instructions. For example, given the following native method:

native boolean foo(int x);

We can transform the bytecode file into:

boolean foo(int x) {
//... record entry to foo ...
return wrapped_foo(x);
}
native boolean wrapped_foo(int x);

And set the native method resolution rule as:

method(wrapped_foo) -> nativeImplementation(foo)

Thus, the native method hooking strategy can be broken down into three steps:

  • Convert the original native method to a non-native method and add a method body;
  • Call the wrapped native method within the method body;
  • Add the wrapper native method;

The following code demonstrates using ASM to modify command execution methods. Key code snippets:

Agent startup class code is shown below:

public class Agent {
public static void premain(String args, Instrumentation inst) {
main(args, inst);
}
public static void agentmain(String args, Instrumentation inst) {
main(args, inst);
}```
public static void main(String args, Instrumentation inst) {
System.out.println(String.format("%s INFO [rasp] %s ",
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss").format(new Date()), "enter agent"));
// Register ClassFileTransformer object
RaspClassFileTransformer raspClassFileTransformer = new RaspClassFileTransformer(inst);
inst.addTransformer(raspClassFileTransformer);
}
}

After loading the Agent, register a ClassFileTransformer object with JVM’s Instrumentation.

Here’s the implementation of ClassFileTransformer code as follows:

public class RaspClassFileTransformer implements ClassFileTransformer {
private final Instrumentation inst;
public RaspClassFileTransformer(Instrumentation inst) {
this.inst = inst;
}
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
// Match specified classes
if ("java/lang/UNIXProcess".equals(className) || "java/lang/ProcessImpl".equals(className)) {
final ClassReader cr = new ClassReader(classfileBuffer);
final ClassWriter cw = new ClassWriter(cr, COMPUTE_FRAMES | COMPUTE_MAXS);
cr.accept(new RaspClassVisitor(ASM9, cw, cr.getClassName(), inst, this), EXPAND_FRAMES);
return dumpClassIfNecessary(cr.getClassName(), cw.toByteArray());
}```
return null;
}
// Dump modified bytecode to file
private static byte[] dumpClassIfNecessary(String className, byte[] data) {
final File dumpClassFile = new File("./rasp-class-dump/" + className + ".class");
final File classPath = new File(dumpClassFile.getParent());
if (!classPath.mkdirs() && !classPath.exists()) {
return data;
}
try {
FileUtils.writeByteArrayToFile(dumpClassFile, data);
} catch (IOException e) {
e.printStackTrace();
}
return data;
}
}

In the transform method, when command execution related classes are matched, it enters the method modification process. The code that modifies the target method’s bytecode is in RaspClassVisitor. Let’s look at its implementation:

public class RaspClassVisitor extends ClassVisitor {
private RaspMethod method = null;
private final String targetClassInternalName;
private final Instrumentation inst;
private RaspClassFileTransformer raspClassFileTransformer;
private final static String NATIVE_PREFIX = "$$JRASP$$_"; // Native method prefix
public RaspClassVisitor(final int api, final ClassVisitor cv, String targetClassInternalName, Instrumentation inst,
RaspClassFileTransformer raspClassFileTransformer) {
super(api, cv);
this.targetClassInternalName = targetClassInternalName;
this.inst = inst;
this.raspClassFileTransformer = raspClassFileTransformer;
}
``````java
@Override
public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
if ("forkAndExec".equals(name)) { // Match specified method
// Set native method parsing prefix
if (inst.isNativeMethodPrefixSupported()) {
inst.setNativeMethodPrefix(raspClassFileTransformer, NATIVE_PREFIX);
} else {
throw new UnsupportedOperationException("Native Method Prefix Unspported");
}
// Modify method access modifier
// Change from private native int forkAndExec to private int forkAndExec
int newAccess = access & ~Opcodes.ACC_NATIVE;
method = new RaspMethod(access, NATIVE_PREFIX + name, desc);
final MethodVisitor mv = super.visitMethod(newAccess, name, desc, signature, exceptions);
return new AdviceAdapter(api, new JSRInlinerAdapter(mv, newAccess, name, desc, signature, exceptions), newAccess, name, desc) {
@Override
public void visitEnd() {
// Call native method $$JRASP$$_forkAndExec within forkAndExec method
loadThis();
loadArgs();
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, targetClassInternalName, method.getName(), method.getDescriptor(), false);
returnValue();
super.visitEnd();
}
};
}
return super.visitMethod(access, name, desc, signature, exceptions);
}

@Override public void visitEnd() { if (method != null) { // Add a new native method with prefix, i.e. $$JRASP$$_forkAndExec int newAccess = (Opcodes.ACC_PRIVATE | Opcodes.ACC_NATIVE | Opcodes.ACC_FINAL); MethodVisitor mv = cv.visitMethod(newAccess, method.getName(), method.getDescriptor(), null, null); mv.visitEnd(); } super.visitEnd(); } }

In the visitMethod method, when a target method is matched, first set the prefix for native method resolution, then modify the method's access modifier,
which involves removing the native keyword from the native method and calling the prefixed native method within the method. In the visitEnd method, a new prefixed native method is added.Let's examine the configuration of the agent project's pom.xml. The key configurations related to the agent are as follows:
```xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Premain-Class>com.jrasp.example.agent.Agent</Premain-Class>
<Agent-Class>com.jrasp.example.agent.Agent</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
<!--Allow setting prefix for native method resolution-->
<Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix>
</manifestEntries>
</archive>
</configuration>
</plugin>

9.2.3 Modified Native Method

After compilation, start in premain mode:

java -javaagent:rce-agent-1.0-SNAPSHOT.jar -jar springboot.jar

Since the command execution class won’t be actively loaded after application startup, we need to trigger its loading to modify its bytecode. After modifying the bytecode, rce-agent will dump the bytecode files in the rasp-class-dump directory under the application folder.

Let’s look at the bytecode of java.lang.UNIXProcess’s forkAndExec method before and after modification.

Original bytecode:

private final native int forkAndExec(int var1, byte[] var2, byte[] var3, byte[] var4, int var5, byte[] var6, int var7, byte[] var8, int[] var9, boolean var10);
```Modified Bytecode:
```java
// Modify method access modifier and call native method within the method
private int forkAndExec(int var1, byte[] var2, byte[] var3, byte[] var4, int var5, byte[] var6, int var7, byte[] var8, int[] var9, boolean var10) throws IOException {
// Typically add detection logic here
return this.$$JRASP$$_forkAndExec(var1, var2, var3, var4, var5, var6, var7, var8, var9, var10);
}
// New native method with prefix
private final native int $$JRASP$$_forkAndExec(int var1, byte[] var2, byte[] var3, byte[] var4, int var5, byte[] var6, int var7, byte[] var8, int[] var9, boolean var10);

The modified forkAndExec now has a method body, allowing RASP detection logic to be added when executing this method.

9.2.4 Limitations of Native Method Hooking

Not all native methods can be modified through bytecode changes—there are certain limitations. If the method to be modified has macro definitions in the file jdk11/src/hotspot/share/classfile/vmSymbols.hpp as shown in Figure 9-4:

Figure 9-4 Methods that cannot be directly modified

Figure 9-4 Methods that cannot be directly modified To enhance native methods defined in VM_INTRINSICS_DO, the following VM parameters need to be added:

-XX:+UnlockDiagnosticVMOptions
-XX:CompileCommand=dontinline,${ClassName}::${MethodName}
-XX:DisableIntrinsic=${MethodNameId}

Where ${ClassName} represents the fully qualified class name, {MethodName} represents the class method name, and ${MethodNameId} represents the intrinsic id obtained from the first parameter of the VM_INTRINSICS_DO macro definition.

For example, for java.lang.System.currentTimeMillis, the JVM parameters to enable native hooking are as follows:

-XX:+UnlockDiagnosticVMOptions
-XX:CompileCommand=dontinline,java.lang.System::currentTimeMillis
-XX:DisableIntrinsic=_currentTimeMillis

9.3 Apache Spark Command Injection Vulnerability

  • Vulnerability Overview

The Apache Spark UI provides ACL functionality through the configuration option spark.acls.enable. When ACL is enabled, code paths in HttpSecurityFilter may allow attackers to perform impersonation by providing arbitrary usernames. Consequently, attackers can access permission checking functionality that ultimately constructs and executes a Unix shell command based on attacker input. Successful exploitation of this vulnerability could lead to arbitrary shell command execution.

  • Affected VersionsApache Spark <= v3.0.3

3.1.1 <= Apache Spark <= 3.1.2

3.2.0 <= Apache Spark <= 3.2.1

  • Environment Setup

Download spark-3.2.1-bin-hadoop2.7.tgz (https://repo.huaweicloud.com/apache/spark/spark-3.2.1/spark-3.2.1-bin-hadoop2.7.tgz)

Figure 9-5 Download URL for spark-3.2.1-bin-hadoop2.7.tgz

Figure 9-5 Download URL for spark-3.2.1-bin-hadoop2.7.tgz

The key to triggering the vulnerability lies in whether ACL is enabled. The script to start Spark is as follows:

./spark-shell --conf spark.acls.enable=true

Figure 9-6 Starting Spark

Figure 9-6 Starting Spark

From the startup logs, it can be seen that the Spark web server access address is: http://192.168.2.4:4040

Append ?doAs=command to the startup address, and here execute touch%20/tmp/1.txt, which creates a file named 1.txt in the /tmp directory.

  • Attack Request

Figure 9-7 Launching an attack on Spark

Figure 9-7 Launching an attack on Spark

Figure 9-8 Result of the attack on Spark

Figure 9-8 Result of the attack on Spark

  • Attack Details

Figure 9-9 RASP detection result - Vulnerability details

The attack log intercepted by RASP is as follows: Figure 9-9 RASP detection result - Vulnerability details

Figure 9-10 RASP detection result - HTTP request log

Figure 9-10 RASP detection result - HTTP request log

9.4 Command Execution Detection Algorithm### 9.4.1 Stack Detection Algorithm

This algorithm is one of the most widely used in RASP, whether for offline analysis or real-time detection. The detection principle is relatively simple: when command execution occurs, the current thread’s call stack is obtained. If the call stack contains illegal stack frames, it can be identified as an attack. Common illegal attack stacks are as follows:

com.thoughtworks.xstream.XStream.unmarshal
java.beans.XMLDecoder.readObject
java.io.ObjectInputStream.readObject
org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInput.readObject
com.alibaba.fastjson.JSON.parse
com.fasterxml.jackson.databind.ObjectMapper.readValue
payload.execCommand
net.rebeyond.behinder
org.springframework.expression.spel.support.ReflectiveMethodExecutor.execute
freemarker.template.utility.Execute.exec
freemarker.core.Expression.eval
bsh.Reflect.invokeMethod
org.jboss.el.util.ReflectionUtil.invokeMethod
org.codehaus.groovy.runtime.ProcessGroovyMethods.execute
org.codehaus.groovy.runtime.callsite.AbstractCallSite.call
ScriptFunction.invoke
com.caucho.hessian.io.HessianInput.readObject
org.apache.velocity.runtime.parser.node.ASTMethod.execute
org.apache.commons.jexl3.internal.Interpreter.call
javax.script.AbstractScriptEngine.eval
javax.el.ELProcessor.getValue
ognl.OgnlRuntime.invokeMethod
javax.naming.InitialContext.lookup
org.mvel2.MVEL.executeExpression
org.mvel.MVEL.executeExpression
ysoserial.Pwner
org.yaml.snakeyaml.Yaml.load
org.mozilla.javascript.Context.evaluateString
command.Exec.equals
java.lang.ref.Finalizer.runFinalizer
java.sql.DriverManager.getConnection
// Note: Stack signatures are sourced from OpenRasp

Generally, known vulnerabilities can be reproduced and their call stacks captured using RASP, selecting classes and methods with higher execution privileges.

Since stack signatures are extracted from known vulnerabilities or exploit chains, a limitation of this algorithm is its inability to effectively address threats from unknown vulnerabilities.

9.4.2 Reflective Command ExecutionRegular users also have command execution needs, but when executing commands, they typically directly invoke command execution APIs rather than using reflection. This is because code logic that employs reflection for command execution is more complex and performs worse. Consider the following call stack, which demonstrates normal user command execution:

Scenario: Direct invocation of command execution API

// [1] Command execution API
java.lang.ProcessImpl.start(ProcessImpl.java)
java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
java.lang.Runtime.exec(Runtime.java:620)
java.lang.Runtime.exec(Runtime.java:485)
// [2] User code
com.alibaba.inf.cto.util.ProcessInfoUtil.getSystemInfoByCommand(ProcessInfoUtil.java:256)
com.alibaba.inf.cto.util.ProcessInfoUtil.getHostInfoByIp(ProcessInfoUtil.java:242)
com.alibaba.adsc.predict.monitor.ponitor.getHostName(PMonitor.java:105)
com.alibaba.adsc.predict.monitor.ponitor.lambda$makeSureExist$0(PMonitor.java:94)
com.alibaba.adsc.predict.monitor.ponitor$$Lambda$427/2097793174.run(Unknown Source)
java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
java.util.concurrent.FutureTask.run(FutureTask.java:266)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
java.lang.Thread.run(Thread.java:745)

From the call stack, we can see that the user directly invokes the command execution API in ProcessInfoUtil.getSystemInfoByCommand without any reflection operations in between. If there are reflection call stacks between user code and command execution code, it can be identified as an attack.

9.4.3 User Input Parameter Matching

Detect whether the parameters for command execution originate from user input, meaning the command execution parameters are included in HTTP requests. User inputs include: HTTP parameters, cookies, and headers, etc. For example, consider the following command string:

cat /etc/passwd

Convert the command execution parameters into valid token streams, i.e., three strings: cat, etc, and passwd, then compare them with HTTP parameters. This algorithm can effectively identify command execution backdoors, but its performance degrades when HTTP request parameters or command execution parameter strings are lengthy.### 9.4.4 Detecting Common Penetration Commands
Common penetration commands include but are not limited to:

whoami、wget、echo、touch、pwd、ifconfig、net、wget、telnet、ls、ping
// Other parameters can be referenced from the RASP command execution module

The frequency of command execution is typically not very high. In addition to the detection algorithms mentioned above, all system command executions can be logged and reported. After manual verification, these logs can serve as the system’s security baseline.