Skip to content

Latest commit

 

History

History
1112 lines (918 loc) · 41 KB

File metadata and controls

1112 lines (918 loc) · 41 KB

Lab 2: Security Enhanced Linux (SELinux)

Lab Length
  • Long (~30 mins)

Goal

Use Security Enhanced Linux® (SELinux) to mitigate attacks that exploit privilege escalation vulnerabilities

2.1: Introduction

Security Enhanced Linux (SELinux) can help proactively mitigate systems from the consequences of exploits during the window of vulnerability—​that period of time after an exploit is discovered and before a security fix is released. This protection is provided by defining SELinux policies on your systems.

  • In the first and second sections of the lab, you assume the role of a hacker and exploit vulnerabilities on given Red Hat® Enterprise Linux® 7 and Fedora systems, and then you explore how SELinux can help to protect these systems.

  • In the third section, you use Ansible® to turn SELinux on for an environment of web servers in an automated fashion.

  • In the last section, you configure SELinux according to a U.S. Department of Defense STIG security control policy rule.

2.1.1: SELinux Policy

SELinux isolates all of the processes running on the system to mitigate attacks that take advantage of privilege escalation vulnerabilities. A privilege escalation vulnerability exists whenever a process gains more access rights than it is allowed to have. To prevent this, SELinux enforces a Mandatory Access Control (MAC) policy over processes running on the system. It labels every process, file, or directory according to rules specified in a security policy known as the SELinux policy. The policy also specifies how processes interact with each other and how they can access files and directories. SELinux denies every action that it is not explicitly allowed by the policy.

In the following sections, you see examples where using this process isolation can help protect you from an exploit of a privilege escalation vulnerability.

2.1.2: Shellshock Vulnerability and SELinux

The Shellshock vulnerability is a bash exploit that adds a command after the env variable in a command, which allows an attacker to execute arbitrary code. The following image shows an example of the bash exploit on an Apache server:

lab2 shellshock 1

If you have a CGI script accessing user data or the internal network, this script can compromise both the internal network and user data on your system.

So what can SELinux do to mitigate the Shellshock bash exploit?

It is important to understand that SELinux does not block the exploit, but rather prevents escalation from confined domains. These confined domains are defined by the SELinux policy installed on a system. Because SELinux places controls on its types (SELinux labels), the process is limited to doing what it was designed to do and cannot access what is not allowed by the SELinux policy.

This image shows the situation as described with SELinux enabled and in enforcing mode:

lab2 shellshock 2

While the attacker can execute the CGI script, the attacker is not able to access either user data or certain parts of the internal network as defined in the SELinux policy.

The following sections describe how types provide process separation and how SELinux is able to mitigate the exploit’s effects.

2.2: Exploiting Red Hat Enterprise Linux 7 with Shellshock Vulnerability

In this part of the lab, you assume the role of a hacker and exploit Red Hat Enterprise Linux 7 (RHEL 7) using the Shellshock vulnerability.

As an administrator of Red Hat Enterprise Linux servers, you want to enable SELinux for the web servers in your environment to mitigate damages caused by zero-day vulnerabilities.

In this section, you exploit a RHEL system first with SELinux in permissive mode and again with SELinux in enforcing mode. You then analyze the SELinux denials.

2.2.1: Logging in to the System and Navigating to the SELinux Scripts Directory

In this section, you log in to the selinux1.example.com system and navigate to the SELinux scripts directory.

The exploit is executed from the selinux1.example.com system.

  1. If you are not already there, log in to the bastion host as lab-user from your desktop system (replacing GUID with your lab-provided GUID and using r3dh4t1! as the password):

    [localhost ~]$ ssh [email protected]
  2. If you are not already root, become root and then log in to the selinux1.example.com system:

    [lab-user@workstation-GUID ~]# ssh [email protected]
  3. Change to the /root/selinux_scripts/ directory that has the scripts needed to perform the attack:

    [root@selinux1 ~]# cd /root/selinux_scripts
    Note
    You can find copies of these selinux_scripts at this Red Hat GitHub security demos link.
  4. Repeat the previous steps in this section on a second terminal that is also connected to selinux1.example.com.

2.2.2: Exploiting the System with SELinux in Permissive Mode

In this section, using the two terminal shells that you opened on selinux1.example.com, you listen on TCP port 9999 (in the first shell) and execute the exploit script (in the second).

This image depicts how the exploit is executed:

lab2 shellshock flow

On the left, you see the victim server, the selinux4 system. An HTTP request is sent to this server from the attacker machine selinux1 on the right. The HTTP request opens a shell to the attacker machine, which is listening on port 9999.

The victim server, selinux4, has SELinux set up in permissive mode by default. It is running the Apache web server and an older version of bash.

  1. In the first terminal shell that you opened earlier on selinux1.example.com and whose working directory is /root/selinux_scripts, start Ncat listening on TCP port 9999:

    [root@selinux1 selinux_scripts]# nc -lvp 9999
    Ncat: Version 7.50 ( https://nmap.org/ncat )
    Ncat: Listening on :::9999
    Ncat: Listening on 0.0.0.0:9999

    Ncat is a feature-packed networking utility that reads and writes data across networks.

  2. From the second selinux1 terminal, run the exploit:

    [root@selinux1 selinux_scripts]# ./shellshock_exploit.sh
  3. Back on the first selinux1 terminal (where you executed nc), look for a bash prompt to appear:

    [root@selinux1 selinux_scripts]# nc -lvp 9999
    Ncat: Version 7.50 ( https://nmap.org/ncat )
    Ncat: Listening on :::9999
    Ncat: Listening on 0.0.0.0:9999
    Ncat: Connection from 192.168.0.24.
    Ncat: Connection from 192.168.0.24:38668.
    bash: no job control in this shell
    bash-4.2$
  4. At the bash prompt that appeared in the selinux1 shell, type id, then uname -a, followed by exit to see the results of the exploit:

    bash-4.2$ id
    id
    uid=48(apache) gid=48(apache) groups=48(apache) context=system_u:system_r:httpd_sys_script_t:s0
    bash-4.2$ uname -a
    uname -a
    Linux selinux4.example.com 3.10.0-418.el7.x86_64 #1 SMP Thu May 26 20:35:02 EDT 2016 x86_64 x86_64 x86_64 GNU/Linux
    bash-4.2$ exit

    As you can see from the resulting output, these commands were executed on selinux4, the victim server (selinux4), despite the fact that the session was started on the attacker machine (selinux1).

    The id command prints real and effective user and group IDs, where the user and group are apache, demonstrating that the CGI script was started as the Apache user.

    The uname command prints system information. You can see the selinux4.example.com host name being printed, which indicates that this is the victim system.

    These commands indicate that the attack succeeded.

2.2.4: Exploiting the System with SELinux in Enforcing Mode

The victim server (selinux4) has been running SELinux in permissive mode. In this section, you switch SELinux to enforcing mode and then repeat the attack.

  1. Connect to selinux4 and switch to enforcing mode:

    [root@selinux1 selinux_scripts]# ssh root@selinux4 setenforce 1
  2. Begin the Shellshock attack again by listening on TCP port 9999 in one of the terminal shells for the selinux1 system:

    [root@selinux1 selinux_scripts]# nc -lvp 9999
    Ncat: Version 7.50 ( https://nmap.org/ncat )
    Ncat: Listening on :::9999
    Ncat: Listening on 0.0.0.0:9999
  3. From the other terminal shell on the selinux1 system, run the exploit again:

    [root@selinux1 selinux_scripts]# ./shellshock_exploit.sh

    This time there is no bash prompt on the terminal where you executed the nc command. This is because SELinux blocked this access.

2.2.5: Analyzing the SELinux Denial

In this section, you analyze what happened and why SELinux blocked the Shellshock exploit.

  1. Connect to the selinux4 system from the selinux1 machine:

    [root@selinux1 selinux_scripts]# ssh root@selinux4
    [root@selinux4 ~]# ausearch -m AVC -ts today | grep name_connect
    type=AVC msg=audit(1524909646.681:86): avc:  denied  { name_connect } for  pid=2091 comm="bashbug.sh" dest=9999 scontext=system_u:system_r:httpd_sys_script_t:s0 tcontext=system_u:object_r:jboss_management_port_t:s0 tclass=tcp_socket

    This is the AVC record from the audit daemon. It says that the CGI script, called bashbug.sh (labeled as httpd_sys_script_t) tried to connect to TCP port 9999 (labeled as jboss_management_port_t). There is no allow rule for this access, so the kernel denied access. This demonstrates that SELinux mitigated this attack.

2.3: Exploiting Fedora 27 with a Runcescape Vulnerability

The CVE-2019-5736 runc escape is the latest vulnerability in the area of containers. It is a privilege escalation vulnerability that allows arbitrary code execution as root when a malicious process inside a container escapes from the container namespace and executes on the host system. Because any container can contain a malicious process, a container can gain root access to the entire system when a system administrator starts that container.

Fortunately, SELinux technology, which separates containers from each other and from the host system, blocks this exploit.

Note
For more information about this exploit, see Runcescape Security Vulnerability.

In this section, you assume the role of a hacker and try to exploit Fedora 27 using the Runcescape vulnerability. As mentioned, this vulnerability in runc allows breaking out from a container to gain root-level access on the host machine.

An earlier release of Fedora 27 is used for the purpose, because the Docker vulnerability is fixed in Red Hat Enterprise Linux 7. Fedora 27 is in its end-of-life state, which means no updates are provided and its Docker daemon is still vulnerable.

As an administrator of Red Hat Enterprise Linux servers, you want to enable SELinux for containers in your environment to mitigate damages caused by zero-day vulnerabilities.

This lab exercise consists of three key parts:

  • Exploiting a Fedora system with SELinux in enforcing mode

  • Exploiting a Fedora system with SELinux in permissive mode

  • Analyzing SELinux denials

2.3.1: Logging in to the selinux6.example.com System and Navigating to the selinux Directory

The exploit is executed from the selinux6.example.com system.

  1. If not already there, log in to the bastion host as lab-user from your desktop system (replacing GUID with your lab-provided GUID and using r3dh4t1! as the password):

    [localhost ~]$ ssh [email protected]
  2. If you are not already root, become root and then log in to the selinux6.example.com system:

    [lab-user@workstation-GUID ~]# ssh [email protected]
  3. Change to the /root/selinux_scripts/ directory that has the scripts needed to perform the attack:

    [root@selinux6 ~]# cd /root/selinux_scripts
    Note
    You can find copies of these selinux_scripts at this Red Hat GitHub security demos link.

2.3.2: Reproducing the Attack

In this section, you reproduce the attack. First, you verify the state of the host system, then you prepare the environment, and finally you execute the program within the container.

  1. Verify that the host system is running SELinux in enforcing mode:

    [root@selinux6 selinux_scripts]# sestatus
    SELinux status:                 enabled
    SELinuxfs mount:                /sys/fs/selinux
    SELinux root directory:         /etc/selinux
    Loaded policy name:             targeted
    Current mode:                   enforcing
    Mode from config file:          enforcing
    Policy MLS status:              enabled
    Policy deny_unknown status:     allowed
    Memory protection checking:     actual (secure)
    Max kernel policy version:      31

    Now you prepare the environment for the attack. A runcescape.sh shell script is already prepared for you. The script installs and starts a Docker container engine that has the vulnerable runc bundled inside. Then a standard container with the latest Ubuntu distribution is downloaded. Finally, the script uploads a malicious program to the container and prepares the exploit.

  2. Execute the script to prepare the environment:

    [root@selinux6 selinux_scripts]# ./runcescape.sh
    [+] Installing docker
    [+] Starting docker
    [+] Downloading container
    [+] Uploading exploit
    [+] Executing docker

    Now, the container with the malicious program is ready and waiting for a system administrator to execute the program inside the container. In this case, the malicious program is renamed to bash and replaced with the real /bin/bash.

  3. Start the Docker container, which executes the malicious program:

    [root@selinux6 selinux_scripts]# docker exec -it pwnme /bin/bash
    [+] bad_libseccomp.so booted.
    [+] opened ro /proc/self/exe <3>.
    [+] constructed fdpath </proc/self/fd/3>
    [+] bad_init is ready -- see </tmp/bad_init_log> for logs.
    [*] dying to allow /proc/self/exe to be unused...

    Due to the nature of the attack, it may be necessary to execute the last command multiple times to make sure the attack is successful.

  4. Execute the docker exec -it pwnme /bin/bash command multiple times.

2.3.3: Analyzing the SELinux Denial

In this section, you analyze what happened and why SELinux blocked the runc escape exploit.

  1. Run the ausearch command to see the SELinux denial:

    [root@selinux6 selinux_scripts]# ausearch -m AVC -ts today | grep container_runtime_exec_t
    type=AVC msg=audit(1554464510.001:479): avc:  denied  { write } for  pid=5190 comm="bad_init" name="docker-runc-current" dev="dm-0" ino=9162730 scontext=system_u:system_r:container_t:s0:c915,c946 tcontext=system_u:object_r:container_runtime_exec_t:s0 tclass=file permissive=0

    This is the AVC record from the audit daemon. It says that a malicious process inside the bad_init container (labeled container_t) is trying to modify the docker-runc-current container (labeled as container_runtime_exec_t) on the host system. SELinux blocked this clearly malicious behavor.

2.3.4: Repeating the Attack in Permissive Mode

The selinux6 system has been running SELinux in enforcing mode. In this section, you switch SELinux to permissive mode and then you repeat the attack with SELinux in permissive mode.

  1. Switch SELinux to permissive mode:

    [root@selinux6 selinux_scripts]# setenforce 0
    [root@selinux6 selinux_scripts]# getenforce
    Permissive
  2. First, prepare the new container with the exploit:

    [root@selinux6 selinux_scripts]# ./runcescape.sh
    [+] Installing docker
    [+] Starting docker
    [+] Downloading container
    [+] Uploading exploit
    [+] Executing docker

    The container with the malicious program is ready for the system administrator to execute it. In this case, the malicious program is renamed to bash and replaced with the real /bin/bash.

  3. Repeat the attack and, because SELinux is in permissive mode, expect it to be successful:

    [root@selinux6 selinux_scripts]# docker exec -it pwnme /bin/bash
    [+] bad_libseccomp.so booted.
    [+] opened ro /proc/self/exe <3>.
    [+] constructed fdpath </proc/self/fd/3>
    [+] bad_init is ready -- see </tmp/bad_init_log> for logs.
    [*] dying to allow /proc/self/exe to be unused...

    Because of the nature of the attack, it is sometimes necessary to execute the last command multiple times to make sure the attack is successful.

  4. Execute the last command (docker exec -it pwnme /bin/bash) multiple times until you see this output:

    [root@selinux6 selinux_scripts]# docker exec -it pwnme /bin/bash
    rpc error: code = 2 desc = containerd: container not started

    This proves that exploit was successful.

  5. Run the ausearch -m AVC -ts today command again and note that it shows the same SELinux denial as it did in enforcing mode—​but because the machine is in permissive mode, the payload of the exploit is also executed.

  6. Determine that the exploit actually worked:

    [root@selinux6 selinux_scripts]# cd /
    [root@selinux6 /]# ls
    bin   dev  HACKED  lib    media  opt   root  sbin  sys  usr
    boot  etc  home    lib64  mnt    proc  run   srv   tmp  var

    The payload creates a file named HACKED in the root file system.

    Note the HACKED file. This is simply an example—​a real exploit, rather than merely creating a file in the / directory, would have allowed arbitrary and far more dangerous code execution as root.

2.4: Enabling SELinux via Ansible

SELinux brings additional security to an environment and often needs to be modified to reflect the current environment configuration. In such cases, SELinux can be switched during debugging to permissive mode so that it does not block the basic functionality of the system. In permissive mode, you can run the system for some time to debug all possible SELinux AVC denials. Once you have adjusted the rules to handle all of the desired functionality, you can switch SELinux back to enforcing mode.

There are many ways to view or modify the installed SELinux policy. In this section, you use the SELinux Ansible role to distribute all of the required changes in the SELinux policy to make your Apache configuration work with SELinux in enforcing mode.

More specifically, you enable SELinux in your environment, which consists of an Apache server using both custom and standard paths for web files, so that the Apache server is fully confined by SELinux. You do this by using the SELinux system roles feature as an Ansible role to configure SELinux in an automated fashion.

2.4.1: Setting Up Environment

In this section, you have an environment with Apache web servers, where both default and custom paths for Apache web files are used. Specifically:

  • /var/www/html (default)

  • /var/www_new/html (custom)

These web files are accessible using TCP/80 and TCP/7070 ports on each web server:

  • selinux2.example.com:80 (default)

  • selinux2.example.com:7070 (custom)

  • selinux3.example.com:80 (default)

  • selinux3.example.com:7070 (custom)

  • selinux5.example.com:80 (default)

  • selinux5.example.com:7070 (custom)

By default, SELinux is disabled for all web servers. In a fully automated fashion, you turn SELinux on for all web servers without breaking any functionality using the SELinux system roles feature as an Ansible role.

The SELinux part of the lab environment consists of four machines:

  • selinux1, selinux1.example.com (RHEL-8 admin host)

  • selinux2, selinux2.example.com (RHEL-8 host)

  • selinux3, selinux3.example.com (RHEL-6 host)

  • selinux5, selinux5.example.com (RHEL-7 host)

The first selinux1.example.com host is used as an administrative interface to set up the other hosts, where you complete all of the configuration steps.

2.4.2: Reviewing Setup Steps (Preconfigured)

Important
All of the steps in this Setup Steps section have already been performed in the lab environment for you. They are described here for informative purposes, and must be executed only if you use the revert script for this lab.

2.4.2.1: Viewing Basic Preconfigured Environment

In this section, you explore what is already configured for you in this part of the lab.

  1. If not already there, log in to the workstation bastion host as lab-user from your desktop system (replacing GUID with your lab-provided GUID and using r3dh4t1! as the password):

    [localhost ~]$ ssh [email protected]
  2. Log in to the selinux1.example.com system as root:

    [lab-user@workstation-GUID ~]# ssh [email protected]
  3. Look at the DNS records on the selinux1 server:

    [root@selinux1 ~]# cat /etc/hosts
    127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
    ::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
    192.168.0.20 selinux2
    192.168.0.21 selinux3
    192.168.0.6 selinux5
  4. Install Ansible if it is not already installed on the selinux1 host:

    [root@selinux1 ~]# pip3 install ansible
  5. Change to the selinux_scripts working directory on the selinux1 host:

    [root@selinux1 ~]# cd /root/selinux_scripts
  6. Look at the created inventory file for your Ansible usage:

    [root@selinux1 selinux_scripts]# cat inventory
    selinux2 ansible_python_interpreter=/usr/libexec/platform-python
    selinux3
    selinux5

2.4.2.2: Testing Preconfigured Apache Web Servers with SELinux Disabled

The Apache web servers are already set up using the setup-webserver.yml playbook, which was executed on the selinux2, selinux3, and selinux5 hosts. SELinux is also turned off.

All of the Ansible commands in this section were executed from selinux1.example.com.

In this section, you test whether all of the servers are available via the ansible command.

  1. If not already there, log in to the workstation bastion host as lab-user from your desktop system (replacing GUID with your lab-provided GUID and using r3dh4t1! as the password):

    [localhost ~]$ ssh [email protected]
  2. If you are not already root, become root and then log in to the selinux1.example.com system:

    [lab-user@workstation-GUID ~]# ssh [email protected]
  3. Change to the selinux_scripts working directory on the selinux1 host:

    [root@selinux1 ~]# cd /root/selinux_scripts
  4. Test which servers are accessible:

    [root@selinux1 selinux_scripts]# ansible all -i inventory -m ping -u root

    This Ansible invocation specifies all listed servers in the inventory file and tests to see if they are accessible. Accessible servers return the pong response:

        selinux3 | SUCCESS => {
            "changed": false,
            "ping": "pong"
        }
        selinux5 | SUCCESS => {
            "changed": false,
            "ping": "pong"
        }
        selinux2 | SUCCESS => {
            "changed": false,
            "ping": "pong"
        }
  5. Configure Apache web servers on the given servers via the setup_webserver.yml playbook:

    [root@selinux1 selinux_scripts]# ansible-playbook -i inventory -u root setup-webserver.yml

    This playbook performs the following actions for all of the hosts mentioned in the inventory file:

    • SELinux is disabled.

    • Apache web servers are:

      • Installed

      • Configured to listen on ports TCP/80 and TCP/7070 via the linux-sytem-roles/firewall Ansible role

      • Configured to use /var/www/html (default) and /var/www_new/html (custom) as root directories

      • Rebooted

  6. Install the setools-console package containing SELinux policy query tools, which is used for SELinux troubleshooting:

    [root@selinux1 selinux_scripts]# ssh root@selinux2 yum install setools-console -y
    
    [root@selinux1 selinux_scripts]# ssh root@selinux3 yum install setools-console -y
    
    [root@selinux1 selinux_scripts]# ssh root@selinux5 yum install setools-console -y

2.4.3: Testing the Preconfigured Setup

  1. Test the preconfigured setup steps:

    [root@selinux1 selinux_scripts]# hostname
    selinux1.example.com
    [root@selinux1 selinux_scripts]# cd /root/selinux_scripts
    [root@selinux1 selinux_scripts]# curl selinux{2,3,5}
    <h1>Default Document Root</h1>
    <h1>Default Document Root</h1>
    <h1>Default Document Root</h1>
    [root@selinux1 selinux_scripts]# curl selinux{2,3,5}:7070
    <h1>Custom Document Root</h1>
    <h1>Custom Document Root</h1>
    <h1>Custom Document Root</h1>
    [root@selinux1 selinux_scripts]# ssh root@selinux2 getenforce
    Disabled
    [root@selinux1 selinux_scripts]# ssh root@selinux3 getenforce
    Disabled
    [root@selinux1 selinux_scripts]# ssh root@selinux5 getenforce
    Disabled

2.4.4: Turning SELinux On

  1. Set SELinux to permissive mode and relabel the entire file system:

    [root@selinux1 selinux_scripts]# ansible-playbook -i inventory -u root enable-selinux.yml

    SELinux is switched to permissive mode using the enable-selinux playbook. This means that SELinux policy is enabled but not enforced.

  2. Use getenforce and sestatus to view the current SELinux mode for your servers:

    [root@selinux1 selinux_scripts]# ssh root@selinux2 getenforce
    [root@selinux1 selinux_scripts]# ssh root@selinux2 sestatus

    SELinux does not deny access, but denials are logged for actions that would have been denied had SELinux been running in enforcing mode.

  3. Run the curl command to show logged denials for certain actions:

    [root@selinux1 selinux_scripts]# curl selinux{2,3,5}:7070
    <h1>Custom Document Root</h1>
    <h1>Custom Document Root</h1>
    <h1>Custom Document Root</h1>
  4. Note that AVC denials are generated and and view the denials using the ausearch:

    [root@selinux1 selinux_scripts]# ssh root@selinux2
    
    [root@selinux2 ~]# ausearch -m AVC -su httpd_t -ts recent
    
    avc:  denied  { name_bind } for  pid=1830 comm="httpd" src=7070 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket
    avc:  denied  { read } for  pid=1831 comm="httpd" name="index.html" dev="vda3" ino=8511801 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:var_t:s0 tclass=file
    avc:  denied  { map } for  pid=778 comm="httpd" path="/var/www_new/html/index.html" dev="dm-0" ino=8751871 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:var_t:s0 tclass=file permissive=1
    avc:  denied  { open } for  pid=778 comm="httpd" path="/var/www_new/html/index.html" dev="dm-0" ino=8751871 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:var_t:s0 tclass=file permissive=1
    avc:  denied  { getattr } for  pid=778 comm="httpd" path="/var/www_new/html/index.html" dev="dm-0" ino=8751871 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:var_t:s0 tclass=file permissive=1

2.4.5: SELinux Troubleshooting

In the previous section, you enabled SELinux and AVC denials occurred. Denial messages are logged when SELinux denies access. In this section, you investigate why these denials occurred.

SELinux troubleshooting can be performed on both the selinux2 and selinux3 hosts. You use the selinux2 host in the following examples.

  1. Log in to the selinux2 host, if you are not already logged in:

    [root@selinux1 selinux_scripts]# ssh root@selinux2

2.4.5.1: Checking SELinux Port

  1. Verify that SELinux httpd_t process domain used for Apache web servers is not able to bind to TCP/7070 port by default:

    [root@selinux2 ~]# sesearch -A -s httpd_t -t unreserved_port_t -c tcp_socket -p name_bind | grep httpd_t

    There is no default rule for this access in the SELinux policy on the RHEL-7 selinux5 and RHEL-8 selinux2 hosts.

  2. Verify that Apache web servers can bind to other ports and these SELinux port types can be assigned to your selected custom port (TCP/7070):

    [root@selinux2 ~]# sesearch -A -s httpd_t -c tcp_socket -p name_bind | grep httpd_t

2.4.5.2: Checking SELinux File Context

The SELinux httpd_t process domain used for Apache web servers is not able to read a general /var content with the SELinux var_t file type.

  1. Verify that there is no rule for this access in the SELinux policy:

    [root@selinux2 ~]# sesearch -A -s httpd_t -t var_t -c file -p read
  2. Verify that Apache web servers can read a specific content with a specific SELinux file type:

    [root@selinux2 ~]# sesearch -A -s httpd_t -c file -p read
  3. Use the matchpathcon utility to decide the proper context for your alternate location for web files:

    [root@selinux2 ~]# matchpathcon /var/www/html
    /var/www/html    system_u:object_r:httpd_sys_content_t:s0
    [root@selinux2 ~]# exit

2.4.6: Viewing and Executing the SELinux setup-selinux.yml Ansible Playbook

In this section, you examine and then execute an Ansible Playbook that switches SELinux to enforcing mode and applies all of the required changes for your web servers' configurations.

The playbook uses the linux-system-roles/selinux Ansible role.

  1. Make sure that you are on the selinux1 system, then navigate to the /root/selinux_scripts directory:

    [root@selinux1 selinux_scripts]# hostname
    selinux1.example.com
    
    [root@selinux1 selinux_scripts]# pwd
    /root/selinux_scripts
  2. Open the setup-selinux.yml Ansible Playbook to take a closer look at it:

    [root@selinux1 selinux_scripts]# cat setup-selinux.yml
    
      - hosts: all
      become: true
      become_user: root
      vars:
        SELinux_type: targeted
        SELinux_mode: enforcing
        SELinux_change_running: 1
        SELinux_file_contexts:
           - { target: '/var/www_new(/.*)?', setype: 'httpd_sys_content_t', ftype: 'a' }
        SELinux_restore_dirs:
          - /var/www/html
          - /var/www_new/
        SELinux_ports:
          - { ports: '7070', proto: 'tcp', setype: 'http_port_t', state: 'present' }
      roles:
        - linux-system-roles.selinux

    In the vars section, you switch SELinux to enforcing mode:

        SELinux_type: targeted
        SELinux_mode: enforcing
        SELinux_change_running: 1

    Web servers use the custom /var/www_new/html path for web pages. SELinux labels must be fixed for this directory and subdirectories/files to reflect the default SELinux security labels for the /var/www/html location. This is done by the following lines in the playbook:

        SELinux_file_contexts:
            - { target: '/var/www_new(/.*)?', setype: 'httpd_sys_content_t', ftype: 'a' }

    After SELinux security labels are defined in the SELinux context database, these labels must be applied into extended attributes of selected files as done by these lines in the playbook:

        SELinux_restore_dirs:
            - /var/www_new

    All web servers are bound to the custom TCP/7070 port in the configuration. This setup must be reflected in a SELinux configuration as done in these lines of the playbook:

        SELinux_ports:
            - { ports: '7070', proto: 'tcp', setype: 'http_port_t', state: 'present' }
  3. Execute the setup_selinux.yml Ansible Playbook and apply these defined configurations for all of the servers:

    [root@selinux1 selinux_scripts]# ansible-playbook -i inventory -u root setup-selinux.yml

2.4.7: Viewing SELinux Configuration Changes

  1. Test and view all of the recent SELinux configuration changes:

    [root@selinux1 selinux_scripts]# ssh selinux2 semanage export
    
    [root@selinux1 selinux_scripts]# ssh selinux5 semanage export
    
    [root@selinux1 selinux_scripts]# ssh selinux3 semanage -o -
  2. Determine the current SELinux status for all of the servers:

    [root@selinux1 selinux_scripts]# ansible all -i inventory -u root -a getenforce
  3. Check the functionality with SELinux enabled:

        [root@selinux1 selinux_scripts]# curl selinux{2,3,5}
    	<h1>Default Document Root</h1>
    	<h1>Default Document Root</h1>
    	<h1>Default Document Root</h1>
        [root@selinux1 selinux_scripts]# curl selinux{2,3,5}:7070
    	<h1>Custom Document Root</h1>
    	<h1>Custom Document Root</h1>
    	<h1>Custom Document Root</h1>

2.4.8: Reverting Script

This revert script is needed to proceed to the next lab section (or if you plan to repeat the lab again from the beginning). Additionally, all of the steps in the Setup Steps section mentioned in the beginning of this lab must be executed, with the exception of the package installation steps.

In this section, you invoke the revert script.

  1. Run the revert script:

    [root@selinux1 selinux_scripts]# hostname
    selinux1.example.com
    
    [root@selinux1 selinux_scripts]# pwd
    /root/selinux_scripts
    
    [root@selinux1 selinux_scripts]# cat inventory
    selinux2 ansible_python_interpreter=/usr/libexec/platform-python
    selinux3
    selinux5
    
    [root@selinux1 selinux_scripts]# ansible-playbook -i inventory -u root revert-all.yml

2.5: Setting Up a System with SELinux Confined Users

As an enterprise system administrator, you may want your systems to follow the U.S. Department of Defense STIG security rule V-71971 so that your system is fully confined without unconfined users. In addition, you may want to have only one administrator user who can become root and manage the system, and to limit the access of other users.

In Red Hat Enterprise Linux, Linux users are mapped to the SELinux unconfined_u user by default. All of the processes run by unconfined_u are in the unconfined_t domain. This means that users can access the system within the limits of the standard Linux DAC policy. However, a number of confined SELinux users are available in Red Hat Enterprise Linux. This means that users can be restricted to a limited set of capabilities. Each Linux user is mapped to an SELinux user using SELinux policy, allowing Linux users to inherit the restrictions placed on SELinux users.

This lab section is comprised of three key parts:

  • Confining regular Linux users

  • Confining Linux root users

  • Using the revert script

2.5.1: Confining Regular Linux Users

  1. Execute the revert script if you did not do this in the previous section:

    [root@selinux1 selinux_scripts]# ansible-playbook -i inventory -u root revert-all.yml

    All actions are performed on the selinux5 host, which is a RHEL 7.5 system.

  2. If you are not already there, log in to the bastion host as lab-user from your desktop system (replacing GUID with your lab-provided GUID and using r3dh4t1! as the password):

    [localhost ~]$ ssh [email protected]
  3. Log in to the selinux1.example.com system as root, then SSH to selinux5.example.com as root:

    [lab-user@workstation-GUID ~]# ssh [email protected]
    [root@selinux1 ~]# ssh [email protected]
  4. Use the semanage login tool to assign Linux users to SELinux users:

    [root@selinux5 ~]# semanage login -l

    Users are mapped to unconfined_u by default.

2.5.1.1: Changing the Default Mapping

  1. Modify the record with __default__, which represents all of the users without an explicit mapping, to change the mapping of all Linux users:

    [root@selinux5 ~]# semanage login -m -s user_u -r s0 __default__
    [root@selinux5 ~]# semanage login -l

    system_u is a special user used only for system processes and is not listed.

2.5.1.2: Adding a Test User

After this, when users who are not root log in, their processes run in the user_t domain.

  1. Every user session, other than for root, runs with user_t:

    [root@selinux5 ~]# adduser user42
    [root@selinux5 ~]# passwd user42
    Tip

    You can select any password for user42, but make sure you remember what it is.

    [root@selinux5 ~]# ssh user42@localhost
    user42@localhost's password:
    [user42@selinux5 ~]$ id -Z
    user_u:user_r:user_t:s0
    [user42@selinux5 ~]$ ps axZ
    LABEL                     PID TTY    STAT  TIME COMMAND
    -                           1 ?      Ss    0:00 /usr/lib/systemd/systemd --switched-root --system --deserialize 21
    user_u:user_r:user_t:s0  2780 ?      S     0:00 sshd: user42@pts/1
    user_u:user_r:user_t:s0  2781 pts/1  Ss    0:00 -bash
    user_u:user_r:user_t:s0  2808 pts/1  R+    0:00 ps axZ
    
    # exit
  2. Determine whether the user can become root.

  3. Add this line to the /etc/sudoers.d/administrators file:

    user42  ALL=(ALL)       NOPASSWD: ALL
    [root@selinux5 ~]# visudo -f /etc/sudoers.d/administrators
  4. In the text editor, copy and past this line:

    user42  ALL=(ALL)       NOPASSWD: ALL
    Tip

    To insert the line, copy it and then press i to insert. To save and exit, press esc and then press :wq!.

  5. Confirm your changes:

    [root@selinux5 ~]# grep user42 /etc/sudoers.d/administrators
    user42  ALL=(ALL)       NOPASSWD: ALL
    [root@selinux5 ~]# ssh user42@localhost
    user42@localhost's password:
    [user42@selinux5 ~]$ sudo -i
    sudo: PERM_SUDOERS: setresuid(-1, 1, -1): Operation not permitted
    sudo: no valid sudoers sources found, quitting
    sudo: setresuid() [0, 0, 0] -> [1001, -1, -1]: Operation not permitted
    sudo: unable to initialize policy plugin
  6. Attempt the same in permissive mode:

    [user42@selinux5 ~]$ exit
    [root@selinux5 ~]# id -Z
    unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
    [root@selinux5 ~]# setenforce 0
    [root@selinux5 ~]# ssh user42@localhost
    user42@localhost's password:
    [user42@selinux5 ~]$ sudo -i
    [root@selinux5 ~]# id
    uid=0(root) gid=0(root) groups=0(root) context=user_u:user_r:user_t:s0
    [root@selinux5 ~]# id -Z
    User_u:user_r:user_t:s0
    [root@selinux5 ~]# exit
    [user42@selinux5 ~]$ exit
    [root@selinux5 ~]# setenforce 1

    Because SELinux denials are not enforced in permissive mode, user42 can become root. But you can see that the context stayed user_t and did not change to unconfined_t.

2.5.2: Confining the Administrator

There are two basic methods for confining the administrator user:

An administrator can be directly mapped to the sysadm_u SELinux user so that when that user logs in, the session is run with sysadm_t domain. Alternatively, you assign administrator users to staff_u and configure sudo so that specific users can gain the SELinux administrator role.

  1. In this case, enable the ssh_sysadm_login SELinux boolean option to allow users assigned sysadm_u to log in using SSH:

    [root@selinux5 ~]# semanage user -m -R "sysadm_r secadm_r" sysadm_u
    [root@selinux5 ~]# adduser -G wheel -Z sysadm_u admin1
    [root@selinux5 ~]# passwd admin1
    Tip

    You can select any password for admin1, but make sure you remember what it is.

    [root@selinux5 ~]# semanage login -l | grep admin
    admin1               sysadm_u             s0-s0:c0.c1023       *
    [root@selinux5 ~]# setsebool -P ssh_sysadm_login on
    [root@selinux5 ~]# ssh admin1@localhost
    [admin1@selinux5 ~]$ id -Z
    sysadm_u:sysadm_r:sysadm_t:s0-s0:c0.c1023
    [admin1@selinux5 ~]$ sudo -i
    [sudo] password for admin1:
    [root@selinux5 ~]# id -Z
    sysadm_u:sysadm_r:sysadm_t:s0-s0:c0.c1023
  2. Perform the administrator’s operation, which can be executed only by admin SELinux users:

    [root@selinux5 ~]# systemctl restart sshd
    [root@selinux5 ~]# exit
    [admin1@selinux5 ~]# exit
  3. Using the second approach, assign administrator users to staff_u and configure sudo so that specific users can gain the SELinux administrator role:

    [root@selinux5 ~]# adduser -G wheel -Z staff_u admin2
    [root@selinux5 ~]# passwd admin2
    Tip

    You can select any password for admin2, but make sure you remember what it is.

    [root@selinux5 ~]# semanage login -l | grep admin
    admin1               sysadm_u             s0-s0:c0.c1023       *
    admin2               staff_u              s0-s0:c0.c1023       *
    [root@selinux5 ~]# ssh admin2@localhost
    [admin2@selinux5 ~]$ id -Z
    staff_u:staff_r:staff_t:s0-s0:c0.c1023
    [admin2@selinux5 ~]$ sudo -i
    [sudo] password for admin2:
    -bash: /root/.bash_profile: Permission denied
    -bash-4.2# id -Z
    staff_u:staff_r:staff_t:s0-s0:c0.c1023
  4. Perform the administrator’s operation, which can be executed only by admin SELinux users:

    -bash-4.2# systemctl restart sshd
    Failed to restart sshd.service: Access denied
    See system logs and 'systemctl status sshd.service' for details.
    -bash-4.2# exit
    [admin2@selinux5 ~]$ exit
  5. Add the following rule to sudoers to allow the admin2 user to gain the SELinux administrator role:

    [root@selinux5 ~]# visudo -f /etc/sudoers.d/administrators
  6. Append the following line to the end of the file:

    admin2  ALL=(ALL)  TYPE=sysadm_t ROLE=sysadm_r    ALL
    admin2  ALL=(ALL)  TYPE=secadm_t ROLE=secadm_r /usr/sbin/semanage,/usr/sbin/semodule
    Tip

    In the vi text editor, press o, then copy and paste these lines into the buffer. Then press esc and then type :wq! to save and exit.

  7. The admin2 user can gain the administrator role using sudo:

    [root@selinux5 ~]# ssh admin2@localhost
    [admin2@selinux5 ~]$ sudo -i
    [sudo] password for admin2:
    [root@selinux5 ~]# id -Z
    staff_u:sysadm_r:sysadm_t:s0-s0:c0.c1023
    [root@selinux5 ~]# systemctl restart sshd
    [root@selinux5 ~]#
    [root@selinux5 ~]# exit
    [admin2@selinux5 ~]# exit

2.5.3: Reverting Script

In this section, you use a revert script to restore the default SELinux user’s configuration.

  1. (Optional) Run this revert script on the selinux5 host:

    [root@selinux5 ~]# hostname
    selinux5.example.com
    [root@selinux5 ~]# cd /root
    [root@selinux5 ~]# sh confined_users_revert.sh