Skip to content

file

2 posts with the tag “file”

Java Agent Principle Analysis

Diagnostic tools commonly used for performance issues, such as arthas and btrace, are all implemented based on Java Agent. A Java Agent is a JAR package, but its startup method differs from regular JAR packages. For regular JAR packages, startup is achieved by specifying the main function of a class, whereas a Java Agent cannot run independently—it must attach to a running Java application. This chapter first implements a simple Java Agent, then analyzes the initialization and underlying implementation source code of Java Agent.

6.1 Java Agent Basics

6.1.1 Implementing a Simple Java Agent

The code for the Agent class is as follows:

package org.example;
import java.lang.instrument.Instrumentation;
public class Agent {
// Loaded via VM parameters, executed before the main method of the Java program
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("premain run");
}
// Loaded via Attach, executed after the Java program starts
public static void agentmain(String agentArgs, Instrumentation inst) {
System.out.println("agentmain run");
}
}

You need to implement specific Agent logic in either the agentmain or premain methods, such as reading thread states, monitoring data, and modifying class bytecode.Due to the special nature of Java Agent, some additional configurations are required. You need to create a MANIFEST.MF file in the META-INF directory, which can be generated either manually or automatically using a Maven plugin. Here we recommend using the Maven plugin for automatic generation. Add the following plugin configuration in the pom.xml file, where the values for Premain-Class and Agent-Class should be the fully qualified name of the Agent class mentioned above. The configuration is as follows:

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifestEntries>
<Premain-Class>org.example.Agent</Premain-Class>
<Agent-Class>org.example.Agent</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
<Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>

After compiling the project, extract the output jar file and check the META-INF/MANIFEST.MF file as shown below. You can see that the Java Agent entry class org.example.Agent has been written into the file.

Manifest-Version: 1.0
Premain-Class: org.example.Agent
Archiver-Version: Plexus Archiver
Built-By: root
Agent-Class: org.example.Agent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Can-Set-Native-Method-Prefix: true
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_261

6.1.2 Loading Agent

A Java Agent can be loaded either before program execution or dynamically during runtime. The main difference lies in the initialization timing of the Agent.

  • Command-line startupAdd the following parameter to the JVM command line:
-javaagent:/path/to/your/jarpath[=options]

The options parameter is optional. For example, the startup parameters for the Jacoco agent are as follows:

java -javaagent:jacocoagent.jar=includes=*,output=tcpserver,port=6300,address=localhost,append=true -jar application.jar

The premain method allows the following two method signatures:

public static void premain(String agentArgs, Instrumentation inst);
public static void premain(String agentArgs);

When both methods exist, the one with the Instrumentation parameter takes precedence and will be called first by the JVM. Here is an example of loading a Java Agent in a SpringBoot application via the command line:

java -javaagent:/path/to/your/my-agent-1.0.jar -jar application.jar

The terminal logs during service startup will display the “premain run” output.

  • Dynamic Loading at Runtime

After the application starts, load the Java Agent using the Attach mechanism provided by the JVM. The Attach mechanism has been detailed in previous chapters and will not be repeated here.

6.1.3 Agent Feature Switches

The following are definitions of Manifest Attributes for an Agent jar package:

  • Premain-Class

Specifies the entry class of the Agent to be loaded before application startup.

  • Agent-Class

Specifies the entry class of the Agent to be loaded at runtime.

  • Boot-Class-Path

Specifies the loading path for the Agent’s dependent jar packages. The jar packages in this path are loaded by the bootstrap classloader before the Agent is loaded.

  • Can-Redefine-Classes

Indicates whether the Agent is allowed to redefine classes. The default value is false.

  • Can-Retransform-Classes

Indicates whether the Agent is allowed to retransform classes. The default value is false.

  • Can-Set-Native-Method-Prefix

Indicates whether the Agent can set a prefix for native methods. If set to true, it allows the current Agent to set prefixes for native methods, indirectly enabling bytecode modification of native methods.

All six attributes above are used in Java Agents. Refer to the official documentation:

Official Documentation: https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/compact3-package-summary.html

An Agent jar package can contain both Premain-Class and Agent-Class. When a Java Agent is started via the command line, only Premain-Class is used, and Agent-Class is ignored. Conversely, when starting a Java Agent at runtime, only Agent-Class is used.

6.1.4 Java Agent Debugging

Before analyzing the initialization source code of a Java Agent, let’s first look at how to debug an Agent’s code, which is crucial for troubleshooting Agent-related issues.

  • Starting the Application

Add jdwp and Java Agent parameters to the JVM startup parameters in a SpringBoot application as shown below:

Figure 6-1 Debugging underlying command execution codeFigure 6-1 Debugging the underlying command execution code

The startup command is as follows:

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -javaagent:rce-agent-1.0-SNAPSHOT.jar -jar jetty-demo-0.0.1-SNAPSHOT.jar
  • Add debug parameters to the Java Agent project

Figure 6-2 Debugging the underlying command execution code

Figure 6-2 Debugging the underlying command execution code

  • Set breakpoints and run debug on Java Agent source code

Figure 6-3 Adding breakpoints at the premain entry method

Figure 6-3 Adding breakpoints at the premain entry Setting debug breakpoints at the premain entry can be challenging. You may add a slight delay when entering the premain method.

Figure 6-4 Adding breakpoints at the visitMethod method

Figure 6-4 Adding breakpoints at the visitMethod method

6.2 Agent Loading Source Code Analysis

https://blog.51cto.com/u_16213564/7607442

6.2.1 javaagent Parameter Parsing

During JVM startup, it reads JVM command-line parameters such as heap space, metaspace, and thread stack size. Numerous parameters are parsed during initialization, so this article focuses only on agent-related parameters like: agentlib, agentpath, and javaagent. When “-javaagent:/path/to/your/agent.jar” is added to the startup parameters during JVM launch, it can be loaded and initialized by the JVM. Let’s examine the implementation of this logic. All JVM startup parameters are parsed in parse_each_vm_init_arg. The following code snippet shows the parsing code for javaagent.

jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain){
// ...
jint parse_result=Arguments::parse(args);
if(parse_result!=JNI_OK)return parse_result;
// ...
}

The Arguments class is responsible for parameter parsing. Let’s look at the implementation of the parse member method:

jdk11/src/hotspot/share/runtime/arguments.cpp

jint Arguments::parse(const JavaVMInitArgs* initial_cmd_args) {
assert(verify_special_jvm_flags(), "deprecated and obsolete flag table inconsistent");
// Initialize ranges, constraints and writeables
JVMFlagRangeList::init();
JVMFlagConstraintList::init();
JVMFlagWriteableList::init(); // If flag "-XX:Flags=flags-file" is used it will be the first option to be processed.
const char* hotspotrc = ".hotspotrc";
bool settings_file_specified = false;
bool needs_hotspotrc_warning = false;
ScopedVMInitArgs initial_java_tool_options_args("env_var='JAVA_TOOL_OPTIONS'");
ScopedVMInitArgs initial_java_options_args("env_var='_JAVA_OPTIONS'");
// Pointers to current working set of containers
JavaVMInitArgs* cur_cmd_args;
JavaVMInitArgs* cur_java_options_args;
JavaVMInitArgs* cur_java_tool_options_args;
// Containers for modified/expanded options
ScopedVMInitArgs mod_cmd_args("cmd_line_args");
ScopedVMInitArgs mod_java_tool_options_args("env_var='JAVA_TOOL_OPTIONS'");
ScopedVMInitArgs mod_java_options_args("env_var='_JAVA_OPTIONS'");
jint code =
parse_java_tool_options_environment_variable(&initial_java_tool_options_args);
if (code != JNI_OK) {
return code;
}
code = parse_java_options_environment_variable(&initial_java_options_args);
if (code != JNI_OK) {
return code;
}
code = expand_vm_options_as_needed(initial_java_tool_options_args.get(),
&mod_java_tool_options_args,
&cur_java_tool_options_args);
if (code != JNI_OK) {
return code;
}
code = expand_vm_options_as_needed(initial_cmd_args,
&mod_cmd_args,
&cur_cmd_args);
if (code != JNI_OK) {
return code;
}```c
code = expand_vm_options_as_needed(initial_java_options_args.get(),
&mod_java_options_args,
&cur_java_options_args);
if (code != JNI_OK) {
return code;
}
const char* flags_file = Arguments::get_jvm_flags_file();
settings_file_specified = (flags_file != NULL);
if (IgnoreUnrecognizedVMOptions) {
cur_cmd_args->ignoreUnrecognized = true;
cur_java_tool_options_args->ignoreUnrecognized = true;
cur_java_options_args->ignoreUnrecognized = true;
}
// Parse specified settings file
if (settings_file_specified) {
if (!process_settings_file(flags_file, true,
cur_cmd_args->ignoreUnrecognized)) {
return JNI_EINVAL;
}
} else {
#ifdef ASSERT
// Parse default .hotspotrc settings file
if (!process_settings_file(".hotspotrc", false,
cur_cmd_args->ignoreUnrecognized)) {
return JNI_EINVAL;
}
#else
struct stat buf;
if (os::stat(hotspotrc, &buf) == 0) {
needs_hotspotrc_warning = true;
}
#endif
}
if (PrintVMOptions) {
print_options(cur_java_tool_options_args);
print_options(cur_cmd_args);
print_options(cur_java_options_args);
}
``````c
// Parse JavaVMInitArgs structure passed in, as well as JAVA_TOOL_OPTIONS and _JAVA_OPTIONS
jint result = parse_vm_init_args(cur_java_tool_options_args,
cur_java_options_args,
cur_cmd_args);
if (result != JNI_OK) {
return result;
}
// Call get_shared_archive_path() here, after possible SharedArchiveFile option got parsed.
SharedArchivePath = get_shared_archive_path();
if (SharedArchivePath == NULL) {
return JNI_ENOMEM;
}
// Set up VerifySharedSpaces
if (FLAG_IS_DEFAULT(VerifySharedSpaces) && SharedArchiveFile != NULL) {
VerifySharedSpaces = true;
}
// Delay warning until here so that we've had a chance to process
// the -XX:-PrintWarnings flag
if (needs_hotspotrc_warning) {
warning("%s file is present but has been ignored. "
"Run with -XX:Flags=%s to load the file.",
hotspotrc, hotspotrc);
}
if (needs_module_property_warning) {
warning("Ignoring system property options whose names match the '-Djdk.module.*'."
" names that are reserved for internal use.");
}
#if defined(_ALLBSD_SOURCE) || defined(AIX) // UseLargePages is not yet supported on BSD and AIX.
UNSUPPORTED_OPTION(UseLargePages);
#endif
ArgumentsExt::report_unsupported_options();
```The provided content is already in English and consists of C++ code blocks with conditional compilation directives and configuration settings. Since this is code and not Chinese text requiring translation, I'll return it exactly as provided:
```c++
#ifndef PRODUCT
if (TraceBytecodesAt != 0) {
TraceBytecodes = true;
}
if (CountCompiledCalls) {
if (UseCounterDecay) {
warning("UseCounterDecay disabled because CountCalls is set");
UseCounterDecay = false;
}
}
#endif // PRODUCT
if (ScavengeRootsInCode == 0) {
if (!FLAG_IS_DEFAULT(ScavengeRootsInCode)) {
warning("Forcing ScavengeRootsInCode non-zero");
}
ScavengeRootsInCode = 1;
}
if (!handle_deprecated_print_gc_flags()) {
return JNI_EINVAL;
}
// Set object alignment values.
set_object_alignment();
#if !INCLUDE_CDS
if (DumpSharedSpaces || RequireSharedSpaces) {
jio_fprintf(defaultStream::error_stream(),
"Shared spaces are not supported in this VM\n");
return JNI_ERR;
}
if ((UseSharedSpaces && FLAG_IS_CMDLINE(UseSharedSpaces)) ||
log_is_enabled(Info, cds)) {
warning("Shared spaces are not supported in this VM");
FLAG_SET_DEFAULT(UseSharedSpaces, false);
LogConfiguration::configure_stdout(LogLevel::Off, true, LOG_TAGS(cds));
}
no_shared_spaces("CDS Disabled");
#endif // INCLUDE_CDS
return JNI_OK;
}
``````c++
jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, bool* patch_mod_javabase, JVMFlag::Flags origin) {
const char* tail;
// Iterate through each startup parameter
for (int index = 0; index < args->nOptions; index++) {
const JavaVMOption* option = args->options + index;
// Other parameters omitted
// -agentlib, -agentpath parameter parsing
if (match_option(option, "-agentlib:", &tail) ||
(is_absolute_path = match_option(option, "-agentpath:", &tail))) {
if(tail != NULL) {
const char* pos = strchr(tail, '=');
size_t len = (pos == NULL) ? strlen(tail) : pos - tail;
char* name = strncpy(NEW_C_HEAP_ARRAY(char, len + 1, mtArguments), tail, len);
name[len] = '\0';
char *options = NULL;
if(pos != NULL) {
options = os::strdup_check_oom(pos + 1, mtArguments);
}
add_init_agent(name, options, is_absolute_path);
}
// -javaagent parameter parsing
} else if (match_option(option, "-javaagent:", &tail)) {
if (tail != NULL) {
size_t length = strlen(tail) + 1;
char *options = NEW_C_HEAP_ARRAY(char, length, mtArguments);
jio_snprintf(options, length, "%s", tail);
// Add an instrument agent
add_instrument_agent("instrument", options, false);
}
}
// ...
return JNI_OK;
}

The add_instrument_agent method primarily encapsulates agent parameters into AgentLibrary objects and adds them to a linked list. The code is as follows:

// -agentlib and -agentpath arguments
static AgentLibraryList _agentList;
``````c++
void Arguments::add_instrument_agent(const char* name, char* options, bool absolute_path) {
_agentList.add(new AgentLibrary(name, options, absolute_path, NULL, true));
}

6.2.2 Agentlib Loading

Agent initialization and

jdk11/src/hotspot/share/runtime/thread.cpp

jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
// Other VM initialization code
// Launch agents
if (Arguments::init_agents_at_startup()) {
create_vm_init_agents();
}
}

The implementation of create_vm_init_agents method is as follows:

void Threads::create_vm_init_agents() {
extern struct JavaVM_ main_vm;
AgentLibrary* agent;
JvmtiExport::enter_onload_phase();
// Traverse the agentList linked list and call Agent_OnLoad to complete agent initialization
for (agent = Arguments::agents(); agent != NULL; agent = agent->next()) {
OnLoadEntry_t on_load_entry = lookup_agent_on_load(agent);
if (on_load_entry != NULL) {
// Call Agent_OnLoad function
jint err = (*on_load_entry)(&main_vm, agent->options(), NULL);
if (err != JNI_OK) {
vm_exit_during_initialization("agent library failed to init", agent->name());
}
} else {
vm_exit_during_initialization("Could not find Agent_OnLoad function in the agent library", agent->name());
}
}
JvmtiExport::enter_primordial_phase();
}

File Access

Chapter Introduction

This chapter will explore the application of RASP in file access, including commonly used file read/write APIs, examples of file access vulnerabilities, and how to set up Hook points and detection algorithms.

Examples of File Access Vulnerabilities

  • Vulnerability Introduction

Apache Solr has an arbitrary file deletion vulnerability that remains unpatched in the current latest version (8.8.2). The root cause is that the function Files.deleteIfExists() does not validate the filename to be deleted. Additionally, Apache Solr’s Config API is publicly exposed, allowing any user to modify configurations, thereby causing harm.

  • Environment Setup

Download the binary and source code files of Apache Solr 8.8.2 for debugging purposes.

Download link: http://archive.apache.org/dist/lucene/solr/8.8.2

apachae-solr-download

Navigate to the bin directory and execute:

Terminal window
solr -e dih

Access http://IP:8983/solr/#/

solr-startup.png

  • Vulnerability Reproduction

Create a new file in the temporary directory:

Terminal window
touch /tmp/solr.txt

Send a POST request to any solr core’s config API, such as /solr/db/config or /solr/solr/config.

HTTP body:

{
"add-requesthandler": {
"name": "/test2021",
"class":"solr.PingRequestHandler",
"healthcheckFile":"../../../../../../../../../../../../../tmp/solr.txt"
}
}

The complete request is as follows:

img.png

Check if the creation was successful:

img.png

Send Request

Send a GET request to the core’s config API with the parameter action=DISABLE, for example: /solr/db/test2021?action=DISABLE

file-delete

Check /tmp/solr.txt File

The file has been deleted.

solr_txt

Hook Points and Detection Algorithms

To defend against file access vulnerabilities, Hook points can be set up in the application to intercept file operations and apply detection algorithms. Here are some common Hook points and detection strategies:

  • File Open Hook: Check the file path and permissions before a file is opened.
  • File Write Hook: Validate the content and target location before a write operation is executed.
  • File Delete Hook: Confirm the legality of the operation before a file is deleted.

Detection algorithms may include:

  • Path Normalization: Ensure paths are normalized and do not contain relative references like ”..”.
  • Whitelist/Blacklist: Allow or deny file access requests based on predefined rules.
  • Content Inspection: Scan uploaded file content to detect potential malicious code.