Python Penetration Testing: Being a Linux Control Freak!

How I control multiple services on multiple Linux Servers at once!

R. Eric Kiser
InfoSec Write-ups

--

R. Eric Kiser

During an engagement, you may come across various services running on multiple systems that you may desire to have turned on. These services may include FTP, web services for printers, cameras, and other management services, and even VNC servers. Conversely, you may also want to disable logging services, firewalls, antivirus, authentication, and access control services.

In an earlier article, I detailed the process of using a Python script to turn on windows services from a remote location. While it’s possible to do the same thing in Linux, I want to push the envelope. I despise monotonous, repetitive tasks, even when performing penetration tests. I prioritize efficiency. With the limited time I have to offer my clients, I aim to provide them with as much information as possible. Engaging in repetitive tasks detracts from my ability to serve them to the fullest extent. So for this article, I will demonstrate how I control multiple services on multiple Linux servers at once.

Ansible Playbook

We will need to build out a Ansible playbook. This playbook will need to use the Ansible service module to start or stop the service on the remote systems. This will need to be saved in the same location as the Python script. Note that I have named the file service_control.yml. The playbook takes two variables:

  • service_location: the location of the service file on the remote system, as entered by the user in the Python script.
  • service_state: the desired state of the service (on or off), as entered by the user in the Python script.
---
- name: Start or stop service on remote systems
hosts: all
become: yes
tasks:
- name: Start or stop service
service:
name: "{{ service_location.split('/')[-1] }}"
state: "{{ service_state }}"

Python Script

For the Python script the only module we will need to import is the subprocess module. Next, we will request the IPs, service path location (if necessary), and the desired state of the service. The aim is to make a single comprehensive selection of the services we want to enable or disable.

import subprocess

# Prompt user for list of IP addresses of remote systems
remote_ips = input("Enter comma-separated list of IP addresses of remote systems: ").split(',')

# Prompt user for location of service to start
service_location = input("Enter location of service to start (e.g. /etc/systemd/system/): ")

# Prompt user for desired state of service (on/off)
service_state = input("Would you like the service to be on or off? ").lower()

We can use the subprocess module to run an external command that invokes the ansible command-line tool. The ansible command is then used to run a command that checks the status of the service on each remote system that we specified.

To specify the remote user when connecting the system we use a -u, your_username. Replace your_username with the username you are using to connect to the remote systems.

we use the -k to prompt the for our SSH password when connecting to the remote system.

As a way to prompt for the sudo password when running the command on the remote system we use the --ask-become-pass

We then use-a 'systemctl is-active ' + service_location.split('/')[-1] to specify the command to run on the remote system. We are using the command systemctl to check the status of the service specified by the service_location variable. The service_location variable is parsed to extract the name of the service file, which is used as the argument to the systemctl command.

The capture_output=True parameter tells subprocess to capture the output of the command, and the text=True parameter tells subprocess to return the output as a string.

Now the ansible_check variable can hold the result of the subprocess command, which is a CompletedProcess object. The stdout attribute of the CompletedProcess object contains the output of the Ansible command. In this case, the output is the status of the service (active, inactive, or unknown). The stderr attribute of the CompletedProcess object contains any error messages generated by the Ansible command.

We check the output of the Ansible command to determine the current state of the service. I know that is a lot of detail. However it is not all that bad once you see the code together. It is also very easy to modify once your base script is written.

# Check current state of service on each system
for remote_ip in remote_ips:
# Use subprocess module to run an external command that invokes the ansible command-line tool
# Pass a list of command-line arguments that specify the inventory (-i), remote user (-u), ask for SSH password (-k),
# prompt for sudo password (--ask-become-pass), and an ad-hoc command (-a) that checks the status of the service
ansible_check = subprocess.run(['ansible', remote_ip + ',', '-u', 'your_username', '-k', '--ask-become-pass', '-a', 'systemctl is-active ' + service_location.split('/')[-1]], capture_output=True, text=True)

# Check the output of the ansible command to determine current state of the service
if 'inactive' in ansible_check.stdout:
current_state = 'off'
elif 'active' in ansible_check.stdout:
current_state = 'on'
else:
current_state = 'unknown'

# Print current state of service for each system
print("Current state of service on " + remote_ip + ": " + current_state)

If desired state is different from current state, we want to run the Ansible playbook to change state. This is done by using subprocess module to run the command service_contol.yml which invokes the ansible-playbook we wrote earlier. We can then pass a list of command-line arguments that specify the inventory (-i), remote user (-u), ask for SSH password (-k), prompt for sudo password ( — ask-become-pass), and extra variables (-e) for service location, name, and state.

 if service_state != current_state:
ansible_playbook = subprocess.run(['ansible-playbook', '-i', remote_ip + ',', '-u', 'your_username', '-k', '--ask-become-pass', '-e', 'service_location=' + service_location, '-e', 'service_state=' + service_state, 'service_control.yml'], capture_output=True, text=True)

Finally we check the return code of the ansible-playbook command to determine success or failure and print the output to the terminal for each system

 if ansible_playbook.returncode == 0:
print("Service turned " + service_state + " successfully on " + remote_ip)
else:
print("Error turning service " + service_state + " on " + remote_ip + ": " + ansible_playbook.stderr)

Conclusion

Whew 😅. With this script, you can easily control and automate the status of services on remote systems. Moreover, if you frequently need to manage the same services, you can modify the script to accept a list of service locations. Doing so will save you time and effort by eliminating the need to input the same commands repeatedly. Therefore, I highly recommend implementing this modification for efficient service management (a challenge💪). If you like this article please follow, clap and respond. Happy Hunting!

Challenge Answer:

--

--

R. Eric Kiser is highly skilled certified information security manager with 10+ years of experience in the field.