├── 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 |
--------------------------------------------------------------------------------