Skip to content
This repository has been archived by the owner on Nov 28, 2018. It is now read-only.

Commit

Permalink
(MCOP-600) Prevent public key overwriting attack via identity
Browse files Browse the repository at this point in the history
When using two-way automatic public key distribution, each end writes
the others `identity` as a public key file locally. No validation was
done on the `identity`, so it could trigger directory traversal and
allow the attacker to overwrite an unexpected file (like a trusted
public key certificate). Prevent this by verifying identity does not
result in traversing outside the intended distribution directory.
  • Loading branch information
MikaelSmith committed Jun 29, 2017
1 parent ad56bb7 commit 3388a31
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 10 deletions.
13 changes: 10 additions & 3 deletions security/sshkey.rb
Expand Up @@ -142,20 +142,27 @@ def write_key_to_disk(key, identity)
end

if File.directory?(publickey_dir)
if File.exists?(old_keyfile = File.join(publickey_dir, "#{identity}_pub.pem"))
# Reject identity if it would result in directory traversal.
old_keyfile = File.join(File.expand_path(publickey_dir), "#{identity}_pub.pem")
unless File.expand_path(old_keyfile) == old_keyfile
Log.warn("Identity returned by server would result in directory traversal. Not writing key to disk.")
return
end

if File.exists?(old_keyfile)
old_key = File.read(old_keyfile).chomp

unless old_key == key
unless lookup_config_option('overwrite_stored_keys', 'n') =~ /^1|y/
Log.warn("Public key sent from '%s' does not match the stored key. Not overwriting." % identity)
else
Log.warn("Public key sent from '%s' does not match the stored key. Overwriting." % identity)
File.open(File.join(publickey_dir, "#{identity}_pub.pem"), 'w') { |f| f.puts key }
File.open(old_keyfile, 'w') { |f| f.puts key }
end
end
else
Log.debug("Discovered a new public key for '%s'. Writing to '%s'" % [identity, publickey_dir])
File.open(File.join(publickey_dir, "#{identity}_pub.pem"), 'w') { |f| f.puts key }
File.open(old_keyfile, 'w') { |f| f.puts key }
end
else
raise("Cannot write public key to '%s'. Directory does not exist." % publickey_dir)
Expand Down
26 changes: 19 additions & 7 deletions spec/security/sshkey_spec.rb
Expand Up @@ -280,13 +280,23 @@ class Verifier; end
}.to raise_error
end

it 'should fail if identity would result in directory traversal' do
@plugin.stubs(:lookup_config_option).with('learn_public_keys').returns('1')
@plugin.stubs(:lookup_config_option).with('publickey_dir').returns('ssh/pkd')
File.stubs(:directory?).with('ssh/pkd').returns(true)
Log.expects(:warn)
File.expects(:open).never
@plugin.send(:write_key_to_disk, 'ssh-rsa abcd', '../test')
end

it 'should write the public key to disk if its the first time its been seen' do
@plugin.stubs(:lookup_config_option).with('learn_public_keys').returns('1')
@plugin.stubs(:lookup_config_option).with('publickey_dir').returns('ssh/pkd')
File.stubs(:directory?).with('ssh/pkd').returns(true)
File.stubs(:exists?).with('ssh/pkd/rspec_pub.pem').returns(false)
full_path = File.join(File.expand_path('ssh/pkd'), 'rspec_pub.pem')
File.stubs(:exists?).with(full_path).returns(false)
file = mock
File.expects(:open).with('ssh/pkd/rspec_pub.pem', 'w').yields(file)
File.expects(:open).with(full_path, 'w').yields(file)
file.expects(:puts).with('ssh-rsa abcd')
@plugin.send(:write_key_to_disk, 'ssh-rsa abcd', 'rspec')
end
Expand All @@ -296,8 +306,9 @@ class Verifier; end
@plugin.stubs(:lookup_config_option).with('publickey_dir').returns('ssh/pkd')
@plugin.stubs(:lookup_config_option).with('overwrite_stored_keys', 'n').returns('n')
File.stubs(:directory?).with('ssh/pkd').returns(true)
File.stubs(:exists?).with('ssh/pkd/rspec_pub.pem').returns(true)
File.stubs(:read).with('ssh/pkd/rspec_pub.pem').returns('ssh-rsa dcba')
full_path = File.join(File.expand_path('ssh/pkd'), 'rspec_pub.pem')
File.stubs(:exists?).with(full_path).returns(true)
File.stubs(:read).with(full_path).returns('ssh-rsa dcba')
Log.expects(:warn)
File.expects(:open).never
@plugin.send(:write_key_to_disk, 'ssh-rsa abcd', 'rspec')
Expand All @@ -308,10 +319,11 @@ class Verifier; end
@plugin.stubs(:lookup_config_option).with('publickey_dir').returns('ssh/pkd')
@plugin.stubs(:lookup_config_option).with('overwrite_stored_keys', 'n').returns('1')
File.stubs(:directory?).with('ssh/pkd').returns(true)
File.stubs(:exists?).with('ssh/pkd/rspec_pub.pem').returns(true)
File.stubs(:read).with('ssh/pkd/rspec_pub.pem').returns('ssh-rsa dcba')
full_path = File.join(File.expand_path('ssh/pkd'), 'rspec_pub.pem')
File.stubs(:exists?).with(full_path).returns(true)
File.stubs(:read).with(full_path).returns('ssh-rsa dcba')
file = mock
File.expects(:open).with('ssh/pkd/rspec_pub.pem', 'w').yields(file)
File.expects(:open).with(full_path, 'w').yields(file)
file.expects(:puts).with('ssh-rsa abcd')
Log.expects(:warn)
@plugin.send(:write_key_to_disk, 'ssh-rsa abcd', 'rspec')
Expand Down

0 comments on commit 3388a31

Please sign in to comment.