Introducing the RUHA Lib
By popular request—or at least by Harry’s request—the Really Ugly Hack Authentication Library.
First a little preface:
This library was designed a couple of years ago to make up for the lack of NTLM authentication for apache. We—my system administrator and myself—tried several flavors of “mod_ntlm” projects for apache. We were not able to successfully compile any of these against Linux (let alone the HP-UX 10.20 production environment ). We tried to contact several of the maintainers without success, and so gave up the quest. This solution probably does not have very wide applicability, but we already had both Unix/Apache and Windows/IIS servers up and running, plus our staging process required users to have IE which supports NTLM authentication out of the box (as does Mozilla and Firefox now) so it did suit my needs.
BTW, if anyone knows of a working mod_ntlm module for apache now, please comment :).
My early background in web programming (prior to PHP enlightenment) was in ASP on IIS. Under this scenario, turning on security on the directory allowed you to pull the NT user name from the server variable…very handy :). Knowing this was possible, I devised a scheme were a PHP script on Apache would redirect to an ASP script on another IIS server in a NTLM protected directory, which would return the user id encrypted for the PHP script to decrypt. This scenario worked for the most part, but occasionally the user name got garbled in decryption, so I resolved to try this trick multiple times, and if I got the same result back twice in a row, I considered it golden.
Visually, this is how it looks:
Here then are the scripts to make this hack work (please remember this code is several years old, procedural in nature and lack any sort of layering, I have not spent that much time cleaning it up for you-please be kind in your comments 😉 ):
A function call to wrap all of this code, this would be in “the_lib.php” from my previous post:
<?php
/**
* integrate with auth.php for scrty
*
* NOTE: must start script calling this function with session_start();
*
* @author Jason E. Sweat
* @since 2002-05-03
* @param string $appl_idftn the name of the variable to register
* @param boolean $deny_accs optional exit the script if scrty check fails, default true
* @param string $redir_parms optional parameters for the redirect url, default nothing
* @param string $redir_url optional redirection url, default php_self()
* @global string $scrty will be set to the contents of $_SESSION['scrty']
* @global string $_scrty_appl_idftn will be set to the contents of $_SESSION['scrty_data']['sctry_appl']
*
*/
function use_scrty(
$appl_idftn = 'APPL_IDFTN_NOT_SET'
,$deny_accs = true
,$redir_parms = ''
,$redir_url = 'REDIR_URL_NOT_SET' )
{
global $scrty;
global $_scrty_appl_idftn;
$scrty = (array_key_exists('scrty',$_SESSION)) ? $_SESSION['scrty'] : NULL;
if (array_key_exists('scrty_data', $_SESSION)) {
$_scrty_appl_idftn = (array_key_exists('scrty_appl',$_SESSION['scrty_data'])) ? $_SESSION['scrty_data']['scrty_appl'] : NULL;
} else {
$_scrty_appl_idftn = NULL;
}
if ('APPL_IDFTN_NOT_SET' == $appl_idftn) {
die('Must pass Application Identification to use_scrty().');
}
if ('REDIR_URL_NOT_SET' == $redir_url) {
$redir_url = phpself();
}
$s_host = $_SERVER['SERVER_NAME'];
if (!$scrty || !($_scrty_appl_idftn == $appl_idftn) ) {
$loc = "location: http://$s_host/auth/auth.php?appl=$appl_idftn&redirurl=";
$loc .= urlencode($redir_url.$redir_parms);
header($loc);
} else {
if ($deny_accs && !$scrty['appl_acss']) {
$die_msg = ($scrty['deny_msg'])
? $scrty['deny_msg']
: "<h1 style='text-align: center'>You do not have access to this application.</h1>n<!-- $appl_idftn -->n";
die( $die_msg );
}
}
}
</>
?>
Next, you have to have this auth.php script in a /auth/ directory of your web server:
<?php
/**
* @author Jason E. Sweat
* @purpose integrate NT security with SCRTY for PHP web apps
* @created 2002-03-21
* @revision $Revision: 1.4 $
*/
session_start();
//general script files
require_once('the_lib.php'); //has the "register" function to extract $_REQUEST vars
// and sql_format to do some string replacement
require_once('auth_sql.php'); //just some SQL statement
//rc4 encryption class file
require_once('rc4crypt/class.rc4crypt.php'); //will post this one below
//database related includes
require_once('adodb/adodb.inc.php');
require_once('dblogin.php'); //containts connection parameters used below
//set up unix environment variables (required for oracle connectivity)
putenv('ORACLE_SID=prdcrm');
putenv('ORACLE_HOME=/pkg/prdcrm/oracle');
//required for Oracle version below 8.1.5
putenv('NLS_LANG=');
// define modes
define('SCRTY_START',1);
define('SCRTY_AUTH',2);
define('SCRTY_ERROR',99);
define('AUTH_TIMEOUT',30);
define('AUTH_MAXTRIES',30);
$dbc = 'prdgenl';
$conn = &ADONewConnection('oci8');
//$conn->debug=true;
$conn->PConnect('',${$dbc}['uid'],${$dbc}['pwd'],${$dbc}['db']);
$ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
$ntauth_url = 'http://youriis.server.example.com/auth/auth.asp';
$scrty_data = $_SESSION['scrty_data'];
$mode = $scrty_data['scrty_mode'];
if (!$mode || SCRTY_ERROR == $mode) {
$mode = SCRTY_START;
}
if (!scrty_data) {
$scrty_data = array();
}
switch ($mode) {
case SCRTY_START:
$referer = $_SERVER['HTTP_REFERER'];
// check if scrty appl is valid
$redirurl = register('redirurl');
if (!$redirurl) {
show_error ("I don't know where to go when I am done!");
show_error("Please contact the maintainer of $referer for proper configuration.");
exit;
} else {
$scrty_data['redirurl'] = $redirurl;
}
$scrty_appl = register('appl');
if (!$scrty_appl) {
show_error("Application ($referer) is not configured correctly,");
show_error('contact application developer.');
exit;
} else {
$sql = sql_format($auth_sql['check_scrty_appl'], array('APPL' => $scrty_appl));
$rs = $conn->Execute($sql);
$row = $rs->fields;
if (!1==$row['CNT']) {
show_error("$scrty_appl is not a valid application.");
show_error("Please contact the maintainer of $referer for proper configuration.");
} else { // scrty_appl is okay, proceed to user authentication
$scrty_data['scrty_appl'] = $scrty_appl;
$scrty_data['scrty_appl'] = $scrty_appl;
$scrty_data['scrty_mode'] = SCRTY_AUTH;
$scrty_data['scrty_tries'] = 1;
$scrty_data['scrty_last_uid'] = 'not yet set';
$_SESSION['scrty_data'] = $scrty_data;
$skey = substr(md5(microtime()),0,15).mktime(); //strftime('%Y%m%d%H%M%S').
header('Location: '.$ntauth_url.'?skey='.urlencode($skey));
exit;
}
}
// set mode to auth and start identifying NT user
break;
case SCRTY_AUTH:
// check if current decrypted NT user is same as last time
$uid = register('uid');
$rc4 = new rc4crypt;
if (!$uid) {
show_error('Something funny is going on, I did not get your encrypted user id.');
exit;
}
$skey = register('skey');
$diff = mktime() - substr($skey,15,10);
if ($diff > AUTH_TIMEOUT) {
show_error('Are you tring something funny? Please try to log in again.');
exit;
}
if (strlen($skey)) {
while (strlen($skeytmp) < strlen($rc4->NT_IIS_Auth_key)) {
$skeytmp .= $skey;
}
} else {
$skeytmp = '00000000000000000000000 ';
}
$usekey = urldecode($rc4->endecrypt($skeytmp, $rc4->NT_IIS_Auth_key, ''));
$output = $rc4->endecrypt($usekey, $uid, 'de');
if ( !is_valid_login($output) || $output <> $scrty_data['scrty_last_uid']) {
$scrty_data['scrty_tries']++;
$scrty_data['scrty_last_uid'] = $output;
if ($scrty_data['scrty_tries'] > AUTH_MAXTRIES) {
show_error('Pulling the andon cord.');
print '<pre>';
var_dump($_SESSION);
exit;
}
$skey = substr(md5(microtime()),0,15).mktime(); //strftime('%Y%m%d%H%M%S').
$_SESSION['scrty_data'] = $scrty_data;
header('Location: '.$ntauth_url.'?skey='.urlencode($skey));
exit;
} else {// yes -> do scrty load stuff
$scrty_data['scrty_mode'] = SCRTY_START;
$_SESSION['scrty_data'] = $scrty_data;
scrty_load_info($output);
}
break;
default:
// can't get here normally, should abort with error
break;
}
/**
* @function scrty_deny
* @author Jason E. Sweat
* @purpose load scrty data into the session and redirect to the application page
* @created 2002-03-21
* @parm string $scrty the $scrty array, passed by reference
* @parm string $login the login from NT_AUTH_USER
*
*/
function scrty_deny(&$scrty, $login)
{
global $scrty_data;
global $conn;
global $auth_sql;
$parms = array(
'LOGIN' => $login
,'APPL' => $scrty_data['scrty_appl']
);
$scrty['deny_msg'] = < <<EOS
<html>
<head>
<title>Access Denied - {$scrty_data['scrty_appl']}</title>
</head>
<body>
<div style="text-align: center"><h1 style="color: red">Access Denied</h1>
EOS;
if ($scrty['valid_login']) {
//query for name info from all pers view
$sql = sql_format($auth_sql['fetch_scrty_info'], $parms);
$rs = $conn->Execute($sql);
$row = $rs->fields;
$scrty['deny_msg'] .= '<p style="color: blue">'.$row['NAME_FIRST'].' '.$row['NAME_LAST']."n";
}
// fetch admin users for appl
$sql = sql_format($auth_sql['admin_scrty_info'], $parms);
$rs = $conn->Execute($sql);
$appl_desc = $rs->fields['APPL_DESC'];
$scrty['deny_msg'] .= < <<EOS
<table border="0" cellpadding="5" cellspacing="0">
<tr>
<td colspan="2">
You do not have access to the $appl_desc application.<br>
If you think you should have access to this site, you might try contacting:
</td>
</tr>
<tr><th width="50%">Contact</th><th width="50%">Phone</th></tr>
EOS;
while ($rs && !$rs->EOF) {
$row = $rs->fields;
if ($row['EMAIL_ADDR']) {
$email_start = '<a href="mailto:'.$row['EMAIL_ADDR'].'?subject=Access+to+'.$scrty_data['scrty_appl'].'">';
$email_end = '</a>';
} else {
$email_start = '';
$email_end = '';
}
$scrty['deny_msg'] .= < <<EOS
<tr align="center">
<td>$email_start{$row['NAME_FIRST']} {$row['NAME_LAST']}$email_end</td>
<td>{$row['SITE_PHONE_NMBR']}</td>
EOS;
$rs->MoveNext();
}
$scrty['deny_msg'] .= "n</></></div>n</body>n";
}
/**
* @function scrty_load_info
* @author Jason E. Sweat
* @purpose load scrty data into the session and redirect to the application page
* @created 2002-03-21
* @parm string $login the DOMAINlogin from NT_AUTH_USER
*
*/
function scrty_load_info($login)
{
global $scrty_data;
global $conn;
global $auth_sql;
$login_info = split_login($login);
$scrty_info['login_info'] = $login_info;
$parms = array(
'LOGIN' => $login_info['login']
,'APPL' => $scrty_data['scrty_appl']
);
$sql = sql_format($auth_sql['check_login_idftn'], $parms);
$rs = $conn->Execute($sql);
$row = $rs->fields;
if (!1==$row['CNT']) { // login not found, bail
$scrty['valid_login'] = false;
$scrty['appl_acss'] = false;
scrty_deny($scrty,$login_info['login']);
} else { // is a valid login, continue
$scrty['valid_login'] = true;
$sql = sql_format($auth_sql['check_appl_acss'], $parms);
$rs = $conn->Execute($sql);
$row = $rs->fields;
if (!1==$row['CNT']) { // login does not have access
$scrty['appl_acss'] = false;
scrty_deny($scrty,$login_info['login']);
} else {
$scrty['appl_acss'] = true;
// bump login counts
$sql = sql_format($auth_sql['upd_appl_acss'], $parms);
$rs = $conn->Execute($sql);
}
$sql = sql_format($auth_sql['fetch_scrty_info'], $parms);
$rs = $conn->Execute($sql);
$row = $rs->fields;
$scrty['info'] = $row;
}
$_SESSION['scrty_data'] = $scrty_data;
$_SESSION['scrty'] = $scrty;
header('Location: '.$scrty_data['redirurl']);
exit;
}
/**
* @function show_error
* @author Jason E. Sweat
* @purpose show an error message in BIG RED LETTERS
* @created 2002-03-21
* @parm string $msg the error message to display
*
*/
function show_error($msg)
{
global $scrty_data;
global $_SESSION;
$scrty_data['scrty_mode'] = SCRTY_ERROR;
$_SESSION['scrty_data'] = $scrty_data;
print '<h1><font color="red">'. htmlentities($msg) .'</font></h1>';
}
/**
* @function split_login
* @author Jason E. Sweat
* @purpose Return an array of the domain and login, assumes a valid login
* @created 2002-03-21
* @parm string $login the DOMAINlogin from NT_AUTH_USER
* @return array
*
*/
function split_login($login)
{
$slash = '';
$check = split("[$slash]",$login);
$out['domain'] = $check[0];
$out['login'] = $check[1];
return $out;
}
/**
* @function is_valid_login
* @author Jason E. Sweat
* @purpose Return true or false depending on whether the value appears to be a valid
* domainlogin. The logic uses the split function to created an array of elements
* one for each of the in the string, then check to make sure there are exactly
* two elements and each of the elements is a populated string.
* @created 2002-03-21
* @parm string $login the DOMAINlogin from NT_AUTH_USER
* @return boolean
*
*/
function is_valid_login($login)
{
$slash = '';
$check = split("[$slash]",$login);
if ( 2 == count($check)) {
if (strlen($check[0]) && strlen($check[1])) {
return true;
}
}
return false;
}
</>
?>
here is the rc4 class (class.rc4crypt.php):
<?php
Made By Mukul Sabharwal [mukulsabharwal@yahoo.com]
// http://www.devhome.net/php/
// On October 21, 2000
// Updated February 24, 2001
// Now passes RC4 Vector Harness
class rc4crypt {
var $NT_IIS_Auth_key = 'shared_secret_key_between_asp_and_php';
function endecrypt ($pwd, $data, $case='') {
if ($case == 'de') {
$data = urldecode($data);
}
$key[] = "";
$box[] = "";
$temp_swap = "";
$pwd_length = 0;
$pwd_length = strlen($pwd);
for ($i = 0; $i < = 255; $i++) {
$key[$i] = ord(substr($pwd, ($i % $pwd_length), 1));
$box[$i] = $i;
}
$x = 0;
for ($i = 0; $i <= 255; $i++) {
$x = ($x + $box[$i] + $key[$i]) % 256;
$temp_swap = $box[$i];
$box[$i] = $box[$x];
$box[$x] = $temp_swap;
}
$temp = "";
$k = "";
$cipherby = "";
$cipher = "";
$a = 0;
$j = 0;
for ($i = 0; $i < strlen($data); $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$temp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $temp;
$k = $box[(($box[$a] + $box[$j]) % 256)];
$cipherby = ord(substr($data, $i, 1)) ^ $k;
$cipher .= chr($cipherby);
}
if ($case == 'de') {
$cipher = urldecode(urlencode($cipher));
} else {
$cipher = urlencode($cipher);
}
return $cipher;
}
}
?>
And now the ASP side of the equation, auth.asp:
< %@ Language=VBScript %> < % option explicit %> < % ' auth.asp ' requires sid post variable ' returns hash of sid, server.auth_user and private key dim sSID, sReDirURL, sNTUser, sKey, i, sBuffer, sSIDTmp, sUseKey sSID = Request.Form("skey") & Request.QueryString("skey") sReDirURL = Request.Form("redirurl") & Request.QueryString("redirurl") sNTUser = Request.ServerVariables("AUTH_USER") sKey = "shared_secret_key_between_asp_and_php" if len(sReDirURL) = 0 then sReDirURL = "http://yourapache.server.example.com/auth/auth.php" end if If len(sSID) > 0 Then While len(sSIDTmp) < len(sKey) sSIDTmp = sSIDTmp & sSID Wend Else sSIDTmp = "00000000000000000000000" End If 'For i = 1 to len(sKey) ' sUseKey = sUseKey & Chr( (Asc(Mid(sSIDTmp,i,1))+1) Xor (Asc(Mid(sKey,i,1))+1) ) 'Next sUseKey = EnDeCrypt(sKey,sSIDTmp) ' this works, requires anonymous access disabled on the directory 'Response.Write "You are: " & sNTUser sBuffer = EnDeCrypt(sNTUser,sUseKey) Response.Redirect(sReDirURL & "?skey=" & sSID & "&uid=" & server.URLEncode(sBuffer)) '&"&uk="&server.URLEncode(sUseKey)) %> >>>>>
and rc4.inc.asp:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
'::: :::
'::: This script performs 'RC4' Stream Encryption :::
'::: (Based on what is widely thought to be RSA's RC4 :::
'::: algorithm. It produces output streams that are identical :::
'::: to the commercial products) :::
'::: :::
'::: This script is Copyright
I was already thinking to ask you about that, after you cited that lib in another post. My interest in, or better, my very knowledge of the existence of NTLM, came after a client demanded a transparent and SSO solution for our webapp authentication and since i’m investigating the issue. NTLM is only the first piece of the puzzle, because once I’ll be able to verify that this route is viable, I’d like to find if one can obtain logon credentials from the browser and then authentify against a different back-end than IIS (says Ldap).
Thanks for these informations.
Very cool stuff 😉 Back to the original question on testing – no idea!
What may be interesting (or depressing) is Curl now seems to have some form of NTLM support (turns up in the PHP src here http://lxr.php.net/source/php-src/ext/curl/interface.c#230) since 7.10.6 (June 2003 judging from mailing lists).
What did you use to draw the diagram?
In this case, I used Visio, but I think dia now has all the capabilities to do this. An example of a recent drawing I did with dia is here.
Yep I have been using this capability (with the command line curl) to authenticate against our NTLM based proxy server at work to fetch gentoo portage files. Works great. Handy to know that if I need it, there may be an option to use it with the PHP curl extension.
Beware, this will only be “transparent” if your client uses IE. Mozilla and Firefox both support NTLM authentication, but still prompt you for your user name and password once per session (like other HTTP authentication schemes). You can then cache the credentials and it is not painful, but you should be aware of it.
I wish you good luck and happy coding on your project!
Regards,
Following on from a sitepoint thread, I arrived at this blog
Now though the diagram Marcus talks about is broken, Jason, can you fix it please? Thx
Image fixed, symptom of the WordPress upgrade.