├── ntlm.php └── verifyntlm.c /ntlm.php: -------------------------------------------------------------------------------- 1 | 'test', 'user1'=>'password'); 39 | 40 | if (!isset($userdb[strtolower($user)])) 41 | return false; 42 | return ntlm_md4(ntlm_utf8_to_utf16le($userdb[strtolower($user)])); 43 | } 44 | 45 | session_start(); 46 | $auth = ntlm_prompt("testwebsite", "testdomain", "mycomputer", "testdomain.local", "mycomputer.local", "get_ntlm_user_hash"); 47 | 48 | if ($auth['authenticated']) { 49 | print "You are authenticated as $auth[username] from $auth[domain]/$auth[workstation]"; 50 | } 51 | 52 | To logout, use the code: 53 | 54 | ntlm_unset_auth(); 55 | 56 | SAMBA 57 | ----- 58 | To use this library with samba, please read the instructions inside verifyntlm.c 59 | to compile the verifyntlm helper. Use the ntlm.php library as above but omit the 60 | get_ntlm_user_hash function and replace the ntlm_prompt line with this one: 61 | 62 | $auth = ntlm_prompt("testwebsite", "testdomain", "mycomputer", "testdomain.local", "mycomputer.local", null, "ntlm_verify_hash_smb"); 63 | 64 | For more, see http://siphon9.net/loune/2010/12/php-ntlm-integration-with-samba/ 65 | 66 | */ 67 | 68 | $ntlm_verifyntlmpath = '/sbin/verifyntlm'; 69 | 70 | function ntlm_utf8_to_utf16le($str) { 71 | //$result = ""; 72 | //for ($i = 0; $i < strlen($str); $i++) 73 | // $result .= $str[$i]."\0"; 74 | //return $result; 75 | return iconv('UTF-8', 'UTF-16LE', $str); 76 | } 77 | 78 | function ntlm_md4($s) { 79 | if (function_exists('mhash')) 80 | return mhash(MHASH_MD4, $s); 81 | return pack('H*', hash('md4', $s)); 82 | } 83 | 84 | function ntlm_av_pair($type, $utf16) { 85 | return pack('v', $type).pack('v', strlen($utf16)).$utf16; 86 | } 87 | 88 | function ntlm_field_value($msg, $start, $decode_utf16 = true) { 89 | $len = (ord($msg[$start+1]) * 256) + ord($msg[$start]); 90 | $off = (ord($msg[$start+5]) * 256) + ord($msg[$start+4]); 91 | $result = substr($msg, $off, $len); 92 | if ($decode_utf16) { 93 | //$result = str_replace("\0", '', $result); 94 | $result = iconv('UTF-16LE', 'UTF-8', $result); 95 | } 96 | return $result; 97 | } 98 | 99 | function ntlm_hmac_md5($key, $msg) { 100 | $blocksize = 64; 101 | if (strlen($key) > $blocksize) 102 | $key = pack('H*', md5($key)); 103 | 104 | $key = str_pad($key, $blocksize, "\0"); 105 | $ipadk = $key ^ str_repeat("\x36", $blocksize); 106 | $opadk = $key ^ str_repeat("\x5c", $blocksize); 107 | return pack('H*', md5($opadk.pack('H*', md5($ipadk.$msg)))); 108 | } 109 | 110 | function ntlm_get_random_bytes($length) { 111 | $result = ""; 112 | for ($i = 0; $i < $length; $i++) { 113 | $result .= chr(rand(0, 255)); 114 | } 115 | return $result; 116 | } 117 | 118 | function ntlm_get_challenge_msg($msg, $challenge, $targetname, $domain, $computer, $dnsdomain, $dnscomputer) { 119 | $domain = ntlm_field_value($msg, 16); 120 | $ws = ntlm_field_value($msg, 24); 121 | $tdata = ntlm_av_pair(2, ntlm_utf8_to_utf16le($domain)).ntlm_av_pair(1, ntlm_utf8_to_utf16le($computer)).ntlm_av_pair(4, ntlm_utf8_to_utf16le($dnsdomain)).ntlm_av_pair(3, ntlm_utf8_to_utf16le($dnscomputer))."\0\0\0\0\0\0\0\0"; 122 | $tname = ntlm_utf8_to_utf16le($targetname); 123 | 124 | $msg2 = "NTLMSSP\x00\x02\x00\x00\x00". 125 | pack('vvV', strlen($tname), strlen($tname), 48). // target name len/alloc/offset 126 | "\x01\x02\x81\x00". // flags 127 | $challenge. // challenge 128 | "\x00\x00\x00\x00\x00\x00\x00\x00". // context 129 | pack('vvV', strlen($tdata), strlen($tdata), 48 + strlen($tname)). // target info len/alloc/offset 130 | $tname.$tdata; 131 | return $msg2; 132 | } 133 | 134 | function ntlm_verify_hash_smb($challenge, $user, $domain, $workstation, $clientblobhash, $clientblob, $get_ntlm_user_hash) { 135 | global $ntlm_verifyntlmpath; 136 | $cmd = bin2hex($challenge)." ".bin2hex(ntlm_utf8_to_utf16le(strtoupper($user)))." ".bin2hex(ntlm_utf8_to_utf16le($domain))." ".bin2hex(ntlm_utf8_to_utf16le($workstation))." ".bin2hex($clientblobhash)." ".bin2hex($clientblob); 137 | 138 | return (`$ntlm_verifyntlmpath $cmd` == "1\n"); 139 | } 140 | 141 | function ntlm_verify_hash($challenge, $user, $domain, $workstation, $clientblobhash, $clientblob, $get_ntlm_user_hash) { 142 | 143 | $md4hash = $get_ntlm_user_hash($user); 144 | if (!$md4hash) 145 | return false; 146 | $ntlmv2hash = ntlm_hmac_md5($md4hash, ntlm_utf8_to_utf16le(strtoupper($user).$domain)); 147 | $blobhash = ntlm_hmac_md5($ntlmv2hash, $challenge.$clientblob); 148 | 149 | /* 150 | print $domain ."
"; 151 | print $user ."
"; 152 | print bin2hex($challenge )."
"; 153 | print bin2hex($clientblob )."
"; 154 | print bin2hex($clientblobhash )."
"; 155 | print bin2hex($md4hash )."
"; 156 | print bin2hex($ntlmv2hash)."
"; 157 | print bin2hex($blobhash)."
"; die; */ 158 | 159 | return ($blobhash == $clientblobhash); 160 | } 161 | 162 | function ntlm_parse_response_msg($msg, $challenge, $get_ntlm_user_hash_callback, $ntlm_verify_hash_callback) { 163 | $user = ntlm_field_value($msg, 36); 164 | $domain = ntlm_field_value($msg, 28); 165 | $workstation = ntlm_field_value($msg, 44); 166 | $ntlmresponse = ntlm_field_value($msg, 20, false); 167 | //$blob = "\x01\x01\x00\x00\x00\x00\x00\x00".$timestamp.$nonce."\x00\x00\x00\x00".$tdata; 168 | $clientblob = substr($ntlmresponse, 16); 169 | $clientblobhash = substr($ntlmresponse, 0, 16); 170 | 171 | if (substr($clientblob, 0, 8) != "\x01\x01\x00\x00\x00\x00\x00\x00") { 172 | return array('authenticated' => false, 'error' => 'NTLMv2 response required. Please force your client to use NTLMv2.'); 173 | } 174 | 175 | // print bin2hex($msg)."
"; 176 | 177 | if (!$ntlm_verify_hash_callback($challenge, $user, $domain, $workstation, $clientblobhash, $clientblob, $get_ntlm_user_hash_callback)) 178 | return array('authenticated' => false, 'error' => 'Incorrect username or password.', 'username' => $user, 'domain' => $domain, 'workstation' => $workstation); 179 | return array('authenticated' => true, 'username' => $user, 'domain' => $domain, 'workstation' => $workstation); 180 | } 181 | 182 | function ntlm_unset_auth() { 183 | unset ($_SESSION['_ntlm_auth']); 184 | } 185 | 186 | function ntlm_prompt($targetname, $domain, $computer, $dnsdomain, $dnscomputer, $get_ntlm_user_hash_callback, $ntlm_verify_hash_callback = 'ntlm_verify_hash', $failmsg = "

Authentication Required

") { 187 | 188 | $auth_header = isset($_SERVER['HTTP_AUTHORIZATION']) ? $_SERVER['HTTP_AUTHORIZATION'] : null; 189 | if ($auth_header == null && function_exists('apache_request_headers')) { 190 | $headers = apache_request_headers(); 191 | $auth_header = isset($headers['Authorization']) ? $headers['Authorization'] : null; 192 | } 193 | 194 | if (isset($_SESSION['_ntlm_auth'])) 195 | return $_SESSION['_ntlm_auth']; 196 | 197 | // post data retention, looks like not needed 198 | /*if ($_SERVER['REQUEST_METHOD'] == 'POST') { 199 | $_SESSION['_ntlm_post_data'] = $_POST; 200 | }*/ 201 | 202 | if (!$auth_header) { 203 | header('HTTP/1.1 401 Unauthorized'); 204 | header('WWW-Authenticate: NTLM'); 205 | print $failmsg; 206 | exit; 207 | } 208 | 209 | if (substr($auth_header,0,5) == 'NTLM ') { 210 | $msg = base64_decode(substr($auth_header, 5)); 211 | if (substr($msg, 0, 8) != "NTLMSSP\x00") { 212 | unset($_SESSION['_ntlm_post_data']); 213 | die('NTLM error header not recognised'); 214 | } 215 | 216 | if ($msg[8] == "\x01") { 217 | $_SESSION['_ntlm_server_challenge'] = ntlm_get_random_bytes(8); 218 | header('HTTP/1.1 401 Unauthorized'); 219 | $msg2 = ntlm_get_challenge_msg($msg, $_SESSION['_ntlm_server_challenge'], $targetname, $domain, $computer, $dnsdomain, $dnscomputer); 220 | header('WWW-Authenticate: NTLM '.trim(base64_encode($msg2))); 221 | //print bin2hex($msg2); 222 | exit; 223 | } 224 | else if ($msg[8] == "\x03") { 225 | $auth = ntlm_parse_response_msg($msg, $_SESSION['_ntlm_server_challenge'], $get_ntlm_user_hash_callback, $ntlm_verify_hash_callback); 226 | unset($_SESSION['_ntlm_server_challenge']); 227 | 228 | if (!$auth['authenticated']) { 229 | header('HTTP/1.1 401 Unauthorized'); 230 | header('WWW-Authenticate: NTLM'); 231 | //unset($_SESSION['_ntlm_post_data']); 232 | print $failmsg; 233 | print $auth['error']; 234 | exit; 235 | } 236 | 237 | // post data retention looks like not needed 238 | /*if (isset($_SESSION['_ntlm_post_data'])) { 239 | foreach ($_SESSION['_ntlm_post_data'] as $k => $v) { 240 | $_REQUEST[$k] = $v; 241 | $_POST[$k] = $v; 242 | } 243 | $_SERVER['REQUEST_METHOD'] = 'POST'; 244 | unset($_SESSION['_ntlm_post_data']); 245 | }*/ 246 | 247 | $_SESSION['_ntlm_auth'] = $auth; 248 | return $auth; 249 | } 250 | } 251 | } 252 | 253 | 254 | ?> 255 | -------------------------------------------------------------------------------- /verifyntlm.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | php ntlm authentication library 4 | Version 1.2 5 | verifyntlm.c - verifies NTLM credentials against samba using pdbedit 6 | 7 | Copyright (c) 2009-2010 Loune Lam 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | 27 | 28 | Prerequisites: 29 | - pdbedit (samba) - user database must be store locally 30 | - libssl (openssl) 31 | 32 | 33 | To install, compile and set the sticky bit: 34 | # gcc verifyntlm.c -lssl -o verifyntlm 35 | # chown root verifyntlm 36 | # chmod u=rwxs,g=x,o=x verifyntlm 37 | 38 | Move the binary to a location such as /sbin/ 39 | # mv verifyntlm /sbin 40 | 41 | If you put the binary somewhere else, please modify $ntlm_verifyntlmpath in ntlm.php 42 | 43 | For more, see http://siphon9.net/loune/2010/12/php-ntlm-integration-with-samba/ 44 | 45 | 46 | */ 47 | 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | 56 | #include 57 | #include 58 | 59 | #include 60 | 61 | #define PDBEDIT_PATH "/usr/bin/pdbedit" 62 | 63 | int get_hash_from_str(char* line, char* buf, int bufsize) { 64 | int colons = 0; 65 | char *capture = 0; 66 | char *c = line; 67 | int count = 0; 68 | int targetcolon = 3; 69 | 70 | for (; *c != 0; c++) { 71 | if (*c == ':') { 72 | colons++; 73 | if (colons == targetcolon + 1) 74 | goto done; 75 | if (colons == targetcolon) { 76 | capture = c+1; 77 | continue; 78 | } 79 | } 80 | 81 | if (capture != 0) { 82 | if (bufsize == count + 1) 83 | goto done; 84 | buf[c-capture] = *c; 85 | count++; 86 | } 87 | 88 | } 89 | 90 | done: 91 | buf[count] = '\0'; 92 | return count; 93 | } 94 | 95 | int get_ntlm_hash(char* user, size_t userlen, char *buffer, size_t buflen) { 96 | char str[512]; 97 | int pipefd[2]; 98 | pid_t pid; 99 | int status, died; 100 | 101 | pipe (pipefd); 102 | 103 | switch (pid = fork()) { 104 | case -1: 105 | printf("Error: can't fork\n"); 106 | exit(-1); 107 | 108 | case 0: /* child */ 109 | close(STDOUT_FILENO); 110 | close(STDERR_FILENO); 111 | 112 | dup2(pipefd[1], STDOUT_FILENO); 113 | dup2(pipefd[1], STDERR_FILENO); 114 | 115 | close (pipefd[0]); 116 | setuid(0); 117 | execl(PDBEDIT_PATH, "pdbedit", "-w", user, NULL); 118 | 119 | default: /* parent */ 120 | close(STDIN_FILENO); 121 | dup2(pipefd[0], STDIN_FILENO); 122 | close(pipefd[1]); 123 | while (fgets(str, sizeof(str), stdin)) { 124 | char *c; 125 | 126 | int len = strlen(str); 127 | if (userlen < len) 128 | len = userlen; 129 | 130 | for (c = str; c < str + len; c++) 131 | *c = toupper(*c); 132 | 133 | if (len && !strncmp(str, user, len)) { 134 | /* found our match */ 135 | get_hash_from_str(str, buffer, buflen); 136 | return 0; 137 | } 138 | } 139 | 140 | died = wait(&status); 141 | } 142 | 143 | return 1; 144 | } 145 | 146 | 147 | int hex_decode(const char* input, char* buffer, unsigned int max_buf_len) { 148 | const char *c = input; 149 | char *b = buffer; 150 | unsigned char mult = 16; 151 | 152 | if (max_buf_len == 0) 153 | return 0; 154 | 155 | *b = 0; 156 | while (1) { 157 | if (*c >= 'A' && *c <= 'F') 158 | *b += (*c - 'A' + 10) * mult; 159 | else if (*c >= 'a' && *c <= 'f') 160 | *b += (*c - 'a' + 10) * mult; 161 | else if (*c >= '0' && *c <= '9') 162 | *b += (*c - '0') * mult; 163 | else 164 | break; 165 | 166 | if (mult == 16) { 167 | mult = 1; 168 | } 169 | else { 170 | mult = 16; 171 | b++; 172 | if ((unsigned int)(b - (char *)buffer) == max_buf_len) 173 | break; 174 | *b = 0; 175 | } 176 | 177 | c++; 178 | } 179 | 180 | if (mult == 1) b++; /* allow partial match */ 181 | 182 | return b - (char *)buffer; 183 | } 184 | 185 | int main(int argc, char** argv) { 186 | 187 | if (geteuid() != 0) { 188 | printf("SUID root needed. Please set the sticky bit and correct permissions for %s.\n" 189 | " ie:\n" 190 | " # chown root %s\n" 191 | " # chmod u=rwxs,g=x,o=x %s\n", argv[0], argv[0], argv[0]); 192 | exit(-1); 193 | } 194 | 195 | if (access(PDBEDIT_PATH, F_OK ) == -1) { 196 | printf("%s not found. Please install samba or change the PDBEDIT_PATH constant.\n", PDBEDIT_PATH); 197 | exit(-1); 198 | } 199 | 200 | if (argc < 7) { 201 | printf("usage: %s challenge user domain workstation clientblob clientblobhash\n", argv[0]); 202 | printf("string arguments are expected to be in UTF16LE\n"); 203 | printf("all arguments are hex encoded\n"); 204 | printf("prints 1 if successful, otherwise 0\n"); 205 | exit(-1); 206 | } 207 | 208 | 209 | char *challenge = (char *)malloc(strlen(argv[1])); 210 | size_t challenge_len = hex_decode(argv[1], challenge, strlen(argv[1])); 211 | char *user = (char *)malloc(strlen(argv[2])); 212 | size_t user_len = hex_decode(argv[2], user, strlen(argv[2])); 213 | char *domain = (char *)malloc(strlen(argv[3])); 214 | size_t domain_len = hex_decode(argv[3], domain, strlen(argv[3])); 215 | char *workstation = (char *)malloc(strlen(argv[4])); 216 | size_t workstation_len = hex_decode(argv[4], workstation, strlen(argv[4])); 217 | char *clientblobhash = (char *)malloc(strlen(argv[5])); 218 | size_t clientblobhash_len = hex_decode(argv[5], clientblobhash, strlen(argv[5])); 219 | char *clientblob = (char *)malloc(strlen(argv[6])); 220 | size_t clientblob_len = hex_decode(argv[6], clientblob, strlen(argv[6])); 221 | 222 | /* convert username from UTF-16LE to UTF-8 */ 223 | char userutf8[512]; 224 | char* conv_user = user; 225 | size_t conv_user_len = user_len; 226 | char* conv_userutf8 = userutf8; 227 | size_t conv_userutf8_len = sizeof(userutf8) - 1; 228 | iconv_t converter = iconv_open("UTF-8", "UTF-16LE"); 229 | iconv(converter, &conv_user, &conv_user_len, &conv_userutf8, &conv_userutf8_len); 230 | *conv_userutf8 = 0; 231 | iconv_close(converter); 232 | 233 | /* find hash */ 234 | char buffer[512]; 235 | if (!get_ntlm_hash(userutf8, conv_userutf8 - userutf8, buffer, sizeof(buffer))) { 236 | char decoded[512]; 237 | int decoded_len = hex_decode(buffer, decoded, sizeof(decoded)); 238 | 239 | int userdomain_len = user_len + domain_len; 240 | unsigned char* userdomain = (unsigned char *)malloc(userdomain_len); 241 | memcpy(userdomain, user, user_len); 242 | memcpy(userdomain + user_len, domain, domain_len); 243 | 244 | unsigned char ntlmv2hash[EVP_MAX_MD_SIZE]; 245 | unsigned int ntlmv2hash_len = 0; 246 | HMAC(EVP_md5(), decoded, decoded_len, userdomain, userdomain_len, ntlmv2hash, &ntlmv2hash_len); 247 | 248 | int challengeclientblob_len = challenge_len + clientblob_len; 249 | unsigned char* challengeclientblob = (unsigned char *)malloc(challengeclientblob_len); 250 | memcpy(challengeclientblob, challenge, challenge_len); 251 | memcpy(challengeclientblob + challenge_len, clientblob, clientblob_len); 252 | 253 | unsigned char blobhash[EVP_MAX_MD_SIZE]; 254 | unsigned int blobhash_len = 0; 255 | HMAC(EVP_md5(), ntlmv2hash, ntlmv2hash_len, challengeclientblob, 256 | challengeclientblob_len, blobhash, &blobhash_len); 257 | 258 | if ((unsigned int)clientblobhash_len == blobhash_len 259 | && !strncmp(clientblobhash, (char *)blobhash, blobhash_len)) 260 | printf("1\n"); 261 | else 262 | printf("0\n"); 263 | 264 | } 265 | else { 266 | printf("0\n"); 267 | } 268 | 269 | return 0; 270 | } 271 | 272 | --------------------------------------------------------------------------------