mirror of https://gitee.com/bigwinds/arangodb
Prevent guessing of Database names (#5748)
This commit is contained in:
parent
b88fde9659
commit
28c3de87e7
|
@ -137,9 +137,15 @@ bool resolveRequestContext(GeneralRequest& req) {
|
||||||
GeneralCommTask::RequestFlow GeneralCommTask::prepareExecution(
|
GeneralCommTask::RequestFlow GeneralCommTask::prepareExecution(
|
||||||
GeneralRequest& req) {
|
GeneralRequest& req) {
|
||||||
if (!::resolveRequestContext(req)) {
|
if (!::resolveRequestContext(req)) {
|
||||||
addErrorResponse(rest::ResponseCode::NOT_FOUND, req.contentTypeResponse(),
|
if (_auth->isActive()) {
|
||||||
req.messageId(), TRI_ERROR_ARANGO_DATABASE_NOT_FOUND,
|
// prevent guessing of database names (issue #5030)
|
||||||
TRI_errno_string(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND));
|
addErrorResponse(rest::ResponseCode::UNAUTHORIZED,
|
||||||
|
req.contentTypeResponse(), req.messageId(),
|
||||||
|
TRI_ERROR_FORBIDDEN);
|
||||||
|
} else {
|
||||||
|
addErrorResponse(rest::ResponseCode::NOT_FOUND, req.contentTypeResponse(),
|
||||||
|
req.messageId(), TRI_ERROR_ARANGO_DATABASE_NOT_FOUND);
|
||||||
|
}
|
||||||
return RequestFlow::Abort;
|
return RequestFlow::Abort;
|
||||||
}
|
}
|
||||||
TRI_ASSERT(req.requestContext() != nullptr);
|
TRI_ASSERT(req.requestContext() != nullptr);
|
||||||
|
@ -313,8 +319,7 @@ void GeneralCommTask::executeRequest(
|
||||||
} else {
|
} else {
|
||||||
addErrorResponse(rest::ResponseCode::SERVICE_UNAVAILABLE,
|
addErrorResponse(rest::ResponseCode::SERVICE_UNAVAILABLE,
|
||||||
request->contentTypeResponse(), messageId,
|
request->contentTypeResponse(), messageId,
|
||||||
TRI_ERROR_QUEUE_FULL,
|
TRI_ERROR_QUEUE_FULL);
|
||||||
TRI_errno_string(TRI_ERROR_QUEUE_FULL));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// synchronous request
|
// synchronous request
|
||||||
|
@ -398,6 +403,12 @@ void GeneralCommTask::addErrorResponse(rest::ResponseCode code,
|
||||||
addSimpleResponse(code, respType, messageId, std::move(buffer));
|
addSimpleResponse(code, respType, messageId, std::move(buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GeneralCommTask::addErrorResponse(rest::ResponseCode code,
|
||||||
|
rest::ContentType respType,
|
||||||
|
uint64_t messageId, int errorNum) {
|
||||||
|
addErrorResponse(code, respType, messageId, errorNum, TRI_errno_string(errorNum));
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// --SECTION-- private methods
|
// --SECTION-- private methods
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
@ -462,8 +473,7 @@ bool GeneralCommTask::handleRequestSync(std::shared_ptr<RestHandler> handler) {
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
addErrorResponse(rest::ResponseCode::SERVICE_UNAVAILABLE,
|
addErrorResponse(rest::ResponseCode::SERVICE_UNAVAILABLE,
|
||||||
handler->request()->contentTypeResponse(), messageId,
|
handler->request()->contentTypeResponse(), messageId,
|
||||||
TRI_ERROR_QUEUE_FULL,
|
TRI_ERROR_QUEUE_FULL);
|
||||||
TRI_errno_string(TRI_ERROR_QUEUE_FULL));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ok;
|
return ok;
|
||||||
|
|
|
@ -132,7 +132,9 @@ class GeneralCommTask : public SocketTask {
|
||||||
|
|
||||||
/// @brief send response including error response body
|
/// @brief send response including error response body
|
||||||
void addErrorResponse(rest::ResponseCode, rest::ContentType,
|
void addErrorResponse(rest::ResponseCode, rest::ContentType,
|
||||||
uint64_t messageId, int code, std::string const&);
|
uint64_t messageId, int errorNum, std::string const&);
|
||||||
|
void addErrorResponse(rest::ResponseCode, rest::ContentType,
|
||||||
|
uint64_t messageId, int errorNum);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
GeneralServer* const _server;
|
GeneralServer* const _server;
|
||||||
|
|
|
@ -307,10 +307,10 @@ void VstCommTask::handleAuthHeader(VPackSlice const& header,
|
||||||
// mop: hmmm...user should be completely ignored if there is no auth IMHO
|
// mop: hmmm...user should be completely ignored if there is no auth IMHO
|
||||||
// obi: user who sends authentication expects a reply
|
// obi: user who sends authentication expects a reply
|
||||||
addErrorResponse(ResponseCode::OK, rest::ContentType::VPACK, messageId, TRI_ERROR_NO_ERROR,
|
addErrorResponse(ResponseCode::OK, rest::ContentType::VPACK, messageId, TRI_ERROR_NO_ERROR,
|
||||||
"authentication successful");
|
"auth successful");
|
||||||
} else {
|
} else {
|
||||||
addErrorResponse(rest::ResponseCode::UNAUTHORIZED, rest::ContentType::VPACK, messageId,
|
addErrorResponse(rest::ResponseCode::UNAUTHORIZED, rest::ContentType::VPACK, messageId,
|
||||||
TRI_ERROR_HTTP_UNAUTHORIZED, "authentication failed");
|
TRI_ERROR_HTTP_UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,12 +44,6 @@ RestStatus RestDatabaseHandler::execute() {
|
||||||
if (type == rest::RequestType::GET) {
|
if (type == rest::RequestType::GET) {
|
||||||
return getDatabases();
|
return getDatabases();
|
||||||
} else if (type == rest::RequestType::POST) {
|
} else if (type == rest::RequestType::POST) {
|
||||||
if (!_vocbase.isSystem()) {
|
|
||||||
generateError(GeneralResponse::responseCode(TRI_ERROR_ARANGO_USE_SYSTEM_DATABASE),
|
|
||||||
TRI_ERROR_ARANGO_USE_SYSTEM_DATABASE);
|
|
||||||
|
|
||||||
return RestStatus::DONE;
|
|
||||||
}
|
|
||||||
return createDatabase();
|
return createDatabase();
|
||||||
} else if (type == rest::RequestType::DELETE_REQ) {
|
} else if (type == rest::RequestType::DELETE_REQ) {
|
||||||
return deleteDatabase();
|
return deleteDatabase();
|
||||||
|
@ -71,14 +65,22 @@ RestStatus RestDatabaseHandler::getDatabases() {
|
||||||
return RestStatus::DONE;
|
return RestStatus::DONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result res;
|
||||||
VPackBuilder builder;
|
VPackBuilder builder;
|
||||||
|
|
||||||
if (suffixes.empty() || suffixes[0] == "user") {
|
if (suffixes.empty() || suffixes[0] == "user") {
|
||||||
std::vector<std::string> names;
|
std::vector<std::string> names;
|
||||||
if (suffixes.empty()) {
|
if (suffixes.empty()) {
|
||||||
names = methods::Databases::list(std::string());
|
if (!_vocbase.isSystem()) {
|
||||||
|
res.reset(TRI_ERROR_ARANGO_USE_SYSTEM_DATABASE);
|
||||||
|
} else {
|
||||||
|
names = methods::Databases::list(std::string());
|
||||||
|
}
|
||||||
} else if (suffixes[0] == "user") {
|
} else if (suffixes[0] == "user") {
|
||||||
names = methods::Databases::list(_request->user());
|
if (!_request->authenticated()) {
|
||||||
|
res.reset(TRI_ERROR_FORBIDDEN);
|
||||||
|
} else {
|
||||||
|
names = methods::Databases::list(_request->user());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.openArray();
|
builder.openArray();
|
||||||
|
@ -87,16 +89,12 @@ RestStatus RestDatabaseHandler::getDatabases() {
|
||||||
}
|
}
|
||||||
builder.close();
|
builder.close();
|
||||||
} else if (suffixes[0] == "current") {
|
} else if (suffixes[0] == "current") {
|
||||||
Result res = methods::Databases::info(&_vocbase, builder);
|
res = methods::Databases::info(&_vocbase, builder);
|
||||||
|
|
||||||
if (!res.ok()) {
|
|
||||||
generateError(rest::ResponseCode::BAD, res.errorNumber());
|
|
||||||
|
|
||||||
return RestStatus::DONE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (builder.isEmpty()) {
|
if (res.fail()) {
|
||||||
|
generateError(res);
|
||||||
|
} else if (builder.isEmpty()) {
|
||||||
generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER);
|
generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER);
|
||||||
} else {
|
} else {
|
||||||
generateOk(rest::ResponseCode::OK, builder.slice());
|
generateOk(rest::ResponseCode::OK, builder.slice());
|
||||||
|
@ -108,6 +106,13 @@ RestStatus RestDatabaseHandler::getDatabases() {
|
||||||
// / @brief was docuBlock JSF_get_api_database_create
|
// / @brief was docuBlock JSF_get_api_database_create
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
RestStatus RestDatabaseHandler::createDatabase() {
|
RestStatus RestDatabaseHandler::createDatabase() {
|
||||||
|
if (!_vocbase.isSystem()) {
|
||||||
|
generateError(GeneralResponse::responseCode(TRI_ERROR_ARANGO_USE_SYSTEM_DATABASE),
|
||||||
|
TRI_ERROR_ARANGO_USE_SYSTEM_DATABASE);
|
||||||
|
|
||||||
|
return RestStatus::DONE;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::string> const& suffixes = _request->suffixes();
|
std::vector<std::string> const& suffixes = _request->suffixes();
|
||||||
bool parseSuccess = false;
|
bool parseSuccess = false;
|
||||||
VPackSlice body = this->parseVPackBody(parseSuccess);
|
VPackSlice body = this->parseVPackBody(parseSuccess);
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
desc: false
|
desc: false
|
||||||
},
|
},
|
||||||
|
|
||||||
url: arangoHelper.databaseUrl('/_api/database'),
|
url: arangoHelper.databaseUrl('/_api/database/user'),
|
||||||
|
|
||||||
comparator: function (item, item2) {
|
comparator: function (item, item2) {
|
||||||
var a = item.get('name').toLowerCase();
|
var a = item.get('name').toLowerCase();
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
cache: false,
|
cache: false,
|
||||||
url: this.url + '/user',
|
url: this.url,
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
processData: false,
|
processData: false,
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
|
@ -104,7 +104,7 @@
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
cache: false,
|
cache: false,
|
||||||
url: this.url + '/current',
|
url: arangoHelper.databaseUrl('/_api/database/current'),
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
processData: false,
|
processData: false,
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
|
|
|
@ -35,6 +35,7 @@ const users = require("@arangodb/users");
|
||||||
const request = require('@arangodb/request');
|
const request = require('@arangodb/request');
|
||||||
const crypto = require('@arangodb/crypto');
|
const crypto = require('@arangodb/crypto');
|
||||||
const expect = require('chai').expect;
|
const expect = require('chai').expect;
|
||||||
|
const ERRORS = require('internal').errors;
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
/// @brief test suite
|
/// @brief test suite
|
||||||
|
@ -46,6 +47,7 @@ function AuthSuite() {
|
||||||
return arango.getEndpoint().replace(/^tcp:/, 'http:').replace(/^ssl:/, 'https:');
|
return arango.getEndpoint().replace(/^tcp:/, 'http:').replace(/^ssl:/, 'https:');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// hardcoded in testsuite
|
||||||
const jwtSecret = 'haxxmann';
|
const jwtSecret = 'haxxmann';
|
||||||
const user = 'hackers@arangodb.com';
|
const user = 'hackers@arangodb.com';
|
||||||
|
|
||||||
|
@ -487,6 +489,60 @@ function AuthSuite() {
|
||||||
expect(res).to.be.an.instanceof(request.Response);
|
expect(res).to.be.an.instanceof(request.Response);
|
||||||
expect(res).to.have.property('statusCode', 401);
|
expect(res).to.have.property('statusCode', 401);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
testDatabaseGuessing: function() {
|
||||||
|
let jwt = crypto.jwtEncode(jwtSecret, {
|
||||||
|
"preferred_username": "root",
|
||||||
|
"iss": "arangodb", "exp": Math.floor(Date.now() / 1000) + 3600
|
||||||
|
}, 'HS256');
|
||||||
|
// should respond with unauthorized name guessing
|
||||||
|
var res = request.get({
|
||||||
|
url: baseUrl() + "/_db/nonexisting/_api/version",
|
||||||
|
auth: {
|
||||||
|
bearer: jwt,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(res).to.be.an.instanceof(request.Response);
|
||||||
|
expect(res).to.have.property('statusCode', 401);
|
||||||
|
},
|
||||||
|
|
||||||
|
testDatabaseListNonSystem: function() {
|
||||||
|
let jwt = crypto.jwtEncode(jwtSecret, {
|
||||||
|
"preferred_username": "root",
|
||||||
|
"iss": "arangodb", "exp": Math.floor(Date.now() / 1000) + 3600
|
||||||
|
}, 'HS256');
|
||||||
|
// supported
|
||||||
|
var res = request.get({
|
||||||
|
url: baseUrl() + "/_api/database",
|
||||||
|
auth: {
|
||||||
|
bearer: jwt,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).to.be.an.instanceof(request.Response);
|
||||||
|
expect(res).to.have.property('statusCode', 200);
|
||||||
|
expect(res).to.have.property('json');
|
||||||
|
expect(res.json).to.have.property('result');
|
||||||
|
expect(res.json.result).to.be.an('array');
|
||||||
|
expect(res.json.result).to.include("_system");
|
||||||
|
|
||||||
|
try {
|
||||||
|
db._createDatabase("other");
|
||||||
|
// not supported on non _system
|
||||||
|
res = request.get({
|
||||||
|
url: baseUrl() + "/_db/other/_api/database",
|
||||||
|
auth: {
|
||||||
|
bearer: jwt,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(res).to.be.an.instanceof(request.Response);
|
||||||
|
expect(res).to.have.property('statusCode', ERRORS.ERROR_ARANGO_USE_SYSTEM_DATABASE.code);
|
||||||
|
} catch(e) {
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
db._dropDatabase("other");
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue