From 0410a354590fb244569b60e309ec75e5952a6fb5 Mon Sep 17 00:00:00 2001 From: Tomas Jelinek Date: Sep 04 2015 07:28:26 +0000 Subject: Fix for CVE-2015-5189, CVE-2015-5190 --- diff --git a/fixed-command-injection-vulnerability.patch b/fixed-command-injection-vulnerability.patch new file mode 100644 index 0000000..4b03665 --- /dev/null +++ b/fixed-command-injection-vulnerability.patch @@ -0,0 +1,93 @@ +From 22c173192bb0dc189d8db84bbe8f0555b071f731 Mon Sep 17 00:00:00 2001 +From: Tomas Jelinek +Date: Thu, 3 Sep 2015 17:14:05 +0200 +Subject: [PATCH] fixed command injection vulnerability + +--- + pcsd/fenceagent.rb | 18 ++++++++++++------ + pcsd/remote.rb | 1 + + pcsd/resource.rb | 11 ++++++----- + 3 files changed, 19 insertions(+), 11 deletions(-) + +diff --git a/pcsd/fenceagent.rb b/pcsd/fenceagent.rb +index 8b37147..c348597 100644 +--- a/pcsd/fenceagent.rb ++++ b/pcsd/fenceagent.rb +@@ -18,12 +18,6 @@ def getFenceAgents(fence_agent = nil) + end + + def getFenceAgentMetadata(fenceagentname) +- # There are bugs in stonith_admin & the new fence_agents interaction +- # eventually we'll want to switch back to this, but for now we directly +- # call the agent to get metadata +- #metadata = `stonith_admin --metadata -a #{fenceagentname}` +- metadata = `/usr/sbin/#{fenceagentname} -o metadata` +- doc = REXML::Document.new(metadata) + options_required = {} + options_optional = {} + options_advanced = { +@@ -39,6 +33,18 @@ def getFenceAgentMetadata(fenceagentname) + options_advanced["pcmk_" + a + "_timeout"] = "" + options_advanced["pcmk_" + a + "_retries"] = "" + end ++ # There are bugs in stonith_admin & the new fence_agents interaction ++ # eventually we'll want to switch back to this, but for now we directly ++ # call the agent to get metadata ++ #metadata = `stonith_admin --metadata -a #{fenceagentname}` ++ if not fenceagentname.start_with?('fence_') or fenceagentname.include?('/') ++ return [options_required, options_optional, options_advanced] ++ end ++ stdout, stderr, retval = run_cmd( ++ "/usr/sbin/#{fenceagentname}", '-o', 'metadata' ++ ) ++ doc = REXML::Document.new(stdout.join) ++ + doc.elements.each('resource-agent/parameters/parameter') { |param| + temp_array = [] + if param.elements["shortdesc"] +diff --git a/pcsd/remote.rb b/pcsd/remote.rb +index fe4a5d9..b3eca7e 100644 +--- a/pcsd/remote.rb ++++ b/pcsd/remote.rb +@@ -840,6 +840,7 @@ def resource_metadata (params) + return 200 if not params[:resourcename] or params[:resourcename] == "" + resource_name = params[:resourcename][params[:resourcename].rindex(':')+1..-1] + class_provider = params[:resourcename][0,params[:resourcename].rindex(':')] ++ return [400, 'Invalid resource agent name'] if resource_name.include?('/') + + @resource = ResourceAgent.new(params[:resourcename]) + if class_provider == "ocf:heartbeat" +diff --git a/pcsd/resource.rb b/pcsd/resource.rb +index 387e791..4e159f8 100644 +--- a/pcsd/resource.rb ++++ b/pcsd/resource.rb +@@ -103,11 +103,12 @@ def getResourceOptions(resource_id,stonith=false) + + ret = {} + if stonith +- resource_options = `#{PCS} stonith show #{resource_id}` ++ command = [PCS, 'stonith', 'show', resource_id] + else +- resource_options = `#{PCS} resource show #{resource_id}` ++ command = [PCS, 'resource', 'show', resource_id] + end +- resource_options.each_line { |line| ++ stdout, stderr, retval = run_cmd(*command) ++ stdout.each { |line| + keyval = line.strip.split(/: /,2) + if keyval[0] == "Attributes" then + options = keyval[1].split(/ /) +@@ -281,8 +282,8 @@ end + + def getResourceMetadata(resourcepath) + ENV['OCF_ROOT'] = OCF_ROOT +- metadata = `#{resourcepath} meta-data` +- doc = REXML::Document.new(metadata) ++ stdout, stderr, retval = run_cmd(resourcepath, 'meta-data') ++ doc = REXML::Document.new(stdout.join) + options_required = {} + options_optional = {} + long_desc = "" +-- +1.9.1 + diff --git a/fixed-session-and-cookies-processing.patch b/fixed-session-and-cookies-processing.patch new file mode 100644 index 0000000..1ff6b84 --- /dev/null +++ b/fixed-session-and-cookies-processing.patch @@ -0,0 +1,185 @@ +From 9238d7a28f029e53dcdd3a043d5778793502a5aa Mon Sep 17 00:00:00 2001 +From: Tomas Jelinek +Date: Thu, 3 Sep 2015 17:04:29 +0200 +Subject: [PATCH] fixed session and cookies processing + +--- + pcsd/auth.rb | 24 +++++++++++------------- + pcsd/pcs.rb | 8 ++++---- + pcsd/pcsd.rb | 14 ++++++++++---- + pcsd/remote.rb | 2 +- + 4 files changed, 26 insertions(+), 22 deletions(-) + +diff --git a/pcsd/auth.rb b/pcsd/auth.rb +index 8953d60..05bfadf 100644 +--- a/pcsd/auth.rb ++++ b/pcsd/auth.rb +@@ -3,9 +3,8 @@ require 'pp' + require 'securerandom' + require 'rpam' + +-class PCSAuth + # Ruby 1.8.7 doesn't implement SecureRandom.uuid +- def self.uuid ++ def pcsauth_uuid + if defined? SecureRandom.uuid + return SecureRandom.uuid + else +@@ -16,7 +15,7 @@ class PCSAuth + end + end + +- def self.validUser(username, password, generate_token = false, request = nil) ++ def pcsauth_validUser(username, password, generate_token = false, request = nil) + $logger.info("Attempting login by '#{username}'") + if not Rpam.auth(username,password, :service => "pcsd") + $logger.info("Failed login by '#{username}' (bad username or password)") +@@ -37,7 +36,7 @@ class PCSAuth + $logger.info("Successful login by '#{username}'") + + if generate_token +- token = PCSAuth.uuid ++ token = pcsauth_uuid + begin + password_file = File.open($user_pass_file, File::RDWR|File::CREAT) + password_file.flock(File::LOCK_EX) +@@ -57,7 +56,7 @@ class PCSAuth + return true + end + +- def self.validToken(token) ++ def pcsauth_validToken(token) + begin + json = File.read($user_pass_file) + users = JSON.parse(json) +@@ -73,10 +72,10 @@ class PCSAuth + return false + end + +- def self.isLoggedIn(session, cookies) +- if username = validToken(cookies["token"]) +- if username == "hacluster" and $cookies.key?(:CIB_user) and $cookies.key?(:CIB_user) != "" +- $session[:username] = $cookies[:CIB_user] ++ def pcsauth_isLoggedIn(session, cookies) ++ if username = pcsauth_validToken(cookies["token"]) ++ if username == "hacluster" and cookies.key?('CIB_user') and cookies['CIB_user'] != "" ++ session[:username] = cookies['CIB_user'] + end + return true + else +@@ -85,11 +84,11 @@ class PCSAuth + end + + # Always an admin until we implement groups +- def self.isAdmin(session) ++ def pcsauth_isAdmin(session) + true + end + +- def self.createUser(username, password) ++ def pcsauth_createUser(username, password) + begin + json = File.read($user_pass_file) + users = JSON.parse(json) +@@ -97,7 +96,7 @@ class PCSAuth + users = [] + end + +- token = PCSAuth.uuid ++ token = pcsauth_uuid + + users.delete_if{|u| u["username"] == username} + users << {"username" => username, "password" => password, "token" => token} +@@ -105,5 +104,4 @@ class PCSAuth + f.write(JSON.pretty_generate(users)) + end + end +-end + +diff --git a/pcsd/pcs.rb b/pcsd/pcs.rb +index 022d8fa..0a9ca2a 100644 +--- a/pcsd/pcs.rb ++++ b/pcsd/pcs.rb +@@ -303,7 +303,7 @@ def send_request_with_token(node,request, post=false, data={}, remote=true, raw_ + req.set_form_data(data) + end + cookies_to_send = [CGI::Cookie.new("name" => 'token', "value" => token).to_s] +- cookies_to_send << CGI::Cookie.new("name" => "CIB_user", "value" => $session[:username].to_s).to_s ++ cookies_to_send << CGI::Cookie.new("name" => "CIB_user", "value" => get_session()[:username].to_s).to_s + req.add_field("Cookie",cookies_to_send.join(";")) + myhttp = Net::HTTP.new(uri.host, uri.port) + myhttp.use_ssl = true +@@ -620,10 +620,10 @@ def run_cmd(*args) + start = Time.now + out = "" + errout = "" +- if $session[:username] == "hacluster" +- ENV['CIB_user'] = $cookies[:CIB_user] ++ if get_session()[:username] == "hacluster" ++ ENV['CIB_user'] = get_cookies()['CIB_user'] + else +- ENV['CIB_user'] = $session[:username] ++ ENV['CIB_user'] = get_session()[:username] + end + $logger.debug("CIB USER: #{ENV['CIB_user'].to_s}") + status = Open4::popen4(*args) do |pid, stdin, stdout, stderr| +diff --git a/pcsd/pcsd.rb b/pcsd/pcsd.rb +index 8dcba30..9ef1c87 100644 +--- a/pcsd/pcsd.rb ++++ b/pcsd/pcsd.rb +@@ -49,8 +49,6 @@ also_reload 'auth.rb' + also_reload 'wizard.rb' + + before do +- $session = session +- $cookies = cookies + if request.path != '/login' and not request.path == "/logout" and not request.path == '/remote/auth' + protected! + end +@@ -117,7 +115,7 @@ set :run, false + + helpers do + def protected! +- if not PCSAuth.isLoggedIn(session, request.cookies) ++ if not pcsauth_isLoggedIn(session, request.cookies) + # If we're on /managec//main we redirect + match_expr = "/managec/(.*)/(.*)" + mymatch = request.path.match(match_expr) +@@ -198,7 +196,7 @@ if not DISABLE_GUI + end + + post '/login' do +- if PCSAuth.validUser(params['username'],params['password']) ++ if pcsauth_validUser(params['username'],params['password']) + session["username"] = params['username'] + # Temporarily ignore pre_login_path until we come up with a list of valid + # paths to redirect to (to prevent status_all issues) +@@ -741,4 +739,12 @@ helpers do + def h(text) + Rack::Utils.escape_html(text) + end ++ ++ def get_session() ++ return session ++ end ++ ++ def get_cookies() ++ return cookies ++ end + end +diff --git a/pcsd/remote.rb b/pcsd/remote.rb +index 2e898ab..fe4a5d9 100644 +--- a/pcsd/remote.rb ++++ b/pcsd/remote.rb +@@ -594,7 +594,7 @@ def status_all(params, nodes = []) + end + + def auth(params,request) +- token = PCSAuth.validUser(params['username'],params['password'], true, request) ++ token = pcsauth_validUser(params['username'],params['password'], true, request) + # If we authorized to this machine, attempt to authorize everywhere + node_list = [] + if token and params["bidirectional"] +-- +1.9.1 + diff --git a/pcs.spec b/pcs.spec index 0704fc5..9bbee9b 100644 --- a/pcs.spec +++ b/pcs.spec @@ -1,6 +1,6 @@ Name: pcs Version: 0.9.137 -Release: 4%{?dist} +Release: 5%{?dist} License: GPLv2 URL: http://github.com/feist/pcs Group: System Environment/Base @@ -13,6 +13,8 @@ Summary: Pacemaker Configuration System Source0: http://people.redhat.com/cfeist/pcs/pcs-withgems-%{version}.tar.gz Patch0: bindfix.patch Patch1: secure-cookie.patch +Patch2: fixed-session-and-cookies-processing.patch +Patch3: fixed-command-injection-vulnerability.patch Requires: pacemaker ruby python Requires: rubygem-sinatra rubygem-highline rubygem-rack rubygem-rack-protection rubygem-tilt Requires: rubygem-eventmachine rubygem-rack-test rubygem-multi_json rubygem-json @@ -26,6 +28,8 @@ easily view, modify and created pacemaker based clusters. %patch0 -p1 -b .fedfix %patch1 -p1 -b .fedfix +%patch2 -p1 +%patch3 -p1 cd pcsd ; bundle install --local ; cd .. %build @@ -69,6 +73,10 @@ chmod 755 $RPM_BUILD_ROOT/%{python_sitelib}/pcs/pcs.py %doc COPYING README %changelog +* Fri Sep 04 2015 Tomas Jelinek - 0.9.137-5 +- Fix for CVE-2015-5189 incorrect authorization +- Fix for CVE-2015-5190 command injection + * Fri May 22 2015 Tomas Jelinek - 0.9.137-4 - Fix for CVE-2015-1848, CVE-2015-3983 (sessions not signed)