Chef, Inspec, and Dirty COW
Using Compliance to remediate CVE-2016-5195
Many of you know about or will hear about CVE-2016-5195 aka Dirty COW.
This particularly nasty kernel vulnerability has been around for years and likely affects a majority of the Linux nodes are currently running. The short form is that it allows an unprivileged user to gain root access to a system. I'm not going to go into actually exploiting this vulnerability but I am going to show you how to detect and remediate using Inspec and Chef.
Using Chef Compliance to Detect CVE-2016-5195
If you don't already have a master CVE profile go ahead and create one.
inspec init profile CVE-2016-5195
Go ahead and edit the inspec.yml file so that it accurately reflects what we are doing.
inspec.yml
name: CVE-2016-5195
title: CVE-2016-5195 aka Dirty COW
maintainer: John R. Ray
copyright: John R. Ray
copyright_email: ia@johnray.io
license: All Rights Reserved
summary: Red Hat Product Security has been made aware of a vulnerability in the Linux kernel that has been assigned CVE-2016-5195. This issue was publicly disclosed on October 19, 2016 and has been rated as Important.
version: 0.1.0
Add something to the README.md while you are at it.
# Inspec profile for detecting CVE-2016-5195 aka Dirty COW.
This profile contains one control which executes the provided Red Hat detection script to assess vulnerability.
The script can be found at https://access.redhat.com/sites/default/files/rh-cve-2016-5195_2.sh
Red Hat has conveniently provided a script that can be used to determine if your system has an affected kernel so there is no point in rewriting that particular piece of code. The resulting inspec control looks like this:
title 'CVE-2016-5195'
# Run the Red Hat check script to see if we are vulnerable
control 'CVE-2016-5195' do
impact 1.0
title 'CVE-2016-5195 aka Dirty COW'
desc 'A race condition was found in the way the Linux kernel\'s memory subsystem handled the copy-on-write (COW) breakage of private read-only memory mappings. An unprivileged local user could use this flaw to gain write access to otherwise read-only memory mappings and thus increase their privileges on the system.
This could be abused by an attacker to modify existing setuid files with instructions to elevate privileges. An exploit using this technique has been found in the wild. This flaw affects most modern Linux distributions.
if os[:family] == 'redhat'
script = File.join(File.dirname(__FILE__), 'rh-cve-2016-5195_2.sh')
describe bash(script) do
its('stdout') { should match /NOT vulnerable|normally vulnerable|You have a partial mitigation applied/ }
end
end
end
I've added an OS family check into the control just in case I want to go back and expand this control to other operating systems as the method of checking and mitigation is slightly different.
At this point we can either upload the profile to the Compliance server, where it can be applied to any number of registered systems, or we can run it one off like so:
inspec exec CVE-2016-5195
F
Failures:
1) Bash command CVE-2016-5195/controls/rh-cve-2016-5195_2.sh stdout should match /NOT vulnerable|normally vulnerable|You have a partial mitigation applied/
Failure/Error: DEFAULT_FAILURE_NOTIFIER = lambda { |failure, _opts| raise failure }
expected "\e[1;31mYour kernel is \e[0m3.10.0-327.el7.x86_64\e[1;31m which IS vulnerable.\e[0m\nRed Hat recommends that you update your kernel. Alternatively, you can apply partial\nmitigation described at https://access.redhat.com/security/vulnerabilities/2706661 .\n" to match /NOT vulnerable|normally vulnerable|You have a partial mitigation applied/
Diff:
@@ -1,2 +1,4 @@
-/NOT vulnerable|normally vulnerable|You have a partial mitigation applied/
+Your kernel is 3.10.0-327.el7.x86_64 which IS vulnerable.
+Red Hat recommends that you update your kernel. Alternatively, you can apply partial
+mitigation described at https://access.redhat.com/security/vulnerabilities/2706661 .
# CVE-2016-5195/controls/cve-2016-5195.rb:17:in `block (3 levels) in load'
Finished in 0.76524 seconds (files took 3.04 seconds to load)
1 example, 1 failure
So bummer my node is vulnerable. We can do something similar by uploading to the Chef Compliance Server and running the profile against a node or group of nodes. Log in to your compliance server.
[jray@c0wp1n ~]$ inspec compliance login -k https://c0wp1n.eastus.cloudapp.azure.com --user=jray --refresh-token='<refresh-token>'
API access token verified and stored
Now upload your profile.
[jray@c0wp1n ~]$ inspec compliance upload CVE-2016-5195/
I, [2016-10-26T13:42:48.067101 #38431] INFO -- : Checking profile in CVE-2016-5195/
I, [2016-10-26T13:42:48.067197 #38431] INFO -- : Metadata OK.
I, [2016-10-26T13:42:48.070005 #38431] INFO -- : Found 1 controls.
I, [2016-10-26T13:42:48.070072 #38431] INFO -- : Control definitions OK.
Profile is valid
Generate temporary profile archive at /tmp/CVE-2016-519520161026-38431-1frxmq0.tar.gz
I, [2016-10-26T13:42:48.100275 #38431] INFO -- : Generate archive /tmp/CVE-2016-519520161026-38431-1frxmq0.tar.gz.
I, [2016-10-26T13:42:48.102666 #38431] INFO -- : Finished archive generation.
Start upload to jray/CVE-2016-5195
Uploading to Chef Compliance
Successfully uploaded profile
On the Compliance server under the compliance section you will now see your newly uploaded profile.
Go ahead and scan a node to see the report.
Remediation
Traditional remediation means going out to every system and manually updating. The remediation for this particular vulnerability means updating the kernel. While we can use chef to do this requiring a reboot is needed and let’s just say we aren't able to take down production right now. A short term remediation described in the bug report has you build a system tap module and run it as root. The chef code to put the short term fix in place looks like this.
Warning! I've tested this code but YMMV. The main issue is correctly resolving kernel-devel and kernel-debuginfo to the correct kernel version.
# vim: ft=chef.ruby:
#
# Author:: John R. Ray <my.email@johnray.io>
# Cookbook Name:: cve-2016-5195
# Recipe:: default
#
# Place scripts on the machine
scripts = %w(rh-cve-2016-5195_2.sh cve-2016-5195.stp)
scripts.each do |s|
cookbook_file "/tmp/#{s}" do
owner 'root'
group 'root'
mode '0700'
end.run_action(:create)
end
# Check Kernel Status
check = Mixlib::ShellOut.new('/tmp/rh-cve-2016-5195_2.sh').run_command
vuln = true if check.error?
# Temporary mitigation if needed.
# Install system packages needed for stap
packages = %W(systemtap kernel-devel kernel-debuginfo)
packages.each do |p|
package p do
action :install
only_if { vuln == true }
end
end
# Run temporary script so prevent Dirty COW
# Note: This has the side effect of making virus scanners useless until the kernel is updated.
execute 'Run Temporary CVE-2016-5195 Mitigation' do
command 'stap -g /tmp/cve-2016-5195.stp'
only_if { vuln == true }
end
# Clean up scripts if we aren't vulerable anymore
execute 'Clean up files' do
command 'rm -rf /tmp/{cve-2016-5195.stp,rh-cve-2016-5195_2.sh}'
not_if { vuln == true }
end
The systemtap module looks like this.
probe kernel.function("mem_write").call ? {
$count = 0
}
probe syscall.ptrace { // includes compat ptrace as well
$request = 0xfff
}
probe begin {
printk(0, "CVE-2016-5195 mitigation loaded")
}
probe end {
printk(0, "CVE-2016-5195 mitigation unloaded")
}
We can now mitigate by applying the resulting recipe to a node.
Putting it all together
Using compliance we can run the vulnerability scan against many nodes and see a report of the number of nodes affected. Using Chef we can the remediate the issue. What we gain here is a common platform, Chef, and language, Ruby/Inspec/DSL, to not only describe an audit but also remediate it if needed. In an environment with a running Compliance server and a running chef server we would not be able to not only detect, but also remediate and then prove that we had remediated, by providing the end client with exactly what commands we used to validate our results.
Happy Hacking!