new: [internal] Allow to log authkey usage in Redis

pull/6581/head
Jakub Onderka 2020-11-17 20:09:49 +01:00
parent 6ce13b8168
commit e5e855b3c2
2 changed files with 101 additions and 78 deletions

View File

@ -230,82 +230,7 @@ class AppController extends Controller
if (array_key_exists('Security', $this->components)) {
$this->Security->csrfCheck = false;
}
// If enabled, allow passing the API key via a named parameter (for crappy legacy systems only)
$namedParamAuthkey = false;
if (Configure::read('Security.allow_unsafe_apikey_named_param') && !empty($this->params['named']['apikey'])) {
$namedParamAuthkey = $this->params['named']['apikey'];
}
// Authenticate user with authkey in Authorization HTTP header
if (!empty($_SERVER['HTTP_AUTHORIZATION']) || !empty($namedParamAuthkey)) {
$foundMispAuthKey = false;
$authentication = explode(',', $_SERVER['HTTP_AUTHORIZATION']);
if (!empty($namedParamAuthkey)) {
$authentication[] = $namedParamAuthkey;
}
$user = false;
foreach ($authentication as $authKey) {
$authKey = trim($authKey);
if (preg_match('/^[a-zA-Z0-9]{40}$/', $authKey)) {
$foundMispAuthKey = true;
$temp = $this->checkAuthUser($authKey);
if ($temp) {
$user = $temp;
break;
}
}
}
if ($foundMispAuthKey) {
$authKeyToStore = substr($authKey, 0, 4)
. str_repeat('*', 32)
. substr($authKey, -4);
if ($user) {
unset($user['gpgkey']);
unset($user['certif_public']);
// User found in the db, add the user info to the session
if (Configure::read('MISP.log_auth')) {
$this->Log = ClassRegistry::init('Log');
$this->Log->create();
$log = array(
'org' => $user['Organisation']['name'],
'model' => 'User',
'model_id' => $user['id'],
'email' => $user['email'],
'action' => 'auth',
'title' => "Successful authentication using API key ($authKeyToStore)",
'change' => 'HTTP method: ' . $_SERVER['REQUEST_METHOD'] . PHP_EOL . 'Target: ' . $this->here,
);
$this->Log->save($log);
}
$this->Session->renew();
$this->Session->write(AuthComponent::$sessionKey, $user);
$this->isApiAuthed = true;
} else {
// User not authenticated correctly
// reset the session information
$redis = $this->{$this->modelClass}->setupRedis();
// Do not log every fail, but just once per hour
if ($redis && !$redis->exists('misp:auth_fail_throttling:' . $authKeyToStore)) {
$redis->setex('misp:auth_fail_throttling:' . $authKeyToStore, 3600, 1);
$this->Log = ClassRegistry::init('Log');
$this->Log->create();
$log = array(
'org' => 'SYSTEM',
'model' => 'User',
'model_id' => 0,
'email' => 'SYSTEM',
'action' => 'auth_fail',
'title' => "Failed authentication using API key ($authKeyToStore)",
'change' => null,
);
$this->Log->save($log);
}
$this->Session->destroy();
throw new ForbiddenException('Authentication failed. Please make sure you pass the API key of an API enabled user along in the Authorization header.');
}
unset($user);
}
}
if ($this->Auth->user() == null) {
if ($this->__logByAuthKey() === false ||$this->Auth->user() === null) {
throw new ForbiddenException('Authentication failed. Please make sure you pass the API key of an API enabled user along in the Authorization header.');
}
} elseif (!$this->Session->read(AuthComponent::$sessionKey)) {
@ -489,6 +414,100 @@ class AppController extends Controller
}
}
/**
* @return null|bool True if authkey was correct, False if incorrect and Null if not provided
* @throws Exception
*/
private function __logByAuthKey()
{
// If enabled, allow passing the API key via a named parameter (for crappy legacy systems only)
$namedParamAuthkey = false;
if (Configure::read('Security.allow_unsafe_apikey_named_param') && !empty($this->params['named']['apikey'])) {
$namedParamAuthkey = $this->params['named']['apikey'];
}
// Authenticate user with authkey in Authorization HTTP header
if (!empty($_SERVER['HTTP_AUTHORIZATION']) || !empty($namedParamAuthkey)) {
$foundMispAuthKey = false;
$authentication = explode(',', $_SERVER['HTTP_AUTHORIZATION']);
if (!empty($namedParamAuthkey)) {
$authentication[] = $namedParamAuthkey;
}
$user = false;
foreach ($authentication as $authKey) {
$authKey = trim($authKey);
if (preg_match('/^[a-zA-Z0-9]{40}$/', $authKey)) {
$foundMispAuthKey = true;
$temp = $this->checkAuthUser($authKey);
if ($temp) {
$user = $temp;
break;
}
}
}
if ($foundMispAuthKey) {
$authKeyToStore = substr($authKey, 0, 4)
. str_repeat('*', 32)
. substr($authKey, -4);
if ($user) {
unset($user['gpgkey']);
unset($user['certif_public']);
// User found in the db, add the user info to the session
if (Configure::read('MISP.log_auth')) {
$this->loadModel('Log');
$this->Log->create();
$log = array(
'org' => $user['Organisation']['name'],
'model' => 'User',
'model_id' => $user['id'],
'email' => $user['email'],
'action' => 'auth',
'title' => "Successful authentication using API key ($authKeyToStore)",
'change' => 'HTTP method: ' . $_SERVER['REQUEST_METHOD'] . PHP_EOL . 'Target: ' . $this->here,
);
$this->Log->save($log);
}
// Allow to log key usage
if (isset($user['authkey_id']) && Configure::read('MISP.log_auth_redis')) {
/** @var Redis $redis */
$redis = $this->{$this->modelClass}->setupRedis();
if ($redis) {
$key = "misp:authkey:{$user['authkey_id']}";
$hashKey = date("Y-m-d") . ":{$_SERVER['REMOTE_ADDR']}";
$redis->hIncrBy($key, $hashKey, 1);
}
}
$this->Session->renew();
$this->Session->write(AuthComponent::$sessionKey, $user);
$this->isApiAuthed = true;
return true;
} else {
// User not authenticated correctly
// reset the session information
$redis = $this->{$this->modelClass}->setupRedis();
// Do not log every fail, but just once per hour
if ($redis && !$redis->exists('misp:auth_fail_throttling:' . $authKeyToStore)) {
$redis->setex('misp:auth_fail_throttling:' . $authKeyToStore, 3600, 1);
$this->loadModel('Log');
$this->Log->create();
$log = array(
'org' => 'SYSTEM',
'model' => 'User',
'model_id' => 0,
'email' => 'SYSTEM',
'action' => 'auth_fail',
'title' => "Failed authentication using API key ($authKeyToStore)",
'change' => null,
);
$this->Log->save($log);
}
$this->Session->destroy();
}
}
return false;
}
return null;
}
/**
* Check if:
* - user exists in database

View File

@ -57,7 +57,7 @@ class AuthKey extends AppModel
$end = substr($authkey, -4);
$existing_authkeys = $this->find('all', [
'recursive' => -1,
'fields' => ['authkey', 'user_id'],
'fields' => ['id', 'authkey', 'user_id'],
'conditions' => [
'OR' => [
'expiration >' => time(),
@ -70,7 +70,11 @@ class AuthKey extends AppModel
$passwordHasher = $this->getHasher();
foreach ($existing_authkeys as $existing_authkey) {
if ($passwordHasher->check($authkey, $existing_authkey['AuthKey']['authkey'])) {
return $this->User->getAuthUser($existing_authkey['AuthKey']['user_id']);
$user = $this->User->getAuthUser($existing_authkey['AuthKey']['user_id']);
if ($user) {
$user['authkey_id'] = $existing_authkey['AuthKey']['id'];
}
return $user;
}
}
return false;