我正在尝试访问本地 NVR 摄像头系统,该系统对其 API 使用摘要身份验证方法,问题是我收到“密码错误”错误。我对所有内容进行了两次和三次检查,一切似乎都是正确的,所以我不明白为什么我会收到错误消息。我可以用一双新眼睛了。显然我已经检查了几十次密码是否正确,所以我怀疑我的
getAuth
逻辑在某个地方不正确。
'use strict';
const express = require('express');
const app = express();
const crypto = require('crypto')
const md5_hash = function(value) {
return crypto.createHash('md5').update(value).digest('hex');
};
app.use(express.json());
app.use(express.text());
app.get('/', async function(request, response) {
var reply = {};
var api = {
'user': request.query.user || '',
'pass': request.query.pass || '', // decoded
'nonce': request.query.nonce || '',
'uri': request.query.uri || '/LAPI/V1.0/System/Security/Login',
'ip': '192.168.3.254'
};
var url = 'http://' + api.ip + api.uri;
// request to get nonce value from server
var preauth_reply = await req({'url':url, 'options':{'headers':{'method':'PUT'}}});
// gets the nonce from the preauth response headers
api.nonce = preauth_reply.httpHeaders['WWW-Authenticate'].split('=')[4].replace(/[^0-9]+/g, '');
api.auth_string = 'Digest ' + getAuth(api);
var request = {
'url': 'http://' + api.ip + api.uri,
'options': {
'headers': {
'method': 'PUT',
'Content-Type': 'application/json',
'Authorization': api.auth_string
}
}
};
var reply = await req(request);
response.status(200).json(reply);
});
function getAuth(api) {
var auth = {
'username': api.user,
'realm': 'NVRDVR',
'qop': 'auth',
// aleady updated with pre-auth header nonce number
'nonce': api.nonce,
// 'algorithm': 'MD5',
'cnonce': (Math.random() * ((new Date).getTime() / 1e3)).toFixed(0),
'nc': '00000001',
'uri': api.uri,
'response': '' // built later
};
auth.response = (function() {
var c_string = api.user + ':' + auth.realm + ':' + api.pass;
let c_hash = md5_hash(c_string);
var u_string = 'PUT:' + api.uri;
let u_hash = md5_hash(u_string);
var l_string = c_hash + ':' + auth.nonce + ':' + auth.nc + ':' + auth.cnonce + ':' + auth.qop + ':' + u_hash;
return md5_hash(l_string);
})();
// ex output: username="root",realm="NVRDVR",qop=auth,nonce="123456789",cnonce="987654321",nc=00000001,uri="/LAPI/V1.0/System/Security/Login",response="817a24036881587163fb49e04d0d944d"
return Object.keys(auth).map(function(k) {
let is_not_quoted = ['qop', 'algorithm', 'nc'].indexOf(k) > -1;
let value = is_not_quoted ? auth[k] : '"' + auth[k] + '"';
return k + '=' + value;
}).join(',');
}
async function req(request) {
var reply = {};
var response = await fetch(request.url, request.options).catch(function(err) {
// err = [ 'stack', 'message', 'cause' ]
// err.cause = [ 'stack', 'message', 'name', 'code', 'data' ]
if (err.cause.code === 'HPE_INVALID_HEADER_TOKEN') {
// returns header/body payload, so I can parse it later myself
return err.cause.data;
}
});
var body = null;
try {
body = await response.clone().json();
}
catch (e) {
try {
body = await response.clone().text();
}
// resolves "HPE_INVALID_HEADER_TOKEN" error by parsing header/body myself
catch (innerE) {
let res_parts = response.split('\n');
var first_curly_bracket_index = res_parts.findIndex(function(item) {return !!item.match('{');});
// gets everything BEFORE the first open curly bracket, where the JSON starts
let headers = Object.assign({}, ...res_parts.slice(1, first_curly_bracket_index - 1).flatMap(function(item) {
let split_parts = item.split(/:(.*)/s);
return {[split_parts[0]]: split_parts[1]?.trim()};
}));
reply.httpCode = 200;
reply.httpHeaders = headers;
// gets everything AT the first open curly bracket, where the JSON starts
body = JSON.parse(res_parts.slice(first_curly_bracket_index).join('\n'));
}
}
if (body) {
reply.success = true;
reply.httpHeaders = response.header || reply.httpHeaders;
reply.httpCode = response.status || reply.httpCode;
reply.data = body || null;
return reply;
}
reply.error = true;
return reply;
}
app.listen(8888);
作为一种调试方法,我将所有变量放入我的
reply
对象中(在此代码的未删节版本中),以确保没有任何未定义的内容,这是输出。 data
和 httpHeaders
是来自服务器的响应。 "StatusCode": 50801
翻译为 The password is incorrect.
{
"httpCode": 200,
"httpHeaders": {
"Content-Type": "text/html;CHARset=utf-8",
"Connection": "close",
"X-Frame-Options": "SAMEORIGIN",
"Content-Security-Policy": "img-src 'self' data:; default-src 'self' 'unsafe-inline' 'unsafe-eval';",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block"
},
"success": true,
"data": {
"Response": {
"ResponseURL": "/LAPI/V1.0/System/Security/Login",
"ResponseCode": 1,
"ResponseString": "Common Error",
"StatusCode": 50801,
"Data": {
"RemainLockTimes": 2,
"RemainUnlockTime": 5
}
}
},
"api": {
"user": "api",
"pass": "{REMOVED}",
"nonce": "1406364820",
"uri": "/LAPI/V1.0/System/Security/Login",
"ip": "192.168.3.254",
"c_string": "api:NVRDVR:{REMOVED}",
"u_string": "PUT:/LAPI/V1.0/System/Security/Login",
"l_string": "30779ba755a47f5eb9cf90ce2e6d7c2f:1406364820:00000004:200397104:auth:a9bc5304a295f82075fc55d8bfa70457",
"auth": {
"username": "api",
"realm": "NVRDVR",
"qop": "auth",
"nonce": "1406364820",
"cnonce": "200397104",
"nc": "00000004",
"uri": "/LAPI/V1.0/System/Security/Login",
"response": "817a24036881587163fb49e04d0d944d"
},
"auth_string": "Digest username=\"api\",realm=\"NVRDVR\",qop=auth,nonce=\"1406364820\",cnonce=\"200397104\",nc=00000004,uri=\"/LAPI/V1.0/System/Security/Login\",response=\"817a24036881587163fb49e04d0d944d\""
}
}
并且,如果有帮助的话,这些是来自预身份验证请求的标头,我从中获取随机数:
Status: 200
WWW-Authenticate: Digest qop="auth",algorithm="MD5",realm="NVRDVR",nonce="1412251636",stale="FALSE"
Content-Type: text/html;CHARset=utf-8
Connection: close
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: img-src 'self' data:; default-src 'self' 'unsafe-inline' 'unsafe-eval';
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
有人看出有什么问题吗?
并非所有角色都能被“看到”。以十六进制查看。例如,即使是“纯”文本/ASCII 也有 nul 字符。