CVE-2017-18026: Insufficient Information (NVD-CWE-noinfo)
value: Redmine before 3.2.9, 3.3.x before 3.3.6, and 3.4.x before 3.4.4 does not block the --config and --debugger flags to the Mercurial hg program, which allows remote attackers to execute arbitrary commands (through the Mercurial adapter) via vectors involving a branch whose name begins with a --config= or --debugger= substring, a related issue to CVE-2017-17536.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 8.8
Published: 2018-01-10T09:29Z
Repository: https://github.com/redmine/redmine
Language: Ruby
Files changed: lib/redmine/scm/adapters/mercurial_adapter.rb
Commit date: 2017-12-07 11:38:35 +0000
Commit message: mercurial: separate command options and positional arguments with "--" (#27516)

We don't have much problems here thanks to hgtarget(path) and CGI.escape(),
which prepends a repository path and encodes "=" character respectively, but
it's better to not rely on the side effect of these functions.

Contributed by Yuya Nishihara.

git-svn-id: http://svn.redmine.org/redmine/trunk@17061 e93f8b46-1217-0410-a6f0-8f06a7374b81

Diff: diff --git a/lib/redmine/scm/adapters/mercurial_adapter.rb b/lib/redmine/scm/adapters/mercurial_adapter.rb
index 2c71213566..0e7f766d47 100644
--- a/lib/redmine/scm/adapters/mercurial_adapter.rb
+++ b/lib/redmine/scm/adapters/mercurial_adapter.rb
@@ -139,11 +139,11 @@ module Redmine
         private :summary
 
         def entries(path=nil, identifier=nil, options={})
           p1 = scm_iconv(@path_encoding, 'UTF-8', path)
           manifest = hg('rhmanifest', '-r', CGI.escape(hgrev(identifier)),
-                        CGI.escape(without_leading_slash(p1.to_s))) do |io|
+                        '--', CGI.escape(without_leading_slash(p1.to_s))) do |io|
             output = io.read.force_encoding('UTF-8')
             begin
               parse_xml(output)['rhmanifest']['repository']['manifest']
             rescue
             end
@@ -182,11 +182,11 @@ module Redmine
         # makes Mercurial produce a xml output.
         def each_revision(path=nil, identifier_from=nil, identifier_to=nil, options={})
           hg_args = ['log', '--debug', '-C', '--style', self.class.template_path]
           hg_args << '-r' << "#{hgrev(identifier_from)}:#{hgrev(identifier_to)}"
           hg_args << '--limit' << options[:limit] if options[:limit]
-          hg_args << hgtarget(path) unless path.blank?
+          hg_args << '--' << hgtarget(path) unless path.blank?
           log = hg(*hg_args) do |io|
             output = io.read.force_encoding('UTF-8')
             begin
               # Mercurial < 1.5 does not support footer template for '</log>'
               parse_xml("#{output}</log>")['log']
@@ -238,11 +238,11 @@ module Redmine
           else
             hg_args << '-c' << hgrev(identifier_from)
           end
           unless path.blank?
             p = scm_iconv(@path_encoding, 'UTF-8', path)
-            hg_args << CGI.escape(hgtarget(p))
+            hg_args << '--' << CGI.escape(hgtarget(p))
           end
           diff = []
           hg *hg_args do |io|
             io.each_line do |line|
               diff << line
@@ -253,22 +253,22 @@ module Redmine
           nil  # means not found
         end
 
         def cat(path, identifier=nil)
           p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path))
-          hg 'rhcat', '-r', CGI.escape(hgrev(identifier)), hgtarget(p) do |io|
+          hg 'rhcat', '-r', CGI.escape(hgrev(identifier)), '--', hgtarget(p) do |io|
             io.binmode
             io.read
           end
         rescue HgCommandAborted
           nil  # means not found
         end
 
         def annotate(path, identifier=nil)
           p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path))
           blame = Annotate.new
-          hg 'rhannotate', '-ncu', '-r', CGI.escape(hgrev(identifier)), hgtarget(p) do |io|
+          hg 'rhannotate', '-ncu', '-r', CGI.escape(hgrev(identifier)), '--', hgtarget(p) do |io|
             io.each_line do |line|
               line.force_encoding('ASCII-8BIT')
               next unless line =~ %r{^([^:]+)\s(\d+)\s([0-9a-f]+):\s(.*)$}
               r = Revision.new(:author => $1.strip, :revision => $2, :scmid => $3,
                                :identifier => $3)

Vulnerable code:                         CGI.escape(without_leading_slash(p1.to_s))) do |io|
          hg_args << hgtarget(path) unless path.blank?
            hg_args << CGI.escape(hgtarget(p))
          hg 'rhcat', '-r', CGI.escape(hgrev(identifier)), hgtarget(p) do |io|
          hg 'rhannotate', '-ncu', '-r', CGI.escape(hgrev(identifier)), hgtarget(p) do |io|
Fixed code:                         '--', CGI.escape(without_leading_slash(p1.to_s))) do |io|
          hg_args << '--' << hgtarget(path) unless path.blank?
            hg_args << '--' << CGI.escape(hgtarget(p))
          hg 'rhcat', '-r', CGI.escape(hgrev(identifier)), '--', hgtarget(p) do |io|
          hg 'rhannotate', '-ncu', '-r', CGI.escape(hgrev(identifier)), '--', hgtarget(p) do |io|