VYPR
Critical severityNVD Advisory· Published Apr 19, 2022· Updated Sep 16, 2024

Command Injection

CVE-2022-25648

Description

The package git before 1.11.0 are vulnerable to Command Injection via git argument injection. When calling the fetch(remote = 'origin', opts = {}) function, the remote parameter is passed to the git fetch subcommand in a way that additional flags can be set. The additional flags can be used to perform a command injection.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

The ruby-git gem before 1.11.0 allows command injection via the remote parameter in the fetch function, enabling arbitrary command execution.

Vulnerability

The ruby-git gem (Git::Lib#fetch) before version 1.11.0 is vulnerable to command injection through the remote parameter. When calling fetch(remote = 'origin', opts = {}), the remote string is passed unsanitized to the git fetch subcommand, allowing an attacker to inject additional flags such as --upload-pack that can lead to arbitrary command execution [1][2][3].

Exploitation

An attacker can exploit this by providing a crafted remote argument containing injected flags. No authentication is required if the attacker controls the remote parameter (e.g., via user input or external data). The injection occurs during the git fetch system call, and the attacker can execute arbitrary commands on the host system [2][3][4].

Impact

Successful exploitation results in arbitrary command execution with the privileges of the Ruby process running the git gem. This can lead to full system compromise, data exfiltration, or further lateral movement [2][3].

Mitigation

The vulnerability is fixed in ruby-git version 1.11.0, released on 2022-04-19. Users should upgrade to version 1.11.0 or later. No workarounds are available for earlier versions [2][3][4].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
gitRubyGems
< 1.11.01.11.0

Affected products

3

Patches

1
291ca0946bec

Address command line injection in Git::Lib#fetch

https://github.com/ruby-git/ruby-gitJames CouballApr 13, 2022via ghsa
2 files changed · +54 34
  • lib/git/lib.rb+4 3 modified
    @@ -875,14 +875,15 @@ def tag(name, *opts)
           command('tag', arr_opts)
         end
     
    -
         def fetch(remote, opts)
    -      arr_opts = [remote]
    -      arr_opts << opts[:ref] if opts[:ref]
    +      arr_opts = []
           arr_opts << '--tags' if opts[:t] || opts[:tags]
           arr_opts << '--prune' if opts[:p] || opts[:prune]
           arr_opts << '--unshallow' if opts[:unshallow]
           arr_opts << '--depth' << opts[:depth] if opts[:depth]
    +      arr_opts << '--'
    +      arr_opts << remote
    +      arr_opts << opts[:ref] if opts[:ref]
     
           command('fetch', arr_opts)
         end
    
  • tests/units/test_remotes.rb+50 31 modified
    @@ -23,29 +23,29 @@ def test_add_remote
           assert(local.remotes.map{|b| b.name}.include?('testremote2'))
     
           local.add_remote('testremote3', remote, :track => 'master')
    -      
    -      assert(local.branches.map{|b| b.full}.include?('master')) #We actually a new branch ('test_track') on the remote and track that one intead. 
    +
    +      assert(local.branches.map{|b| b.full}.include?('master')) #We actually a new branch ('test_track') on the remote and track that one intead.
           assert(local.remotes.map{|b| b.name}.include?('testremote3'))
    -    end 
    +    end
       end
     
       def test_remove_remote_remove
         in_temp_dir do |path|
           local = Git.clone(@wbare, 'local')
           remote = Git.clone(@wbare, 'remote')
    -      
    +
           local.add_remote('testremote', remote)
           local.remove_remote('testremote')
    -      
    +
           assert(!local.remotes.map{|b| b.name}.include?('testremote'))
     
           local.add_remote('testremote', remote)
           local.remote('testremote').remove
    -      
    +
           assert(!local.remotes.map{|b| b.name}.include?('testremote'))
         end
       end
    -  
    +
       def test_set_remote_url
         in_temp_dir do |path|
           local = Git.clone(@wbare, 'local')
    @@ -65,33 +65,33 @@ def test_remote_fun
         in_temp_dir do |path|
           loc = Git.clone(@wbare, 'local')
           rem = Git.clone(@wbare, 'remote')
    -        
    +
           r = loc.add_remote('testrem', rem)
     
           Dir.chdir('remote') do
             new_file('test-file1', 'blahblahblah1')
             rem.add
             rem.commit('master commit')
    -        
    +
             rem.branch('testbranch').in_branch('tb commit') do
               new_file('test-file3', 'blahblahblah3')
               rem.add
    -          true          
    +          true
             end
           end
           assert(!loc.status['test-file1'])
           assert(!loc.status['test-file3'])
    -    
    +
           r.fetch
    -      r.merge   
    +      r.merge
           assert(loc.status['test-file1'])
    -      
    +
           loc.merge(loc.remote('testrem').branch('testbranch'))
    -      assert(loc.status['test-file3'])    
    -      
    +      assert(loc.status['test-file3'])
    +
           #puts loc.remotes.map { |r| r.to_s }.inspect
    -      
    -      #r.remove  
    +
    +      #r.remove
           #puts loc.remotes.inspect
         end
       end
    @@ -123,18 +123,37 @@ def test_fetch
         end
       end
     
    +  def test_fetch_command_injection
    +    test_file = 'VULNERABILITY_EXISTS'
    +    vulnerability_exists = false
    +    in_temp_dir do |_path|
    +      git = Git.init('test_project')
    +      origin = "--upload-pack=touch #{test_file};"
    +      begin
    +        git.fetch(origin, { ref: 'some/ref/head' })
    +      rescue Git::GitExecuteError
    +        # This is expected
    +      else
    +        raise 'Expected Git::GitExecuteError to be raised'
    +      end
    +
    +      vulnerability_exists = File.exist?(test_file)
    +    end
    +    assert(!vulnerability_exists)
    +  end
    +
       def test_fetch_ref_adds_ref_option
         in_temp_dir do |path|
           loc = Git.clone(@wbare, 'local')
           rem = Git.clone(@wbare, 'remote', :config => 'receive.denyCurrentBranch=ignore')
           loc.add_remote('testrem', rem)
    -      
    +
           loc.chdir do
             new_file('test-file1', 'gonnaCommitYou')
             loc.add
             loc.commit('master commit 1')
             first_commit_sha = loc.log.first.sha
    -        
    +
             new_file('test-file2', 'gonnaCommitYouToo')
             loc.add
             loc.commit('master commit 2')
    @@ -146,46 +165,46 @@ def test_fetch_ref_adds_ref_option
     
             # Make sure fetch message only has the second commit when we fetch the second commit
             assert(loc.fetch('origin', {:ref => second_commit_sha}).include?(second_commit_sha))
    -        assert(!loc.fetch('origin', {:ref => second_commit_sha}).include?(first_commit_sha))        
    -      end      
    +        assert(!loc.fetch('origin', {:ref => second_commit_sha}).include?(first_commit_sha))
    +      end
         end
       end
    -  
    +
       def test_push
         in_temp_dir do |path|
           loc = Git.clone(@wbare, 'local')
           rem = Git.clone(@wbare, 'remote', :config => 'receive.denyCurrentBranch=ignore')
    -        
    +
           loc.add_remote('testrem', rem)
     
           loc.chdir do
             new_file('test-file1', 'blahblahblah1')
             loc.add
             loc.commit('master commit')
             loc.add_tag('test-tag')
    -        
    +
             loc.branch('testbranch').in_branch('tb commit') do
               new_file('test-file3', 'blahblahblah3')
               loc.add
    -          true          
    +          true
             end
           end
           assert(!rem.status['test-file1'])
           assert(!rem.status['test-file3'])
    -      
    +
           loc.push('testrem')
     
    -      assert(rem.status['test-file1'])    
    -      assert(!rem.status['test-file3'])    
    +      assert(rem.status['test-file1'])
    +      assert(!rem.status['test-file3'])
           assert_raise Git::GitTagNameDoesNotExist do
             rem.tag('test-tag')
           end
    -      
    +
           loc.push('testrem', 'testbranch', true)
     
           rem.checkout('testbranch')
    -      assert(rem.status['test-file1'])    
    -      assert(rem.status['test-file3'])    
    +      assert(rem.status['test-file1'])
    +      assert(rem.status['test-file3'])
           assert(rem.tag('test-tag'))
         end
       end
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

14

News mentions

0

No linked articles in our index yet.