CVE-2016-4445: Improper Neutralization of Special Elements used in a Command ('Command Injection') (CWE-77)
value: The fix_lookup_id function in sealert in setroubleshoot before 3.2.23 allows local users to execute arbitrary commands as root by triggering an SELinux denial with a crafted file name, related to executing external commands with the commands.getstatusoutput function.
CWE description: The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.
CVSS v3 base score: 7
Published: 2017-04-11T18:59Z
Repository: https://github.com/fedora-selinux/setroubleshoot
Language: Other
Files changed: framework/src/sealert, framework/src/setroubleshoot/signature.py
Commit date: 2015-04-09 14:14:44 +0200
Commit message: framework: get rid of commands.getstatusoutput()

Diff: diff --git a/framework/src/sealert b/framework/src/sealert
index 9126012..d095a22 100755
--- a/framework/src/sealert
+++ b/framework/src/sealert
@@ -139,26 +139,26 @@ def fix_lookup_id(local_id, analysis_id):
         async_rpc = cl.alert_client.query_alerts(local_id)
         async_rpc.add_callback(query_alerts_callback)
         async_rpc.add_errback(query_alerts_error)
 
     def query_alerts_callback(sigs):
-        import commands
+        import subprocess
         for siginfo in sigs.signature_list:
             for plugin  in siginfo.plugin_list:
                 if analysis_id == plugin.analysis_id:
                     p = load_plugins(analysis_id)[0]
                     if p.fixable == False:
                         print _("Not fixable.")
                         cl.main_loop.quit()
                         return
                     siginfo.update_derived_template_substitutions()
-                    command = siginfo.substitute(p.get_fix_cmd(siginfo.audit_event, plugin.args))
-                    rc, output = commands.getstatusoutput(command)
-                    if rc == 0:
-                        print _("Successfully ran %s" % command)
-                    else:
-                        print output
+                    command = siginfo.substitute_array(p.get_fix_cmd(siginfo.audit_event, plugin.args).split())
+                    try:
+                        output = subprocess.check_output(command)
+                        print _("Successfully ran %s" % ' '.join(command))
+                    except subprocess.CalledProcessError as e:
+                        print(e.output)
                     cl.main_loop.quit()
                     return
         print _("Plugin %s not valid for %s id") % (analysis_id, local_id)
         cl.main_loop.quit()
 
diff --git a/framework/src/setroubleshoot/signature.py b/framework/src/setroubleshoot/signature.py
index 04c875c..7d19e82 100755
--- a/framework/src/setroubleshoot/signature.py
+++ b/framework/src/setroubleshoot/signature.py
@@ -458,10 +458,13 @@ class SEFaultSignatureInfo(XmlSerialize):
         return total_priority, plugins
 
     def substitute(self, txt):
         return Template(txt).safe_substitute(self.template_substitutions)
 
+    def substitute_array(self, args):
+        return [self.substitute(txt) for txt in args]
+
     def format_details(self, replace=False):
         env = self.environment
 
         text = _("Additional Information:\n")
         text += format_2_column_name_value(_("Source Context"),        self.scontext.format())

Vulnerable code:         import commands
                    command = siginfo.substitute(p.get_fix_cmd(siginfo.audit_event, plugin.args))
                    rc, output = commands.getstatusoutput(command)
                    if rc == 0:
                        print _("Successfully ran %s" % command)
                    else:
                        print output
Fixed code:         import subprocess
                    command = siginfo.substitute_array(p.get_fix_cmd(siginfo.audit_event, plugin.args).split())
                    try:
                        output = subprocess.check_output(command)
                        print _("Successfully ran %s" % ' '.join(command))
                    except subprocess.CalledProcessError as e:
                        print(e.output)
    def substitute_array(self, args):
        return [self.substitute(txt) for txt in args]