CVE-2020-8894 (PHP, published 2020-02-12T00:15Z)
Description: value: An issue was discovered in MISP before 2.4.121. ACLs for discussion threads were mishandled in app/Controller/ThreadsController.php and app/Model/Thread.php.
Severity: MEDIUM (CVSS v3 base score: 6.5)
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/MISP/MISP
Files changed:
app/Controller/ThreadsController.php
app/Model/Thread.php
Commit message: fix: [security] discussion thread ACL issues fixed

- as reported by Dawid Czarnecki


Vulnerable code:
            $id = $thread_id;
            // Show the discussion
            $this->Thread->Behaviors->unload('SysLogLogable.SysLogLogable');
            $params = array('conditions' => array('event_id' => $id),
                    'recursive' => -1,
                    'fields' => array('id', 'event_id', 'distribution', 'title', 'sharing_group_id', 'org_id')
            );
            $thread = $this->Thread->find('first', $params);
            if (!empty($thread)) {
                if (!$this->_isSiteAdmin()) {
                    if ($thread['Thread']['distribution'] == 0 && $thread['Thread']['org_id'] != $this->Auth->user('org_id')) {
                        throw new MethodNotAllowedException('Invalid Thread.');
                    }
                    if ($thread['Thread']['distribution'] == 4) {
                        if (!$this->Thread->SharingGroup->checkIfAuthorised($this->Auth->user(), $thread['Thread']['sharing_group_id'])) {
                            new NotFoundException('Invalid thread.');
                        }
                    }
                }
                $thread_id = $thread['Thread']['id'];
            } else {
                $thread_id = 0;
            $this->set('currentEvent', $id);
            $this->Thread->recursive = -1;
            $this->Thread->id = $thread_id;

            //If the thread doesn't exist, throw exception
            if (!$this->Thread->exists()) {
            $thread = $this->Thread->read();

            // If the thread belongs to an event, we have to make sure that the event's distribution level hasn't changed.
            // This is also a good time to update the thread's distribution level if that did happen.
            if (!empty($thread['Thread']['event_id'])) {
                $this->loadModel('Event');
                $this->Event->id = $thread['Thread']['event_id'];
                $this->Event->recursive = -1;
                $this->Event->read(array('id', 'distribution', 'org_id', 'sharing_group_id'));
                if ($this->Event->data['Event']['distribution'] != $thread['Thread']['distribution']) {
                    $this->Thread->saveField('distribution', $this->Event->data['Event']['distribution']);
                }
                if ($this->Event->data['Event']['sharing_group_id'] != $thread['Thread']['sharing_group_id']) {
                    $this->Thread->saveField('sharing_group_id', $this->Event->data['Event']['sharing_group_id']);
                }
                $this->set('event_id', $thread['Thread']['event_id']);
            }

            // If the user shouldn't be allowed to see the event send him away.
            if (!$this->_isSiteAdmin()) {
                if ($thread['Thread']['distribution'] == 0 && $thread['Thread']['org_id'] != $this->Auth->user('org_id')) {
                    throw new MethodNotAllowedException('Invalid Thread.');
                }
                if ($thread['Thread']['distribution'] == 4) {
                    if (!$this->Thread->SharingGroup->checkIfAuthorised($this->Auth->user(), $thread['Thread']['sharing_group_id'])) {
                        new NotFoundException('Invalid thread.');
                    }
                }
        if ($eventView) {
            $this->set('context', 'event');
            if (!$this->request->is('ajax')) {
                $this->redirect(array('controller' => 'events', 'action' => 'view', $id));
            }
        } else {
            $this->set('context', 'thread');
        }
        $this->set('myuserid', $this->Auth->user('id'));
        $sgids = $this->SharingGroup->fetchAllAuthorised($this->Auth->user);
                'conditions' => array($conditions),

Fixed code:
        $conditions = array('id' => $thread_id);
            $event_id = $thread_id;
            if (!$this->request->is('ajax')) {
                $this->redirect(array('controller' => 'events', 'action' => 'view', $event_id));
            $conditions = array('event_id' => $event_id);
            $this->set('currentEvent', $event_id);
            $this->set('event_id', $event_id);
            $this->set('context', 'event');
            $this->set('context', 'thread');
        }
        $this->set('myuserid', $this->Auth->user('id'));
        $thread = $this->Thread->find('first', array(
            'conditions' => $conditions,
            'recursive' => -1
        ));
        if (empty($thread)) {
            if (!$eventView) {
        } else {
            $thread_id = $thread['Thread']['id'];
            if (!$this->Thread->checkIfAuthorised($this->Auth->user(), $thread_id)) {
                throw new NotFoundException('Invalid thread.');
        $sgids = $this->SharingGroup->fetchAllAuthorised($this->Auth->user());
                'conditions' => $conditions,

    // convenience method to check whether a user can see an event
    public function checkIfAuthorised($user, $id)
    {
        if (!isset($user['id'])) {
            throw new MethodNotAllowedException('Invalid user.');
        }
        $this->id = $id;
        if (!$this->exists()) {
            return false;
        }
        $thread = $this->find('first', array(
            'conditions' => array('id' => $id),
            'recursive' => -1
        ));
        if (!empty($thread['Thread']['event_id'])) {
            $event = $this->Event->fetchEvent($user, array(
                'eventid' => $thread['Thread']['event_id'],
                'metadata' => true
            ));
            if (empty($event)) {
                return false;
            }
            $event = $event[0];
            // update the distribution if it diverged from the event
            if (
                $event['Event']['distribution'] != $thread['Thread']['distribution'] ||
                $event['Event']['sharing_group_id'] != $thread['Thread']['sharing_group_id']
            ) {
                $this->Behaviors->unload('SysLogLogable.SysLogLogable');
                $thread['Thread']['distribution'] = $event['Event']['distribution'];
                $thread['Thread']['sharing_group_id'] = $event['Event']['sharing_group_id'];
                $this->save($thread);
            }
            return !empty($event);
        }
        if ($user['Role']['perm_site_admin']) {
            return true;
        }
        if ($thread['Thread']['org_id'] == $user['org_id'] || ($thread['Thread']['distribution'] > 0 && $thread['Thread']['distribution'] < 4)) {
            return true;
        }
        if ($thread['Thread']['distribution'] == 4 && $this->SharingGroup->checkIfAuthorised($user, $thread['Thread']['sharing_group_id'])) {
            return true;
        }
        return false;
    }