Merge branch 'hotfix-2.3.64' into develop

pull/516/head
iglocska 2015-05-13 17:24:21 +02:00
commit 35477b919d
17 changed files with 438 additions and 14 deletions

View File

@ -191,7 +191,6 @@ chmod 700 /var/www/MISP/.gnupg
# can't connect to `/var/www/MISP/.gnupg/S.gpg-agent': No such file or directory
gpg --homedir /var/www/MISP/.gnupg --gen-key
chown -R apache:apache /var/www/MISP/.gnupg
# Recommended key type: RSA
# The email address should match the one set in the config.php configuration file
# Make sure that you use the same settings in the MISP Server Settings tool (Described on line 212)

View File

@ -205,7 +205,7 @@ chmod 700 /var/www/MISP/.gnupg
# can't connect to `/var/www/MISP/.gnupg/S.gpg-agent': No such file or directory
gpg --homedir /var/www/MISP/.gnupg --gen-key
chown -R apache:apache /var/www/MISP/.gnupg
# Recommended key type: RSA
# The email address should match the one set in the config.php configuration file
# Make sure that you use the same settings in the MISP Server Settings tool (Described on line 226)

View File

@ -151,6 +151,7 @@ cp -a config.default.php config.php
# require_once dirname(__DIR__) . '/Vendor/autoload.php';
# Important! Change the salt key in /var/www/MISP/app/Config/config.php
# The salt key must be an at least 32 byte long string.
# The admin user account will be generated on the first login, make sure that the salt is changed before you create that user
# If you forget to do this step, and you are still dealing with a fresh installation, just alter the salt,
# delete the user from mysql and log in again using the default admin credentials (admin@admin.test / admin)
@ -164,7 +165,7 @@ mkdir /var/www/MISP/.gnupg
chown www-data:www-data /var/www/MISP/.gnupg
chmod 700 /var/www/MISP/.gnupg
sudo -u www-data gpg --homedir /var/www/MISP/.gnupg --gen-key
# Recommended key type: RSA
# The email address should match the one set in the config.php configuration file
# Make sure that you use the same settings in the MISP Server Settings tool (Described on line 184)

View File

@ -7,7 +7,7 @@ git pull
# 2. Update CakePHP to the latest 2.6 code
cd /var/www/MISP/Lib/cakephp
cd /var/www/MISP/app/Lib/cakephp
git fetch origin
git checkout 2.6

View File

@ -1 +1 @@
{"major":2, "minor":3, "hotfix":63}
{"major":2, "minor":3, "hotfix":64}

View File

@ -108,6 +108,12 @@ CakePlugin::load('Assets'); // having Logable
CakePlugin::load('SysLogLogable');
CakePlugin::load('UrlCache');
/**
* Uncomment the following line to enable client SSL certificate authentication.
* It's also necessary to configure the plugin for more information, please read app/Plugin/CertAuth/reame.md
*/
// CakePlugin::load('CertAuth');
/**
* You can attach event listeners to the request lifecyle as Dispatcher Filter . By Default CakePHP bundles two filters:
*

View File

@ -6,6 +6,7 @@ $config = array (
'level' => 'medium',
'salt' => 'Rooraenietu8Eeyo<Qu2eeNfterd-dd+',
'cipherSeed' => '',
//'auth'=>array('CertAuth.Certificate'), // additional authentication methods
),
'MISP' =>
array (
@ -48,4 +49,33 @@ $config = array (
'amount' => 5,
'expire' => 300,
),
// Uncomment the following to enable client SSL certificate authentication
/*
'CertAuth' =>
array(
'ca' => array( 'FIRST.Org' ), // allowed CAs
'caId' => 'O', // which attribute will be used to verify the CA
'userModel' => 'User', // name of the User class to check if user exists
'userModelKey' => 'nids_sid', // User field that will be used for querying
'map' => array( // maps client certificate attributes to User properties
'O' => 'org',
'emailAddress'=>'email',
),
'syncUser' => true, // should the User be synchronized with an external REST API
'userDefaults'=> array( // default user attributes, only used when creating new users
'role_id' => 4,
),
'restApi' => array( // API parameters
'url' => 'https://example.com/data/users', // URL to query
'headers' => array(), // additional headers, used for authentication
'param' => array( 'email' => 'email'), // query parameters to add to the URL, mapped to USer properties
'map' => array( // maps REST result to the User properties
'uid' => 'nids_sid',
'team' => 'org',
'email' => 'email',
'pgp_public'=> 'gpgkey',
),
),
),
*/
);

View File

@ -98,6 +98,7 @@ class AppController extends Controller {
}
}
if ($user) {
unset($user['User']['gpgkey']);
// User found in the db, add the user info to the session
$this->Session->renew();
$this->Session->write(AuthComponent::$sessionKey, $user['User']);
@ -107,14 +108,33 @@ class AppController extends Controller {
$this->Session->destroy();
throw new ForbiddenException('The authentication key provided cannot be used for syncing.');
}
unset($user);
}
} else if(!$this->Session->read(AuthComponent::$sessionKey)) {
// load authentication plugins from Configure::read('Security.auth')
$auth = Configure::read('Security.auth');
if($auth) {
$this->Auth->authenticate = array_merge($auth, $this->Auth->authenticate);
if($this->Auth->startup($this)) {
$user = $this->Auth->user();
if ($user) {
unset($user['gpgkey']);
// User found in the db, add the user info to the session
$this->Session->renew();
$this->Session->write(AuthComponent::$sessionKey, $user);
}
unset($user);
}
}
unset($auth);
}
// user must accept terms
//
if ($this->Session->check('Auth.User') && !$this->Auth->user('termsaccepted') && (!in_array($this->request->here, array('/users/terms', '/users/logout', '/users/login')))) {
if ($this->Session->check(AuthComponent::$sessionKey) && !$this->Auth->user('termsaccepted') && (!in_array($this->request->here, array('/users/terms', '/users/logout', '/users/login')))) {
$this->redirect(array('controller' => 'users', 'action' => 'terms', 'admin' => false));
}
if ($this->Session->check('Auth.User') && $this->Auth->user('change_pw') && (!in_array($this->request->here, array('/users/terms', '/users/change_pw', '/users/logout', '/users/login')))) {
if ($this->Session->check(AuthComponent::$sessionKey) && $this->Auth->user('change_pw') && (!in_array($this->request->here, array('/users/terms', '/users/change_pw', '/users/logout', '/users/login')))) {
$this->redirect(array('controller' => 'users', 'action' => 'change_pw', 'admin' => false));
}

View File

@ -1495,11 +1495,6 @@ class AttributesController extends AppController {
// ! - you can negate a search term. For example: google.com&&!mail would search for all attributes with value google.com but not ones that include mail. www.google.com would get returned, mail.google.com wouldn't.
public function restSearch($key='download', $value=false, $type=false, $category=false, $org=false, $tags=false, $from=false, $to=false) {
if ($tags) $tags = str_replace(';', ':', $tags);
if ($tags === 'null') $tags = null;
if ($value === 'null') $value = null;
if ($type === 'null') $type = null;
if ($category === 'null') $category = null;
if ($org === 'null') $org = null;
if ($key!=null && $key!='download') {
$user = $this->checkAuthUser($key);
} else {
@ -1530,6 +1525,13 @@ class AttributesController extends AppController {
else ${$p} = null;
}
}
$simpleFalse = array('value' , 'type', 'category', 'org', 'tags', 'from', 'to');
foreach ($simpleFalse as $sF) {
if (${$sF} === 'null' || ${$sF} == '0' || ${$sF} === false || strtolower(${$sF}) === 'false') ${$sF} = false;
}
if ($from) $from = $this->Attribute->Event->dateFieldCheck($from);
if ($to) $from = $this->Attribute->Event->dateFieldCheck($to);
if (!isset($this->request->params['ext']) || $this->request->params['ext'] !== 'json') {
$this->response->type('xml'); // set the content type
$this->layout = 'xml/default';
@ -1768,8 +1770,8 @@ class AttributesController extends AppController {
if (${$sF} === 'null' || ${$sF} == '0' || ${$sF} === false || strtolower(${$sF}) === 'false') ${$sF} = false;
}
if ($type === 'null' || $type === '0' || $type === 'false') $type = 'all';
if ($from && !preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/', $from)) $from = false;
if ($to && !preg_match('/^[0-9]{4}-[l0-9]{2}-[0-9]{2}$/', $from)) $from = false;
if ($from) $from = $this->Attribute->Event->dateFieldCheck($from);
if ($to) $from = $this->Attribute->Event->dateFieldCheck($to);
if ($key != 'download') {
// check if the key is valid -> search for users based on key
$user = $this->checkAuthUser($key);

View File

@ -1725,6 +1725,8 @@ class EventsController extends AppController {
foreach ($simpleFalse as $sF) {
if (${$sF} === 'null' || ${$sF} == '0' || ${$sF} === false || strtolower(${$sF}) === 'false') ${$sF} = false;
}
if ($from) $from = $this->Event->dateFieldCheck($from);
if ($to) $from = $this->Event->dateFieldCheck($to);
if ($tags) $tags = str_replace(';', ':', $tags);
$eventIdArray = array();
@ -1802,6 +1804,9 @@ class EventsController extends AppController {
foreach ($simpleFalse as $sF) {
if (${$sF} === 'null' || ${$sF} == '0' || ${$sF} === false || strtolower(${$sF}) === 'false') ${$sF} = false;
}
if ($from) $from = $this->Event->dateFieldCheck($from);
if ($to) $from = $this->Event->dateFieldCheck($to);
if ($tags) $tags = str_replace(';', ':', $tags);
// backwards compatibility, swap key and format
if ($format != 'snort' && $format != 'suricata') {
@ -1837,6 +1842,9 @@ class EventsController extends AppController {
foreach ($simpleFalse as $sF) {
if (${$sF} === 'null' || ${$sF} == '0' || ${$sF} === false || strtolower(${$sF}) === 'false') ${$sF} = false;
}
if ($from) $from = $this->Event->dateFieldCheck($from);
if ($to) $from = $this->Event->dateFieldCheck($to);
if ($tags) $tags = str_replace(';', ':', $tags);
$this->response->type('txt'); // set the content type
$this->header('Content-Disposition: download; filename="misp.' . $type . '.rules"');
@ -1869,6 +1877,9 @@ class EventsController extends AppController {
foreach ($simpleFalse as $sF) {
if (${$sF} === 'null' || ${$sF} == '0' || ${$sF} === false || strtolower(${$sF}) === 'false') ${$sF} = false;
}
if ($from) $from = $this->Event->dateFieldCheck($from);
if ($to) $from = $this->Event->dateFieldCheck($to);
if ($tags) $tags = str_replace(';', ':', $tags);
$list = array();
if ($key != 'download') {
@ -2399,6 +2410,9 @@ class EventsController extends AppController {
foreach ($simpleFalse as $sF) {
if (${$sF} === 'null' || ${$sF} == '0' || ${$sF} === false || strtolower(${$sF}) === 'false') ${$sF} = false;
}
if ($from) $from = $this->Event->dateFieldCheck($from);
if ($to) $from = $this->Event->dateFieldCheck($to);
if ($tags) $tags = str_replace(';', ':', $tags);
if ($searchall === 'true') $searchall = "1";
@ -2974,6 +2988,8 @@ class EventsController extends AppController {
foreach ($simpleFalse as $sF) {
if (${$sF} === 'null' || ${$sF} == '0' || ${$sF} === false || strtolower(${$sF}) === 'false') ${$sF} = false;
}
if ($from) $from = $this->Event->dateFieldCheck($from);
if ($to) $from = $this->Event->dateFieldCheck($to);
// set null if a null string is passed
$numeric = false;

View File

@ -1916,4 +1916,12 @@ class Event extends AppModel {
}
return $fn;
}
// expects a date string in the DD-MM-YYYY format
// returns the passed string or false if the format is invalid
// based on the fix provided by stevengoosensB
public function dateFieldCheck($date) {
// regex check for from / to field by stevengoossensB
return (preg_match('/^[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])$/', $date)) ? $date : false;
}
}

View File

@ -0,0 +1,273 @@
<?php
/**
* Client SSL Certificate Authentication component
*
* Authorizes users based on their SSL credentials.
*
* Copyright (c) FIRST.Org, Inc. (https://first.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author Guilherme Capilé, Tecnodesign (https://tecnodz.com)
* @copyright Copyright (c) FIRST.Org, Inc. (https://first.org)
* @link http://github.com/FIRSTdotorg/cakephp-CertAuth
* @package CertAuth.Certificate
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
App::uses('AuthComponent', 'Controller/Component');
App::uses('BaseAuthenticate', 'Controller/Component/Auth');
class CertificateAuthenticate extends BaseAuthenticate
{
/**
* Holds the certificate issuer information (available at SSL_CLIENT_I_DN)
*
* @var array
*/
protected static $ca;
/**
* Holds the certificate user information (available at SSL_CLIENT_S_DN)
*
* @var array
*/
protected static $client;
/**
* Holds the user information
*
* @var array
*/
protected static $user;
/**
* Class constructor.
*
* This should only be called once per request, so it doesn't need to store values in
* the instance. Simply checks if the certificate is valid (against configured valid issuers)
* and returns the user information encoded.
*/
public function __construct()
{
self::$ca = self::$client = false;
// this means the client certificate is valid — check nginx/apache configuration
if(isset($_SERVER['SSL_CLIENT_VERIFY']) && $_SERVER['SSL_CLIENT_VERIFY']=='SUCCESS') {
if(isset($_SERVER['SSL_CLIENT_I_DN'])) {
$CA = self::parse($_SERVER['SSL_CLIENT_I_DN'], Configure::read('CertAuth.mapCa'));
// only valid CAs, if this was configured
if($ca=Configure::read('CertAuth.ca')) {
$k = Configure::read('CertAuth.caId');
if(!$k) $k = 'CN';
$id = (isset($CA[$k]))?($CA[$k]):(false);
if(!$id) {
$CA = false;
} else if(is_array($ca)) {
if(!in_array($id, $ca)) $CA = false;
} else if($ca!=$id) {
$CA = false;
}
unset($id, $k);
}
self::$ca = $CA;
unset($CA, $ca);
}
if(self::$ca && isset($_SERVER['SSL_CLIENT_S_DN'])) {
self::$client = self::parse($_SERVER['SSL_CLIENT_S_DN'], Configure::read('CertAuth.map'));
}
}
}
/**
* Parse certificate extensions
*
* @TODO this should properly address the RFC
* @param string $s text to be parsed
* @param (optional) array $map array of mapping extension to User fields
* @return array parsed values
*/
private static function parse($s, $map=null)
{
$r=array();
foreach(preg_split('#/#', $s, null, PREG_SPLIT_NO_EMPTY) as $v) {
if($p=strpos($v, '=')) {
$k = substr($v, 0, $p);
if($map) {
if(isset($map[$k])) {
$k = $map[$k];
} else {
unset($p, $v, $k);
continue;
}
}
$r[$k] = substr($v, $p+1);
}
unset($p, $v, $k);
}
return $r;
}
// to enable stateless authentication
public function getUser(CakeRequest $request)
{
if(is_null(self::$user)) {
if(self::$client) {
self::$user = self::$client;
$sync = Configure::read('CertAuth.syncUser');
if($sync) {
self::getRestUser();
}
// find and fill user with model
$cn = Configure::read('CertAuth.userModel');
if($cn) {
$k = Configure::read('CertAuth.userModelKey');
if($k) {
$q = array($k=>self::$user[$k]);
} else {
$q = self::$user;
}
$User = ClassRegistry::init($cn);
$U = $User->find('first', array(
'conditions' => $q,
'recursive' => false
));
if($U) {
if($sync) {
$write = array();
foreach(self::$user as $k=>$v) {
if(array_key_exists($k, $U[$cn]) && trim($U[$cn][$k])!=trim($v)) {
$write[] = $k;
$U[$cn][$k] = trim($v);
}
unset($k, $v);
}
if($write && !$User->save($U[$cn], true, $write)) {
CakeLog::write('alert', 'Could not update model at database with RestAPI data.');
}
unset($write);
}
self::$user = $U[$cn];
} else if($sync) {
$User->create();
$d = Configure::read('CertAuth.userDefaults');
if($d && is_array($d)) {
self::$user += $d;
}
unset($d);
if($User->save(self::$user, true, array_keys(self::$user))) {
$U = $User->read();
self::$user = $U[$cn];
} else {
CakeLog::write('alert', 'Could not insert model at database from RestAPI data.');
}
}
unset($U, $User, $q, $k);
}
unset($cn);
}
}
return self::$user;
}
// to enable stateless authentication
public function authenticate(CakeRequest $request, CakeResponse $response)
{
return self::getUser($request);
}
/**
* Fetches user information from external REST API
*
* Valid options (should be configured under CertAuth.restApi):
*
* @param (optional) array $options API configuration
* url (string) Where to fetch information from
* headers (array) list of additional headers to be used, reserved for authentication tokens
* params (array) mapping of additional params to be included at the url, uses $user values
* map (array) mapping of the return values to be added to the self::$user
* @return array updated user object
*/
public function getRestUser($options=null, $user=null)
{
if(is_null($options)) {
$options = Configure::read('CertAuth.restApi');
}
if(!is_null($user)) {
self::$user = $user;
}
if(!isset($options['url'])) {
return null;
}
// Create a stream
$req = array(
'http'=>array(
'method'=>'GET',
'header'=>"Accept: application/json\r\n"
),
);
if(isset($options['headers'])) {
foreach($options['headers'] as $k=>$v) {
if(is_int($k)) {
$req['header'] .= "{$v}\r\n";
} else {
$req['header'] .= "{$k}: {$v}\r\n";
}
unset($k, $v);
}
}
$url = $options['url'];
if(isset($options['param'])) {
foreach($options['param'] as $k=>$v) {
if(isset(self::$user[$v])) {
$url .= ((strpos($url, '?'))?('&'):('?'))
. $k . '=' . urlencode(self::$user[$v]);
}
unset($k, $v);
}
}
$ctx = stream_context_create($req);
$a = file_get_contents($url, false, $ctx);
if(!$a) return null;
$A = json_decode($a, true);
if(!isset($A['data'][0])) return false;
if(isset($options['map'])) {
foreach($options['map'] as $k=>$v) {
if(isset($A['data'][0][$k])) {
self::$user[$v] = $A['data'][0][$k];
}
unset($k, $v);
}
}
return self::$user;
}
protected static $instance;
public static function ca()
{
if(is_null(self::$ca)) new CertificateAuthenticate();
return self::$ca;
}
public static function client()
{
if(is_null(self::$client)) new CertificateAuthenticate();
return self::$client;
}
}

View File

@ -0,0 +1,45 @@
#Client SSL Certificate Authentication for CakePHP
This plugin enables CakePHP applications to use client SSL certificates to stateless authenticate its users. It reads information from the client certificate and can synchronize data with a foreign REST API and the client User model.
Basically it loads the `SSL_CLIENT_*` variables, parses and maps the certificate information to the user. So you first need a server that checks client certificates and forwards that information to the PHP `$_SERVER` environment.
## Usage
Enable the plugin at bootstrap.php:
```php
CakePlugin::load('CertAuth');
```
And configure it:
```php
Configure::write('CertAuth',
array(
'ca' => array( 'FIRST.Org' ), // allowed CAs
'caId' => 'O', // which attribute will be used to verify the CA
'userModel' => 'User', // name of the User class to check if user exists
'userModelKey' => 'nids_sid', // User field that will be used for querying
'map' => array( // maps client certificate attributes to User properties
'O' => 'org',
'emailAddress'=>'email',
),
'syncUser' => true, // should the User be synchronized with an external REST API
'restApi' => array( // API parameters
'url' => 'https://example.com/data/users', // URL to query
'headers' => array(), // additional headers, used for authentication
'param' => array( 'email' => 'email'), // query parameters to add to the URL, mapped to USer properties
'map' => array( // maps REST result to the User properties
'uid' => 'id',
'name' => 'name',
'company' => 'org',
'email' => 'email',
),
),
),
));
```

View File

@ -13,6 +13,9 @@
echo $this->Form->input('authkey', array('value' => $authkey, 'readonly' => 'readonly'));
echo $this->Form->input('nids_sid');
echo $this->Form->input('gpgkey', array('label' => 'GPG key', 'div' => 'clear', 'class' => 'input-xxlarge'));
?>
<div class="clear"><span onClick="lookupPGPKey('UserEmail');" class="btn btn-inverse" style="margin-bottom:10px;">Fetch GPG key</span></div>
<?php
echo $this->Form->input('autoalert', array('label' => 'Receive alerts when events are published'));
echo $this->Form->input('contactalert', array('label' => 'Receive alerts from "contact reporter" requests'));

View File

@ -18,6 +18,9 @@
'class' => 'datepicker',
));
echo $this->Form->input('gpgkey', array('label' => 'GPG key', 'div' => 'clear', 'class' => 'input-xxlarge'));
?>
<div class="clear"><span onClick="lookupPGPKey('UserEmail');" class="btn btn-inverse" style="margin-bottom:10px;">Fetch GPG key</span></div>
<?php
echo $this->Form->input('termsaccepted', array('label' => 'Terms accepted'));
echo $this->Form->input('change_pw', array('type' => 'checkbox', 'label' => 'Change Password'));
echo $this->Form->input('autoalert', array('label' => 'Receive alerts when events are published'));

View File

@ -12,6 +12,9 @@
else echo $this->Form->input('role_id', array('disabled' => 'disabled')); // TODO ACL, check, My Profile not edit role_id.
echo $this->Form->input('nids_sid');
echo $this->Form->input('gpgkey', array('label' => 'GPG key', 'div' => 'clear', 'class' => 'input-xxlarge'));
?>
<div class="clear"><span onClick="lookupPGPKey('UserEmail');" class="btn btn-inverse" style="margin-bottom:10px;">Fetch GPG key</span></div>
<?php
echo $this->Form->input('autoalert', array('label' => 'Receive alerts when events are published'));
echo $this->Form->input('contactalert', array('label' => 'Receive alerts from "contact reporter" requests'));
?>

View File

@ -1350,3 +1350,18 @@ function freetextImportResultsSubmit(id, count) {
},
});
}
function lookupPGPKey(emailFieldName) {
$.ajax({
type: "get",
url: "https://pgp.mit.edu/pks/lookup?op=get&search=" + $('#' + emailFieldName).val(),
success: function (data) {
var result = $("<div>").html(data)[0].getElementsByTagName("pre")[0]['innerText'];
$("#UserGpgkey").val(result);
showMessage('success', "Key found!");
},
error: function (data, textStatus, errorThrown) {
showMessage('fail', textStatus + ": " + errorThrown);
}
});
}