Check for backdoored version of xz (CVE-2024-3094) (Ansible/Bash)

Info on the xc backdoor

https://www.openwall.com/lists/oss-security/2024/03/29/4

https://tukaani.org/xz-backdoor/

https://www.tenable.com/blog/frequently-asked-questions-cve-2024-3094-supply-chain-backdoor-in-xz-utils

Kostas on Twitter posted a helpful one-liner to check the xz version without running the actual command.

https://twitter.com/kostastsale/status/1773890846250926445

Versions 5.6.0 and 5.6.1 are backdoored.

Bash one liner

The following Bash commands were taken and modified from the above Twitter link

Here is a one liner that will check the version of xz binaries and return if they are safe or vulnerable. You’ll need to run this in a Bash shell. May have issues in sh.

for xz_p in $(type -a xz | awk '{print $NF}' ); do  if ( strings "$xz_p" | grep "xz (XZ Utils)" | grep '5.6.0\|5.6.1' ); then echo $xz_p Vulnerable; else echo $xz_p Safe ; fi ; done 

Ansible Playbooks

Here are two different Ansible Playbooks to check if the xz package(s) are backdoored.

This one uses the above Bash commands to check the xz binaries.

Ansible Playbook to Check xz Backdoor
--- - name: Check if XZ tools are compromised # https://twitter.com/kostastsale/status/1773890846250926445 hosts: all tasks: - name: Run Bash command shell : for xz_p in $(type -a xz | awk '{print $NF}' ); do if ( strings "$xz_p" | grep "xz (XZ Utils)" | grep '5.6.0\|5.6.1' ); then echo $xz_p Vulnerable!; else echo $xz_p Safe ; fi ; done args: executable: /bin/bash register: result - name: Show output ansible.builtin.debug: msg: "{{ result.stdout_lines }}"

The following playbook uses the package manager to check the xz version. On RHEL/Fedora this is the xc package. On Debian/Ubuntu, it is part of the liblzma5 package.

Ansible Playbook to Check xz Backdoor using package manager
--- - name: Check if XZ tools are compromised hosts: all tasks: - name: Collect package info ansible.builtin.package_facts: manager: auto - name: Check if liblzma5 is vulnerable (Ubuntu/Debian) ansible.builtin.debug: msg: "Installed version of liblzma5/xz: {{ ansible_facts.packages['liblzma5'] | map(attribute='version') | join(', ') }} Vulnerable!" when: ('liblzma5' in ansible_facts.packages) and (ansible_facts.packages['liblzma5'][0].version.split('-')[0] is version('5.6.0', '==') or ansible_facts.packages['liblzma5'][0].version.split('-')[0] is version('5.6.1', '==')) - name: Check if xz is vulnerable (RHEL/Fedora/Rocky/Alma) ansible.builtin.debug: msg: "Installed version of xz: {{ ansible_facts.packages['xz'] | map(attribute='version') | join(', ') }} is vulnerable" when: ('xz' in ansible_facts.packages) and (ansible_facts.packages['xz'][0].version is version('5.6.0', '==') or ansible_facts.packages['xz'][0].version is version('5.6.1', '=='))

Configuring Firewalld with Ansible

We’ll be using Ansible to change and maintain our firewall settings on a server.

The playbook will do the following.

  1. Set the default zone to drop (Drops all external traffic to server)
  2. Set a zone for internal access
  3. Allow access from RFC1918 addresses to internal zone (Any local IP address will be able to access the server)
  4. Enable the services and ports specified in the vars section
  5. Disable the services listed in firewall_disable_services variable

Modify the variables as needed for your server(s). You can also add or move the variables to the inventory or host_vars files.

If you need to create an inventory file, refer to the first part of this post

BE CAREFUL CHANGING FIREWALL SETTINGS!!! IMPROPER SETTINGS COULD RENDER THE SERVER INACCESSIBLE!!!

Playbook for firewalld

Change the variables under the vars section

---
- name: Configure firewalld
  hosts: rhel
  gather_facts: yes
  become: yes

  vars: 
    firewall_allowed_ips:
      - 10.0.0.0/8
      - 172.16.0.0/12
      - 192.168.0.0/16
    firewall_allowed_services:
      - ssh
      - https
      - snmp
    firewall_allowed_ports:
      - "2222/tcp"
    firewall_disable_services:
      - cockpit
      - dhcpv6-client
      - mdns
      - samba-client

  tasks: 
  - name: Set default zone to drop
    ansible.builtin.command: firewall-cmd --set-default-zone=drop
    register: default_zone_set
    changed_when:
      - '"ZONE_ALREADY_SET" not in default_zone_set.stderr'

  - name: Enable and allow access to internal zone from RFC1918 addresses
    ansible.posix.firewalld:
      source: "{{ item }}"
      zone: internal
      permanent: true
      immediate: true
      state: enabled
    with_items: "{{ firewall_allowed_ips }}"

  - name: Disable unused services for internal zone
    ansible.posix.firewalld:
      service: "{{ item }}"
      zone: internal
      permanent: true
      immediate: true
      state: disabled
    with_items: "{{ firewall_disable_services }}"


  - name: Set services for internal zone
    ansible.posix.firewalld:
      service: "{{ item }}"
      zone: internal
      permanent: true
      immediate: true
      state: enabled
    with_items: "{{ firewall_allowed_services }}"

  - name: Set custom ports for internal zone
    ansible.posix.firewalld:
      port: "{{ item }}"
      zone: internal
      permanent: true
      immediate: true
      state: enabled
    with_items: "{{ firewall_allowed_ports }}"

Helpful links

https://docs.ansible.com/ansible/latest/collections/ansible/posix/firewalld_module.html#parameter-source

https://stackoverflow.com/questions/51563643/how-to-change-firewalld-zone-using-ansible

https://www.middlewareinventory.com/blog/ansible-firewalld/

Ansible Playbook to Detect OS version

This playbook can be used to report the Linux Distribution, OS Family, Distribution Version, and Distribution Major Version. This can be helpful for verifying all operating systems are up to date, or for working out what to use in other playbooks.

You will need to already have an inventory file.

Playbook yaml file

The playbook is very simple. Copy and paste the following contents into a file named “os_info.yaml”

---
- hosts: all
  gather_facts: yes
  become: false
  tasks:
  - name: Distribution
    debug: msg=" distribution {{ ansible_distribution }} - os_family {{ ansible_os_family}} - distribution_version {{ansible_distribution_version}} - distribution_major_version {{ ansible_distribution_major_version }}"

If we wanted to, we could break out each Ansible variable in its own debug line. I prefer having them all on a single line.

Running the Playbook

Run the playbook like any other playbook. Change inventory.ini to your inventory file. If your inventory file is encrypted, use the –ask-vault-pass option.

ansible-playbook -i inventory.ini os_info.yaml 

Results

Here are some example results.

 ---------------------
< TASK [Distribution] >
 ---------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||


ok: [almalinux_server01] => {
    "msg": " distribution AlmaLinux - os_family RedHat - distribution_version 9.3 - distribution_major_version 9"
}
ok: [fedora_server01] => {
    "msg": " distribution Fedora - os_family RedHat - distribution_version 39 - distribution_major_version 39"
}
ok: [centos_server] => {
    "msg": " distribution CentOS - os_family RedHat - distribution_version 7.9 - distribution_major_version 7"
}
ok: [ubuntu_serevr01] => {
    "msg": " distribution Ubuntu - os_family Debian - distribution_version 20.04 - distribution_major_version 20"
}

Ansible Playbook to upgrade Linux Servers (Debian, Ubuntu, RedHat, Fedora, CentOS)

This is an Ansible playbook that can upgrade all your Linux machines! Or at least most of them. No openSUSE support yet.

Copy the playbook below, and put all your servers into an inventory file and run with

ansible-playbook -i hosts.ini master_update.yaml --ask-vault-pass

Couple of notes.

  1. This will do a full update automatically reboot your servers if needed.
  2. There is a special section for RHEL, CentOS 7 servers. If a server is running say CentOS 7, it will default to using YUM instead of DNF.
  3. You need sudo or become: yes to reboot and install upgrades.

Linux OS Upgrade Playbook

Linux Upgrade Playbook
--- - name: Linux OS Upgrade hosts: all gather_facts: yes become: yes tasks: - name: Upgrade Debian and Ubuntu systems with apt block: - name: dist-upgrade ansible.builtin.apt: upgrade: dist update_cache: yes register: upgrade_result - name: Debain check if reboot is required shell: "[ -f /var/run/reboot-required ]" failed_when: False register: debian_reboot_required changed_when: debian_reboot_required.rc == 0 notify: - Reboot server - name: Debian remove unneeded dependencies ansible.builtin.apt: autoremove: yes register: autoremove_result - name: Debian print errors if upgrade failed ansible.builtin.debug: msg: | Upgrade Result: {{ upgrade_result }} Autoremove Result: {{ autoremove_result }} when: ansible_os_family == "Debian" - name: Upgrade RHEL systems with DNF block: - name: Get packages that can be upgraded with DNF ansible.builtin.dnf: list: upgrades state: latest update_cache: yes register: reg_dnf_output_all - name: List packages that can be upgraded with DNF ansible.builtin.debug: msg: "{{ reg_dnf_output_all.results | map(attribute='name') | list }}" - name: Upgrade packages with DNF become: yes ansible.builtin.dnf: name: '*' state: latest update_cache: yes update_only: no register: reg_upgrade_ok - name: Print DNF errors if upgrade failed ansible.builtin.debug: msg: "Packages upgrade failed" when: reg_upgrade_ok is not defined - name: Install dnf-utils become: yes ansible.builtin.dnf: name: 'dnf-utils' state: latest update_cache: yes when: reg_dnf_output_all is defined when: ansible_os_family == "RedHat" and not (ansible_distribution_major_version == "7") - name: Upgrade legacy RHEL systems with YUM block: - name: Get packages that can be upgraded with YUM ansible.builtin.yum: list: upgrades state: latest update_cache: yes register: reg_yum_output_all - name: List packages that can be upgraded with YUM ansible.builtin.debug: msg: "{{ reg_yum_output_all.results | map(attribute='name') | list }}" - name: Upgrade packages with YUM become: yes ansible.builtin.yum: name: '*' state: latest update_cache: yes update_only: no register: reg_yum_upgrade_ok - name: Print YUM errors if upgrade failed ansible.builtin.debug: msg: "Packages upgrade failed" when: reg_yum_upgrade_ok is not defined - name: Check legacy RHEL system if a reboot is required become: yes command: needs-restarting -r register: reg_reboot_required ignore_errors: yes failed_when: false changed_when: reg_reboot_required.rc != 0 notify: - Reboot server when: ansible_os_family == "RedHat" and ansible_distribution_major_version == "7" handlers: - name : Reboot server ansible.builtin.reboot: msg: "Reboot initiated by Ansible after OS update" reboot_timeout: 3600 test_command: uptime

Helpful links

https://github.com/simeononsecurity/ansible_linux_update/tree/main
https://simeononsecurity.com/guides/automate-linux-patching-and-updates-with-ansible/
https://thenathan.net/2020/07/16/yum-and-dnf-update-and-reboot-with-ansible/