Synook

Authenticating NTLM with MS Exchange

A little while ago I discovered a script that emulated the NTLM challenge-resonse sequence in PHP – perfect for the intranet-based messenging system I was developing. Unfortunately, it didn’t actually authenticate users with the AD.

Now, it is rather hard to get a Apache server to connect directly to the domain controller, however @prajalpa on twitter came up with an excellent easily-accessable alternative – using the domain’s Exchange email server to authenticate instead. The challenge-response method is the same in this case, but only a simple socket has to be opened to connect, and the challenges can be sent in plain-text. A great article on NTLM over Exchange SMTP can be found at http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication.

@prajalpa’s script can be found here: http://pastebin.com/f5a2ba991, modified from the original article I found at http://www.iau5.com/ntlm.php.txt. I finally got it to work in the version below.

$headers = apache_request_headers();
$ntlmserver = "EXCHANGESERVER.DOMAIN";
function left($string, $count) { 
  return substr($string, 0, $count);
} 
if (!isset($headers['Authorization'])) { 
  header('HTTP/1.1 401 Unauthorized');
  header('WWW-Authenticate: NTLM');
  exit;
} 
$auth = $headers['Authorization'];
if (substr($auth,0,5) == 'NTLM ') { 
  $msg = base64_decode(substr($auth, 5));
  if (substr($msg, 0, 8) != "NTLMSSP\x00") die('error header not recognised');
  if ($msg[8] == "\x01") { 
    $holder = apache_request_headers();
    $_SESSION['TYPE1MSG'] = $holder['Authorization'];
    if (empty($_SESSION['AUTH']) || !$_SESSION['AUTH']) { 
      $smtp_server = pfsockopen($ntlmserver, 25, $errno, $errstr, 30);
      if(!$smtp_server) exit;
      fwrite($smtp_server, "EHLO\r\n");
      $stop = 0;
      while (!$stop) { 
        $data = fgets($smtp_server, 512);
        if (left($data,6)=="250 OK") fwrite($smtp_server, "AUTH {$_SESSION['TYPE1MSG']}\r\n");
        else if (left($data,3)=="334") { 
          $_SESSION['TYPE2MSG'] = substr($data,4);
          $stop = 1;
        } 
      } 
      $_SESSION['AUTH']=1;
    } 
    $msg2 = $_SESSION['TYPE2MSG'];
    header('HTTP/1.1 401 Unauthorized');
    header('WWW-Authenticate: NTLM '.trim($msg2));
    exit;
  } else if ($msg[8] == "\x03") { 
    $holder = apache_request_headers();
    $_SESSION['TYPE3MSG'] = substr($holder['Authorization'], 5);
    if(empty($_SESSION['AUTH2']) || !$_SESSION['AUTH2']) { 
      $smtp_server = pfsockopen($ntlmserver, 25, $errno, $errstr, 30);
      if(!$smtp_server) exit;
      $stop = 0;
      fwrite($smtp_server, "{$_SESSION['TYPE3MSG']}\r\n");
      $i = 0;
      while (!$stop) { 
        $data = fgets($smtp_server, 512);
        if(left($data,3)=="235") { 
          $_SESSION['AUTH']="235";
          $stop = 1;
        } else if(left($data,3)=="535") { 
          $_SESSION['AUTH']="535";
          $stop = 1;
        } 
      } 
      fwrite($smtp_server, "QUIT\r\n");
      fclose($smtp_server);
      $_SESSION['AUTH2']=1;
    } 
    if ($_SESSION['AUTH'] == 535) { 
      session_destroy();
      header("location:./");
    } 
    function get_msg_str($msg, $start, $unicode = true) { 
      $len = (ord($msg[$start+1]) * 256) + ord($msg[$start]);
      $off = (ord($msg[$start+5]) * 256) + ord($msg[$start+4]);
      if ($unicode) return str_replace("\0", '', substr($msg, $off, $len));
      else return substr($msg, $off, $len);
    } 
    $user = get_msg_str($msg, 36);
    $domain = get_msg_str($msg, 28);
    $workstation = get_msg_str($msg, 44);
    //Authenticated! 
  } 
}

While this script was in no way my own it is a great example of the way vaarious resources can be brought together and used to help develop solutions. So don’t be afraid to look around for and use little pieces of code that, while not quite the right solution, can help you on your way.