Add support for PreAuthEncTimeStamp decoding/decrypting
parent
785ff60d8e
commit
5d2ff5982e
|
@ -49,6 +49,9 @@ module Rex
|
||||||
PA_ENC_TIMESTAMP = 2
|
PA_ENC_TIMESTAMP = 2
|
||||||
PA_PW_SALT = 3
|
PA_PW_SALT = 3
|
||||||
PA_PAC_REQUEST = 128
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,8 +6,97 @@ module Rex
|
||||||
module Model
|
module Model
|
||||||
module Field
|
module Field
|
||||||
# This class is a representation of a PA-ENC-TIMESTAMP, an encrypted timestamp
|
# 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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -40,8 +40,61 @@ module Rex
|
||||||
raise ::RuntimeError, 'EncryptedData encoding is not supported'
|
raise ::RuntimeError, 'EncryptedData encoding is not supported'
|
||||||
end
|
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
|
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
|
# Decodes a Rex::Proto::Kerberos::Model::Type::EncryptedData from an String
|
||||||
#
|
#
|
||||||
# @param input [String] the input to decode from
|
# @param input [String] the input to decode from
|
||||||
|
|
|
@ -10,37 +10,78 @@ describe Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp do
|
||||||
end
|
end
|
||||||
|
|
||||||
=begin
|
=begin
|
||||||
#<OpenSSL::ASN1::Sequence:0x007ff9c1892de0
|
#<OpenSSL::ASN1::Sequence:0x007ff9c3830210
|
||||||
@infinite_length=false,
|
@infinite_length=false,
|
||||||
@tag=16,
|
@tag=16,
|
||||||
@tag_class=:UNIVERSAL,
|
@tag_class=:UNIVERSAL,
|
||||||
@tagging=nil,
|
@tagging=nil,
|
||||||
@value=
|
@value=
|
||||||
[#<OpenSSL::ASN1::ASN1Data:0x007ff9c1893538
|
[#<OpenSSL::ASN1::ASN1Data:0x007ff9c38302d8
|
||||||
@infinite_length=false,
|
@infinite_length=false,
|
||||||
@tag=0,
|
@tag=0,
|
||||||
@tag_class=:CONTEXT_SPECIFIC,
|
@tag_class=:CONTEXT_SPECIFIC,
|
||||||
@value=
|
@value=
|
||||||
[#<OpenSSL::ASN1::Integer:0x007ff9c18936a0
|
[#<OpenSSL::ASN1::GeneralizedTime:0x007ff9c3830300
|
||||||
@infinite_length=false,
|
@infinite_length=false,
|
||||||
@tag=2,
|
@tag=24,
|
||||||
@tag_class=:UNIVERSAL,
|
@tag_class=:UNIVERSAL,
|
||||||
@tagging=nil,
|
@tagging=nil,
|
||||||
@value=#<OpenSSL::BN:0x007ff9c1893a10>>]>,
|
@value=2014-12-09 01:09:09 UTC>]>,
|
||||||
#<OpenSSL::ASN1::ASN1Data:0x007ff9c1892e58
|
#<OpenSSL::ASN1::ASN1Data:0x007ff9c3830238
|
||||||
@infinite_length=false,
|
@infinite_length=false,
|
||||||
@tag=2,
|
@tag=1,
|
||||||
@tag_class=:CONTEXT_SPECIFIC,
|
@tag_class=:CONTEXT_SPECIFIC,
|
||||||
@value=
|
@value=
|
||||||
[#<OpenSSL::ASN1::OctetString:0x007ff9c1893150
|
[#<OpenSSL::ASN1::Integer:0x007ff9c3830260
|
||||||
@infinite_length=false,
|
@infinite_length=false,
|
||||||
@tag=4,
|
@tag=2,
|
||||||
@tag_class=:UNIVERSAL,
|
@tag_class=:UNIVERSAL,
|
||||||
@tagging=nil,
|
@tagging=nil,
|
||||||
@value=
|
@value=#<OpenSSL::BN:0x007ff9c3830288>>]>]>
|
||||||
"`\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
|
=end
|
||||||
let(:sample) do
|
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:0x007ff70196b2c0
|
||||||
|
@infinite_length=false,
|
||||||
|
@tag=0,
|
||||||
|
@tag_class=:CONTEXT_SPECIFIC,
|
||||||
|
@value=
|
||||||
|
[#<OpenSSL::ASN1::Integer:0x007ff70196b2e8
|
||||||
|
@infinite_length=false,
|
||||||
|
@tag=2,
|
||||||
|
@tag_class=:UNIVERSAL,
|
||||||
|
@tagging=nil,
|
||||||
|
@value=#<OpenSSL::BN:0x007ff70196b338>>
|
||||||
|
]>,
|
||||||
|
#<OpenSSL::ASN1::ASN1Data:0x007ff70196b1a8
|
||||||
|
@infinite_length=false,
|
||||||
|
@tag=2,
|
||||||
|
@tag_class=:CONTEXT_SPECIFIC,
|
||||||
|
@value=
|
||||||
|
[#<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">
|
||||||
|
]>
|
||||||
|
]>
|
||||||
|
=end
|
||||||
|
let(:sample_encrypted_data) do
|
||||||
"\x30\x3d\xa0\x03\x02\x01\x17\xa2\x36\x04\x34\x60\xae\x53\xa5\x0b" +
|
"\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" +
|
"\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" +
|
"\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
|
describe "#decode" do
|
||||||
it "returns the decoded Rex::Proto::Kerberos::Model::Field::PreAuthEncTimeStamp" 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
|
end
|
||||||
|
|
||||||
it "decodes etype" do
|
it "decodes pa_time_stamp correctly" do
|
||||||
pre_auth_enc_time_stamp.decode(sample)
|
pre_auth_enc_time_stamp.decode(time_stamp_raw)
|
||||||
expect(pre_auth_enc_time_stamp.etype).to eq(23)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue