Skip to content
Snippets Groups Projects
Commit 0209f44c authored by Nick Thomas's avatar Nick Thomas Committed by GitLab Release Tools Bot
Browse files

Fix blind SSRF when looking up SSH host keys for mirroring

Merge branch 'security-354059-detect-host-keys-blind-ssrf-14-7' into '14-7-stable-ee'

See merge request gitlab-org/security/gitlab!2311

Changelog: security
parent 2de2e279
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -46,11 +46,11 @@ class SshHostKey
.select(&:valid?)
end
 
attr_reader :project, :url, :compare_host_keys
attr_reader :project, :url, :ip, :compare_host_keys
 
def initialize(project:, url:, compare_host_keys: nil)
@project = project
@url = normalize_url(url)
@url, @ip = normalize_url(url)
@compare_host_keys = compare_host_keys
end
 
Loading
Loading
@@ -90,9 +90,11 @@ class SshHostKey
end
 
def calculate_reactive_cache
input = [ip, url.hostname].compact.join(' ')
known_hosts, errors, status =
Open3.popen3({}, *%W[ssh-keyscan -T 5 -p #{url.port} -f-]) do |stdin, stdout, stderr, wait_thr|
stdin.puts(url.host)
stdin.puts(input)
stdin.close
 
[
Loading
Loading
@@ -127,11 +129,31 @@ class SshHostKey
end
 
def normalize_url(url)
full_url = ::Addressable::URI.parse(url)
raise ArgumentError, "Invalid URL" unless full_url&.scheme == 'ssh'
url, real_hostname = Gitlab::UrlBlocker.validate!(
url,
schemes: %w[ssh],
allow_localhost: allow_local_requests?,
allow_local_network: allow_local_requests?,
dns_rebind_protection: Gitlab::CurrentSettings.dns_rebinding_protection_enabled?
)
# When DNS rebinding protection is required, the hostname is replaced by the
# resolved IP. However, `url` is used in `id`, so we can't change it. Track
# the resolved IP separately instead.
if real_hostname
ip = url.hostname
url.hostname = real_hostname
end
# Ensure ssh://foo and ssh://foo:22 share the same cache
url.port = url.inferred_port
 
Addressable::URI.parse("ssh://#{full_url.host}:#{full_url.inferred_port}")
rescue Addressable::URI::InvalidURIError
[url, ip]
rescue Gitlab::UrlBlocker::BlockedUrlError
raise ArgumentError, "Invalid URL"
end
def allow_local_requests?
Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
end
end
Loading
Loading
@@ -177,6 +177,7 @@ RSpec.describe Projects::MirrorsController do
INVALID
git@example.com:foo/bar.git
ssh://git@example.com:foo/bar.git
ssh://127.0.0.1/foo/bar.git
].each do |url|
it "returns an error with a 400 response for URL #{url.inspect}" do
do_get(project, url)
Loading
Loading
Loading
Loading
@@ -4,7 +4,9 @@ require 'spec_helper'
 
RSpec.describe SshHostKey do
using RSpec::Parameterized::TableSyntax
include ReactiveCachingHelpers
include StubRequests
 
let(:key1) do
'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC3UpyF2iLqy1d63M6k3jH1vuEnq/NWtE+o' \
Loading
Loading
@@ -35,6 +37,7 @@ RSpec.describe SshHostKey do
let(:extra) { known_hosts + "foo\nbar\n" }
let(:reversed) { known_hosts.lines.reverse.join }
 
let(:url) { 'ssh://example.com:2222' }
let(:compare_host_keys) { nil }
 
def stub_ssh_keyscan(args, status: true, stdout: "", stderr: "")
Loading
Loading
@@ -50,7 +53,7 @@ RSpec.describe SshHostKey do
 
let(:project) { build(:project) }
 
subject(:ssh_host_key) { described_class.new(project: project, url: 'ssh://example.com:2222', compare_host_keys: compare_host_keys) }
subject(:ssh_host_key) { described_class.new(project: project, url: url, compare_host_keys: compare_host_keys) }
 
describe '.primary_key' do
it 'returns a symbol' do
Loading
Loading
@@ -191,5 +194,45 @@ RSpec.describe SshHostKey do
is_expected.to eq(error: 'Failed to detect SSH host keys')
end
end
context 'DNS rebinding protection enabled' do
before do
stub_application_setting(dns_rebinding_protection_enabled: true)
end
it 'sends an address as well as hostname to ssh-keyscan' do
stub_dns(url, ip_address: '1.2.3.4')
stdin = stub_ssh_keyscan(%w[-T 5 -p 2222 -f-])
cache
expect(stdin.string).to eq("1.2.3.4 example.com\n")
end
end
end
describe 'URL validation' do
let(:url) { 'ssh://127.0.0.1' }
context 'when local requests are not allowed' do
before do
stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
end
it 'forbids scanning localhost' do
expect { ssh_host_key }.to raise_error(/Invalid URL/)
end
end
context 'when local requests are allowed' do
before do
stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
end
it 'permits scanning localhost' do
expect(ssh_host_key.url.to_s).to eq('ssh://127.0.0.1:22')
end
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment