Add support for PreAuthEncTimeStamp decoding/decrypting
parent
785ff60d8e
commit
5d2ff5982e
|
@ -49,6 +49,9 @@ module Rex
|
|||
PA_ENC_TIMESTAMP = 2
|
||||
PA_PW_SALT = 3
|
||||
PA_PAC_REQUEST = 128
|
||||
|
||||
# From RFC-4757: The RC4-HMAC Kerberos Encryption Types Used by Microsoft Windows
|
||||
KERB_ETYPE_RC4_HMAC = 23
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,8 +6,97 @@ module Rex
|
|||
module Model
|
||||
module Field
|
||||
# This class is a representation of a PA-ENC-TIMESTAMP, an encrypted timestamp
|
||||
class PreAuthEncTimeStamp < Rex::Proto::Kerberos::Model::Type::EncryptedData
|
||||
class PreAuthEncTimeStamp < Element
|
||||
|
||||
# @!attribute pa_time_stamp
|
||||
# @return [Time] client's time
|
||||
attr_accessor :pa_time_stamp
|
||||
# @!attribute pausec
|
||||
# @return [Fixnum] optional microseconds client's time
|
||||
attr_accessor :pausec
|
||||
|
||||
# Decodes a Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp
|
||||
#
|
||||
# @param input [String, OpenSSL::ASN1::Sequence] the input to decode from
|
||||
# @return [self] if decoding succeeds
|
||||
# @raise [RuntimeError] if decoding doesn't succeed
|
||||
def decode(input)
|
||||
case input
|
||||
when String
|
||||
decode_string(input)
|
||||
when OpenSSL::ASN1::Sequence
|
||||
decode_asn1(input)
|
||||
else
|
||||
raise ::RuntimeError, 'Failed to decode EncryptedData Name, invalid input'
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
def encode
|
||||
raise ::RuntimeError, 'EncryptedData encoding is not supported'
|
||||
end
|
||||
|
||||
# Decrypts a Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp
|
||||
#
|
||||
# @param input [String, OpenSSL::ASN1::Sequence] the input to decrypt from
|
||||
# @return [Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp] if decryption succeeds
|
||||
# @raise [RuntimeError] if decryption doesn't succeed
|
||||
def self.decrypt(input, key)
|
||||
elem = PreAuthEncTimeStamp.new
|
||||
elem.decrypt(input, key)
|
||||
|
||||
elem
|
||||
end
|
||||
|
||||
# Decrypts a Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp
|
||||
#
|
||||
# @param input [String, OpenSSL::ASN1::Sequence] the input to decrypt from
|
||||
# @return [self] if decryption succeeds
|
||||
# @raise [RuntimeError] if decryption doesn't succeed
|
||||
def decrypt(input, key)
|
||||
ed = Rex::Proto::Kerberos::Model::Type::EncryptedData.decode(input)
|
||||
decrypted = ed.decrypt(key, 1)
|
||||
decode(decrypted[8, decrypted.length - 1])
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Decodes a Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp
|
||||
#
|
||||
# @param input [String] the input to decode from
|
||||
def decode_string(input)
|
||||
asn1 = OpenSSL::ASN1.decode(input)
|
||||
|
||||
decode_asn1(asn1)
|
||||
end
|
||||
|
||||
# Decodes a Rex::Proto::Kerberos::Model::Type::PreAuthEncTimeStamp from an
|
||||
# OpenSSL::ASN1::Sequence
|
||||
#
|
||||
# @param input [OpenSSL::ASN1::Sequence] the input to decode from
|
||||
def decode_asn1(input)
|
||||
self.pa_time_stamp = decode_pa_time_stamp(input.value[0])
|
||||
self.pausec = decode_pausec(input.value[1])
|
||||
end
|
||||
|
||||
# Decodes the decode_pa_time_stamp from an OpenSSL::ASN1::ASN1Data
|
||||
#
|
||||
# @param input [OpenSSL::ASN1::ASN1Data] the input to decode from
|
||||
# @return [Boolean]
|
||||
def decode_pa_time_stamp(input)
|
||||
input.value[0].value
|
||||
end
|
||||
|
||||
# Decodes the pausec from an OpenSSL::ASN1::ASN1Data
|
||||
#
|
||||
# @param input [OpenSSL::ASN1::ASN1Data] the input to decode from
|
||||
# @return [Fixnum]
|
||||
def decode_pausec(input)
|
||||
input.value[0].value.to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -40,8 +40,61 @@ module Rex
|
|||
raise ::RuntimeError, 'EncryptedData encoding is not supported'
|
||||
end
|
||||
|
||||
# Decrypts the cipher with etype encryption schema
|
||||
#
|
||||
# @param key [String] the key to decrypt
|
||||
# @param key [Fixnum] the message type
|
||||
# @return [String] if decryption succeeds
|
||||
# @raise [RuntimeError] if decryption doesn't succeed
|
||||
def decrypt(key, msg_type)
|
||||
if cipher.nil? or cipher.empty?
|
||||
return ''
|
||||
end
|
||||
|
||||
res = ''
|
||||
case etype
|
||||
when KERB_ETYPE_RC4_HMAC
|
||||
res = decrypt_rc4_hmac(key, msg_type)
|
||||
else
|
||||
raise ::RuntimeError, 'EncryptedData encoding is not supported'
|
||||
end
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Decrypts the cipher using RC4-HMAC schema
|
||||
#
|
||||
# @param key [String] the key to decrypt
|
||||
# @param key [Fixnum] the message type
|
||||
# @return [String] if decryption succeeds
|
||||
# @raise [RuntimeError] if decryption doesn't succeed
|
||||
def decrypt_rc4_hmac(key, msg_type)
|
||||
unless cipher && cipher.length > 16
|
||||
raise ::RuntimeError, 'RC4-HMAC decryption failed'
|
||||
end
|
||||
|
||||
my_key = OpenSSL::Digest.digest('MD4', Rex::Text.to_unicode(key))
|
||||
|
||||
checksum = cipher[0, 16]
|
||||
data = cipher[16, cipher.length - 1]
|
||||
|
||||
k1 = OpenSSL::HMAC.digest('MD5', my_key, [msg_type].pack('V'))
|
||||
k3 = OpenSSL::HMAC.digest('MD5', k1, checksum)
|
||||
|
||||
cipher = OpenSSL::Cipher::Cipher.new("rc4")
|
||||
cipher.decrypt
|
||||
cipher.key = k3
|
||||
decrypted = cipher.update(data) + cipher.final
|
||||
|
||||
if OpenSSL::HMAC.digest('MD5', k1, decrypted) != checksum
|
||||
raise ::RuntimeError, 'RC4-HMAC decryption failed, incorrect checksum verification'
|
||||
end
|
||||
|
||||
decrypted
|
||||
end
|
||||
|
||||
# Decodes a Rex::Proto::Kerberos::Model::Type::EncryptedData from an String
|
||||
#
|
||||
# @param input [String] the input to decode from
|
||||
|
|
|
@ -10,37 +10,78 @@ describe Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp do
|
|||
end
|
||||
|
||||
=begin
|
||||
#<OpenSSL::ASN1::Sequence:0x007ff9c1892de0
|
||||
#<OpenSSL::ASN1::Sequence:0x007ff9c3830210
|
||||
@infinite_length=false,
|
||||
@tag=16,
|
||||
@tag_class=:UNIVERSAL,
|
||||
@tagging=nil,
|
||||
@value=
|
||||
[#<OpenSSL::ASN1::ASN1Data:0x007ff9c38302d8
|
||||
@infinite_length=false,
|
||||
@tag=0,
|
||||
@tag_class=:CONTEXT_SPECIFIC,
|
||||
@value=
|
||||
[#<OpenSSL::ASN1::GeneralizedTime:0x007ff9c3830300
|
||||
@infinite_length=false,
|
||||
@tag=24,
|
||||
@tag_class=:UNIVERSAL,
|
||||
@tagging=nil,
|
||||
@value=2014-12-09 01:09:09 UTC>]>,
|
||||
#<OpenSSL::ASN1::ASN1Data:0x007ff9c3830238
|
||||
@infinite_length=false,
|
||||
@tag=1,
|
||||
@tag_class=:CONTEXT_SPECIFIC,
|
||||
@value=
|
||||
[#<OpenSSL::ASN1::Integer:0x007ff9c3830260
|
||||
@infinite_length=false,
|
||||
@tag=2,
|
||||
@tag_class=:UNIVERSAL,
|
||||
@tagging=nil,
|
||||
@value=#<OpenSSL::BN:0x007ff9c3830288>>]>]>
|
||||
=end
|
||||
let(:time_stamp_raw) do
|
||||
"\x30\x1a\xa0\x11\x18\x0f\x32\x30" +
|
||||
"\x31\x34\x31\x32\x30\x39\x30\x31" +
|
||||
"\x30\x39\x30\x39\x5a\xa1\x05\x02" +
|
||||
"\x03\x08\xfc\xc8"
|
||||
end
|
||||
|
||||
=begin
|
||||
#<OpenSSL::ASN1::Sequence:0x007ff70196b158
|
||||
@infinite_length=false,
|
||||
@tag=16,
|
||||
@tag_class=:UNIVERSAL,
|
||||
@tagging=nil,
|
||||
@value=
|
||||
[#<OpenSSL::ASN1::ASN1Data:0x007ff9c1893538
|
||||
[
|
||||
#<OpenSSL::ASN1::ASN1Data:0x007ff70196b2c0
|
||||
@infinite_length=false,
|
||||
@tag=0,
|
||||
@tag_class=:CONTEXT_SPECIFIC,
|
||||
@value=
|
||||
[#<OpenSSL::ASN1::Integer:0x007ff9c18936a0
|
||||
[#<OpenSSL::ASN1::Integer:0x007ff70196b2e8
|
||||
@infinite_length=false,
|
||||
@tag=2,
|
||||
@tag_class=:UNIVERSAL,
|
||||
@tagging=nil,
|
||||
@value=#<OpenSSL::BN:0x007ff9c1893a10>>]>,
|
||||
#<OpenSSL::ASN1::ASN1Data:0x007ff9c1892e58
|
||||
@value=#<OpenSSL::BN:0x007ff70196b338>>
|
||||
]>,
|
||||
#<OpenSSL::ASN1::ASN1Data:0x007ff70196b1a8
|
||||
@infinite_length=false,
|
||||
@tag=2,
|
||||
@tag_class=:CONTEXT_SPECIFIC,
|
||||
@value=
|
||||
[#<OpenSSL::ASN1::OctetString:0x007ff9c1893150
|
||||
[#<OpenSSL::ASN1::OctetString:0x007ff70196b1f8
|
||||
@infinite_length=false,
|
||||
@tag=4,
|
||||
@tag_class=:UNIVERSAL,
|
||||
@tagging=nil,
|
||||
@value=
|
||||
"`\xAES\xA5\vV.Fa\xD9\xD6\x89\x98\xFCy\x9DEs}\r\x8Ax\x84M\xD7|\xC6P\b\x8D\xAB\"y\xC3\x8D\xD3\xAF\x9F^\xB7\xB8\x9BW\xC5\xC9\xC5\xEA\x90\x89\xC3cX">]>]>
|
||||
"`\xAES\xA5\vV.Fa\xD9\xD6\x89\x98\xFCy\x9DEs}\r\x8Ax\x84M\xD7|\xC6P\b\x8D\xAB\"y\xC3\x8D\xD3\xAF\x9F^\xB7\xB8\x9BW\xC5\xC9\xC5\xEA\x90\x89\xC3cX">
|
||||
]>
|
||||
]>
|
||||
=end
|
||||
let(:sample) do
|
||||
let(:sample_encrypted_data) do
|
||||
"\x30\x3d\xa0\x03\x02\x01\x17\xa2\x36\x04\x34\x60\xae\x53\xa5\x0b" +
|
||||
"\x56\x2e\x46\x61\xd9\xd6\x89\x98\xfc\x79\x9d\x45\x73\x7d\x0d\x8a" +
|
||||
"\x78\x84\x4d\xd7\x7c\xc6\x50\x08\x8d\xab\x22\x79\xc3\x8d\xd3\xaf" +
|
||||
|
@ -55,12 +96,55 @@ describe Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp do
|
|||
|
||||
describe "#decode" do
|
||||
it "returns the decoded Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp" do
|
||||
expect(pre_auth_enc_time_stamp.decode(sample)).to eq(pre_auth_enc_time_stamp)
|
||||
expect(pre_auth_enc_time_stamp.decode(time_stamp_raw)).to eq(pre_auth_enc_time_stamp)
|
||||
end
|
||||
|
||||
it "decodes etype" do
|
||||
pre_auth_enc_time_stamp.decode(sample)
|
||||
expect(pre_auth_enc_time_stamp.etype).to eq(23)
|
||||
it "decodes pa_time_stamp correctly" do
|
||||
pre_auth_enc_time_stamp.decode(time_stamp_raw)
|
||||
expect(pre_auth_enc_time_stamp.pa_time_stamp.to_s).to eq('2014-12-09 01:09:09 UTC')
|
||||
end
|
||||
|
||||
it "decodes pausec correctly" do
|
||||
pre_auth_enc_time_stamp.decode(time_stamp_raw)
|
||||
expect(pre_auth_enc_time_stamp.pausec).to eq(589000)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".decrypt" do
|
||||
context "correct key" do
|
||||
it "returns the decoded Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp" do
|
||||
expect(described_class.decrypt(sample_encrypted_data, 'juan')).to be_a(Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp)
|
||||
end
|
||||
end
|
||||
|
||||
context "incorrect key" do
|
||||
it "raises RuntimeError when decrypting with the incorrect key" do
|
||||
expect { described_class.decrypt(sample_encrypted_data, 'error') }.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#decrypt" do
|
||||
context "correct key" do
|
||||
it "returns the decoded Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp" do
|
||||
expect(pre_auth_enc_time_stamp.decrypt(sample_encrypted_data, 'juan')).to eq(pre_auth_enc_time_stamp)
|
||||
end
|
||||
|
||||
it "decodes pa_time_stamp correctly" do
|
||||
pre_auth_enc_time_stamp.decrypt(sample_encrypted_data, 'juan')
|
||||
expect(pre_auth_enc_time_stamp.pa_time_stamp.to_s).to eq('2014-12-09 01:09:09 UTC')
|
||||
end
|
||||
|
||||
it "decodes pausec correctly" do
|
||||
pre_auth_enc_time_stamp.decrypt(sample_encrypted_data, 'juan')
|
||||
expect(pre_auth_enc_time_stamp.pausec).to eq(589000)
|
||||
end
|
||||
end
|
||||
|
||||
context "when incorrect key" do
|
||||
it "raises RuntimeError when decrypting with the incorrect key" do
|
||||
expect { pre_auth_enc_time_stamp.decrypt(sample_encrypted_data, 'error') }.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue