FIX: When following redirects before cloning, use the first git request (#19269)

This is closer to git's redirect following behaviour. We prevented git
following redirects when we clone in order to prevent SSRF attacks.

Follow-up-to: 291bbc4fb9
This commit is contained in:
Daniel Waterworth 2022-11-30 14:21:09 -06:00 committed by GitHub
parent aea492df5e
commit d9364a272e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 48 additions and 35 deletions

View File

@ -67,7 +67,7 @@ class FinalDestination
@status = :ready @status = :ready
@follow_canonical = @opts[:follow_canonical] @follow_canonical = @opts[:follow_canonical]
@http_verb = http_verb(@force_get_hosts, @follow_canonical) @http_verb = @opts[:http_verb] || http_verb(@force_get_hosts, @follow_canonical)
@cookie = nil @cookie = nil
@limited_ips = [] @limited_ips = []
@verbose = @opts[:verbose] || false @verbose = @opts[:verbose] || false
@ -82,8 +82,8 @@ class FinalDestination
20 20
end end
def self.resolve(url) def self.resolve(url, opts = nil)
new(url).resolve new(url, opts).resolve
end end
def http_verb(force_get_hosts, follow_canonical) def http_verb(force_get_hosts, follow_canonical)

View File

@ -77,6 +77,25 @@ class ThemeStore::GitImporter
protected protected
def redirected_uri
first_clone_uri = @uri.dup
first_clone_uri.path.gsub!(/\/\z/, "")
first_clone_uri.path += "/info/refs"
first_clone_uri.query = "service=git-upload-pack"
redirected_uri = FinalDestination.resolve(first_clone_uri.to_s, http_verb: :get)
if redirected_uri&.path.ends_with?("/info/refs")
redirected_uri.path.gsub!(/\/info\/refs\z/, "")
redirected_uri.query = nil
redirected_uri
else
@uri
end
rescue
@uri
end
def raise_import_error! def raise_import_error!
raise RemoteTheme::ImportError.new(I18n.t("themes.import_error.git")) raise RemoteTheme::ImportError.new(I18n.t("themes.import_error.git"))
end end
@ -117,19 +136,7 @@ class ThemeStore::GitImporter
end end
def clone_http! def clone_http!
uris = [@uri] @uri = redirected_uri
begin
resolved_uri = FinalDestination.resolve(@uri.to_s)
if resolved_uri && resolved_uri != @uri
uris.unshift(resolved_uri)
end
rescue
# If this fails, we can stil attempt to clone using the original URI
end
uris.each do |uri|
@uri = uri
@url = @uri.to_s @url = @uri.to_s
unless ["http", "https"].include?(@uri.scheme) unless ["http", "https"].include?(@uri.scheme)
@ -148,15 +155,11 @@ class ThemeStore::GitImporter
begin begin
Discourse::Utils.execute_command(env, *args, timeout: COMMAND_TIMEOUT_SECONDS) Discourse::Utils.execute_command(env, *args, timeout: COMMAND_TIMEOUT_SECONDS)
return
rescue RuntimeError rescue RuntimeError
end end
end end
end end
raise_import_error!
end
def clone_ssh! def clone_ssh!
unless @private_key.present? unless @private_key.present?
raise_import_error! raise_import_error!

View File

@ -6,6 +6,7 @@ require 'theme_store/git_importer'
RSpec.describe ThemeStore::GitImporter do RSpec.describe ThemeStore::GitImporter do
describe "#import" do describe "#import" do
let(:url) { "https://github.com/example/example.git" } let(:url) { "https://github.com/example/example.git" }
let(:first_fetch_url) { "https://github.com/example/example.git/info/refs?service=git-upload-pack" }
let(:trailing_slash_url) { "https://github.com/example/example/" } let(:trailing_slash_url) { "https://github.com/example/example/" }
let(:ssh_url) { "git@github.com:example/example.git" } let(:ssh_url) { "git@github.com:example/example.git" }
let(:branch) { "dev" } let(:branch) { "dev" }
@ -13,8 +14,17 @@ RSpec.describe ThemeStore::GitImporter do
before do before do
hex = "xxx" hex = "xxx"
SecureRandom.stubs(:hex).returns(hex) SecureRandom.stubs(:hex).returns(hex)
FinalDestination.stubs(:resolve).with(url).returns(URI.parse(url))
FinalDestination::SSRFDetector.stubs(:lookup_and_filter_ips).with("github.com").returns(["192.0.2.100"]) FinalDestination::SSRFDetector
.stubs(:lookup_and_filter_ips)
.with("github.com")
.returns(["192.0.2.100"])
FinalDestination
.stubs(:resolve)
.with(first_fetch_url, http_verb: :get)
.returns(URI.parse(first_fetch_url))
@temp_folder = "#{Pathname.new(Dir.tmpdir).realpath}/discourse_theme_#{hex}" @temp_folder = "#{Pathname.new(Dir.tmpdir).realpath}/discourse_theme_#{hex}"
@ssh_folder = "#{Pathname.new(Dir.tmpdir).realpath}/discourse_theme_ssh_#{hex}" @ssh_folder = "#{Pathname.new(Dir.tmpdir).realpath}/discourse_theme_ssh_#{hex}"
end end