Merge branch '2.4' of github.com:SteveClement/MISP into 2.4

pull/7282/head^2
Steve Clement 2021-04-01 15:42:37 +09:00
commit 98934a0a8d
No known key found for this signature in database
GPG Key ID: 69A20F509BE4AEE9
344 changed files with 66722 additions and 55626 deletions

View File

@ -20,7 +20,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
os: [ubuntu-20.04]
php: ['7.2', '7.3', '7.4']
# Steps represent a sequence of tasks that will be executed as part of the job
@ -66,9 +66,12 @@ jobs:
run: |
git submodule update --init --recursive
sudo apt-get -y update
sudo apt-get -y install curl python3 python3-venv virtualenv python3-pip python3-nose python3-redis python3-lxml apache2 libapache2-mod-php$php_version php$php_version-xml php$php_version-mbstring php$php_version-mysql libfuzzy-dev libemail-address-perl libemail-outlook-message-perl
sudo pip3 install --upgrade pip setuptools requests pyzmq poetry
# Repo is missing for unknown reason
LC_ALL=C.UTF-8 sudo apt-add-repository ppa:ondrej/php -y
sudo apt-get -y install curl python3 python3-zmq python3-requests python3-pip python3-nose python3-redis python3-lxml apache2 libapache2-mod-php$php_version libfuzzy-dev
sudo pip3 install virtualenv # virtualenv must be instaled from pip and not from ubuntu pacckages
sudo pip3 install --upgrade -r requirements.txt
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
sudo chown $USER:www-data $HOME/.composer
pushd app
sudo -H -u $USER php composer.phar install --no-progress
@ -185,7 +188,7 @@ jobs:
sudo chmod -R 777 ./tests
# Start workers
# Dirty install python stuff
virtualenv -p python3 ./venv
python3 -m virtualenv -p python3 ./venv
sudo -E su $USER -c 'app/Console/cake Admin setSetting "MISP.python_bin" "$GITHUB_WORKSPACE/venv/bin/python"'
. ./venv/bin/activate
pushd cti-python-stix2
@ -209,9 +212,11 @@ jobs:
- name: Run tests
run: |
source $HOME/.poetry/env # enable poetry binary
./app/Vendor/bin/parallel-lint --exclude app/Lib/cakephp/ --exclude app/Vendor/ --exclude app/Lib/random_compat/ -e php,ctp app/
./app/Vendor/bin/phpunit app/Test/ComplexTypeToolTest.php
./app/Vendor/bin/phpunit app/Test/JSONConverterToolTest.php
./app/Vendor/bin/phpunit app/Test/CidrToolTest.php
# Ensure the perms of config files
sudo chown -R $USER:www-data `pwd`/app/Config

View File

@ -150,6 +150,8 @@ MISPvars () {
post_max_size="50M"
max_execution_time="300"
memory_limit="2048M"
session0sid_length="32"
session0use_strict_mode="1"
CAKE="${PATH_TO_MISP}/app/Console/cake"
@ -836,6 +838,8 @@ installDepsPhp70 () {
do
sudo sed -i "s/^\($key\).*/\1 = $(eval echo \${$key})/" $PHP_INI
done
sudo sed -i "s/^\(session.sid_length\).*/\1 = $(eval echo \${session0sid_length})/" $PHP_INI
sudo sed -i "s/^\(session.use_strict_mode\).*/\1 = $(eval echo \${session0use_strict_mode})/" $PHP_INI
}
# Install Php 7.3 deps
@ -1208,6 +1212,8 @@ installDepsPhp74 () {
do
sudo sed -i "s/^\($key\).*/\1 = $(eval echo \${$key})/" $PHP_INI
done
sudo sed -i "s/^\(session.sid_length\).*/\1 = $(eval echo \${session0sid_length})/" $PHP_INI
sudo sed -i "s/^\(session.use_strict_mode\).*/\1 = $(eval echo \${session0use_strict_mode})/" $PHP_INI
}
# Install Php 7.3 deps
@ -1260,6 +1266,8 @@ installDepsPhp72 () {
do
sudo sed -i "s/^\($key\).*/\1 = $(eval echo \${$key})/" $PHP_INI
done
sudo sed -i "s/^\(session.sid_length\).*/\1 = $(eval echo \${session0sid_length})/" $PHP_INI
sudo sed -i "s/^\(session.use_strict_mode\).*/\1 = $(eval echo \${session0use_strict_mode})/" $PHP_INI
}
# Install Php 7.0 dependencies
@ -1280,6 +1288,8 @@ installDepsPhp70 () {
do
sudo sed -i "s/^\($key\).*/\1 = $(eval echo \${$key})/" $PHP_INI
done
sudo sed -i "s/^\(session.sid_length\).*/\1 = $(eval echo \${session0sid_length})/" $PHP_INI
sudo sed -i "s/^\(session.use_strict_mode\).*/\1 = $(eval echo \${session0use_strict_mode})/" $PHP_INI
}
prepareDB () {
@ -1536,6 +1546,7 @@ coreCAKE () {
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "GnuPG.email" "$GPG_EMAIL_ADDRESS"
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "GnuPG.homedir" "$PATH_TO_MISP/.gnupg"
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "GnuPG.password" "$GPG_PASSPHRASE"
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "GnuPG.obscure_subject" true
# FIXME: what if we have not gpg binary but a gpg2 one?
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "GnuPG.binary" "$(which gpg)"
@ -1565,6 +1576,9 @@ coreCAKE () {
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Plugin.Sightings_range" 365
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Plugin.Sightings_sighting_db_enable" false
# Plugin Enrichment hover defaults
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Plugin.Enrichment_hover_popover_only" false
# Plugin CustomAuth tuneable
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Plugin.CustomAuth_disable_logout" false
@ -1599,8 +1613,10 @@ coreCAKE () {
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.passwordResetText" "Dear MISP user,\\n\\nA password reset has been triggered for your account. Use the below provided temporary password to log into MISP at \$misp, where you will be prompted to manually change your password to something of your own choice.\\n\\nUsername: \$username\\nYour temporary password: \$password\\n\\nIf you have any questions, don't hesitate to contact us at: \$contact.\\n\\nBest regards,\\nYour \$org MISP support team"
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.enableEventBlocklisting" true
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.enableOrgBlocklisting" true
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.log_client_ip" false
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.log_client_ip" true
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.log_auth" false
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.log_user_ips" true
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.log_user_ips_authkeys" true
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.disableUserSelfManagement" false
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.disable_user_login_change" false
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.disable_user_password_change" false
@ -1633,6 +1649,16 @@ coreCAKE () {
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Security.password_policy_complexity" '/^((?=.*\d)|(?=.*\W+))(?![\n])(?=.*[A-Z])(?=.*[a-z]).*$|.{16,}/'
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Security.self_registration_message" "If you would like to send us a registration request, please fill out the form below. Make sure you fill out as much information as possible in order to ease the task of the administrators."
# Appease the security audit, #hardening
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Security.disable_browser_cache" true
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Security.check_sec_fetch_site_header" true
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Security.csp_enforce" true
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Security.advanced_authkeys" true
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Security.do_not_log_authkeys" true
# Appease the security audit, #loggin
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Security.username_in_response_header" true
# It is possible to updateMISP too, only here for reference how to to that on the CLI.
## $SUDO_WWW $RUN_PHP -- $CAKE Admin updateMISP
@ -2032,7 +2058,6 @@ enableReposRHEL () {
sudo subscription-manager refresh
sudo subscription-manager repos --enable rhel-7-server-optional-rpms
sudo subscription-manager repos --enable rhel-7-server-extras-rpms
sudo subscription-manager repos --enable rhel-server-rhscl-7-rpms
}
centosEPEL () {
@ -2046,38 +2071,52 @@ centosEPEL () {
enableEPEL () {
sudo yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm -y
sudo yum install http://rpms.remirepo.net/enterprise/remi-release-7.rpm -y
sudo yum install yum-utils -y
sudo yum-config-manager --enable remi-php74
}
yumInstallCoreDeps () {
# Install the dependencies:
sudo yum install gcc git zip rh-git218 \
httpd24 \
sudo yum install gcc git zip \
mod_ssl \
rh-redis32 \
rh-mariadb102 \
redis \
libxslt-devel zlib-devel ssdeep-devel -y
# Enable and start redis
sudo systemctl enable --now rh-redis32-redis.service
sudo systemctl enable --now redis.service
# Install MariaDB
sudo yum install wget -y
wget https://downloads.mariadb.com/MariaDB/mariadb_repo_setup
chmod +x mariadb_repo_setup
sudo ./mariadb_repo_setup
rm mariadb_repo_setup
sudo yum install MariaDB-server -y
WWW_USER="apache"
SUDO_WWW="sudo -H -u $WWW_USER"
RUN_PHP="/usr/bin/scl enable rh-php72"
PHP_INI="/etc/opt/rh/rh-php72/php.ini"
# Install PHP 7.2 from SCL, see https://www.softwarecollections.org/en/scls/rhscl/rh-php72/
sudo yum install rh-php72 rh-php72-php-fpm rh-php72-php-devel \
rh-php72-php-mysqlnd \
rh-php72-php-mbstring \
rh-php72-php-xml \
rh-php72-php-bcmath \
rh-php72-php-opcache \
rh-php72-php-zip \
rh-php72-php-gd -y
PHP_INI="/etc/opt/remi/php74/php.ini"
# Install PHP 7.4 from Remi's repo, see https://rpms.remirepo.net/enterprise/7/php74/x86_64/repoview/
sudo yum install php74 php74-php-fpm php74-php-devel \
php74-php-mysqlnd \
php74-php-mbstring \
php74-php-xml \
php74-php-bcmath \
php74-php-opcache \
php74-php-zip \
php74-php-pear \
php74-php-brotli \
php74-php-intl \
php74-php-gd -y
# cake has php baked in, thus we link to it
sudo ln -s /usr/bin/php74 /usr/bin/php
# Python 3.6 is now available in RHEL 7.7 base
sudo yum install python3 python3-devel -y
sudo systemctl enable --now rh-php72-php-fpm.service
sudo systemctl enable --now php74-php-fpm.service
}
installCoreRHEL () {
@ -2110,17 +2149,17 @@ installCoreRHEL () {
cd $PATH_TO_MISP/app/files/scripts
$SUDO_WWW git clone https://github.com/CybOXProject/python-cybox.git
$SUDO_WWW git clone https://github.com/STIXProject/python-stix.git
$SUDO_WWW git clone --branch master --single-branch https://github.com/lief-project/LIEF.git lief
##$SUDO_WWW git clone --branch master --single-branch https://github.com/lief-project/LIEF.git lief
$SUDO_WWW git clone https://github.com/CybOXProject/mixbox.git
# If you umask is has been changed from the default, it is a good idea to reset it to 0022 before installing python modules
UMASK=$(umask)
umask 0022
cd $PATH_TO_MISP/app/files/scripts/python-cybox
$SUDO_WWW git config core.filemode false
$SUDO_WWW $PATH_TO_MISP/venv/bin/pip install .
cd $PATH_TO_MISP/app/files/scripts/python-stix
$SUDO_WWW git config core.filemode false
$SUDO_WWW $PATH_TO_MISP/venv/bin/pip install .
@ -2143,40 +2182,8 @@ installCoreRHEL () {
# install redis
$SUDO_WWW $PATH_TO_MISP/venv/bin/pip install -U redis
# lief needs manual compilation
sudo yum install devtoolset-7 cmake3 cppcheck libcxx-devel -y
cd $PATH_TO_MISP/app/files/scripts/lief
$SUDO_WWW git config core.filemode false
$SUDO_WWW mkdir build
cd build
$SUDO_WWW scl enable devtoolset-7 "bash -c 'cmake3 \
-DLIEF_PYTHON_API=on \
-DPYTHON_VERSION=3.6 \
-DPYTHON_EXECUTABLE=$PATH_TO_MISP/venv/bin/python \
-DLIEF_DOC=off \
-DCMAKE_BUILD_TYPE=Release \
..'"
$SUDO_WWW make -j3 pyLIEF
if [ $? == 2 ]; then
# In case you get "internal compiler error: Killed (program cc1plus)"
# You ran out of memory.
# Create some swap
sudo dd if=/dev/zero of=/var/swap.img bs=1024k count=4000
sudo mkswap /var/swap.img
sudo swapon /var/swap.img
# And compile again
$SUDO_WWW make -j3 pyLIEF
sudo swapoff /var/swap.img
sudo rm /var/swap.img
fi
# The following adds a PYTHONPATH to where the pyLIEF module has been compiled
echo $PATH_TO_MISP/app/files/scripts/lief/build/api/python |$SUDO_WWW tee $PATH_TO_MISP/venv/lib/python3.6/site-packages/lief.pth
# install magic, pydeep
$SUDO_WWW $PATH_TO_MISP/venv/bin/pip install -U python-magic git+https://github.com/kbandla/pydeep.git plyara
# install magic, pydeep, lief
$SUDO_WWW $PATH_TO_MISP/venv/bin/pip install -U python-magic git+https://github.com/kbandla/pydeep.git plyara lief
# install PyMISP
cd $PATH_TO_MISP/PyMISP
@ -2184,7 +2191,7 @@ installCoreRHEL () {
# FIXME: Remove libfaup etc once the egg has the library baked-in
# BROKEN: This needs to be tested on RHEL/CentOS
##sudo apt-get install cmake libcaca-dev liblua5.3-dev -y
sudo yum install libcaca-devel cmake3 -y
cd /tmp
[[ ! -d "faup" ]] && $SUDO_CMD git clone https://github.com/stricaud/faup.git faup
[[ ! -d "gtcaca" ]] && $SUDO_CMD git clone https://github.com/stricaud/gtcaca.git gtcaca
@ -2192,21 +2199,20 @@ installCoreRHEL () {
cd gtcaca
$SUDO_CMD mkdir -p build
cd build
$SUDO_CMD cmake .. && $SUDO_CMD make
$SUDO_CMD cmake3 .. && $SUDO_CMD make
sudo make install
cd ../../faup
$SUDO_CMD mkdir -p build
cd build
$SUDO_CMD cmake .. && $SUDO_CMD make
$SUDO_CMD cmake3 .. && $SUDO_CMD make
sudo make install
sudo ldconfig
# Enable dependencies detection in the diagnostics page
# This allows MISP to detect GnuPG, the Python modules' versions and to read the PHP settings.
# The LD_LIBRARY_PATH setting is needed for rh-git218 to work
echo "env[PATH] = /opt/rh/rh-git218/root/usr/bin:/opt/rh/rh-redis32/root/usr/bin:/opt/rh/rh-php72/root/usr/bin:/usr/local/bin:/usr/bin:/bin" |sudo tee -a /etc/opt/rh/rh-php72/php-fpm.d/www.conf
sudo sed -i.org -e 's/^;\(clear_env = no\)/\1/' /etc/opt/rh/rh-php72/php-fpm.d/www.conf
sudo systemctl restart rh-php72-php-fpm.service
echo "env[PATH] = /usr/local/bin:/usr/bin:/bin" |sudo tee -a /etc/opt/remi/php74/php-fpm.d/www.conf
sudo sed -i.org -e 's/^;\(clear_env = no\)/\1/' /etc/opt/remi/php74/php-fpm.d/www.conf
sudo systemctl restart php74-php-fpm.service
umask $UMASK
}
@ -2218,31 +2224,20 @@ installCake_RHEL ()
cd $PATH_TO_MISP/app
# Update composer.phar (optional)
#EXPECTED_SIGNATURE="$(wget -q -O - https://composer.github.io/installer.sig)"
#$SUDO_WWW $RUN_PHP -- php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
#$SUDO_WWW $RUN_PHP -- php -r "if (hash_file('SHA384', 'composer-setup.php') === '$EXPECTED_SIGNATURE') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
#$SUDO_WWW $RUN_PHP "php composer-setup.php"
#$SUDO_WWW $RUN_PHP -- php -r "unlink('composer-setup.php');"
$SUDO_WWW $RUN_PHP "php composer.phar install"
#$SUDO_WWW php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
#$SUDO_WWW php -r "if (hash_file('SHA384', 'composer-setup.php') === '$EXPECTED_SIGNATURE') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
#$SUDO_WWW php composer-setup.php
#$SUDO_WWW php -r "unlink('composer-setup.php');"
$SUDO_WWW php composer.phar install
## sudo yum install php-redis -y
sudo scl enable rh-php72 'pecl channel-update pecl.php.net'
sudo scl enable rh-php72 'yes no|pecl install redis'
echo "extension=redis.so" |sudo tee /etc/opt/rh/rh-php72/php.d/99-redis.ini
sudo yum install php74-php-pecl-redis php74-php-pecl-ssdeep php74-php-pecl-gnupg -y
sudo ln -s /usr/lib64/libfuzzy.so /usr/lib/libfuzzy.so
sudo scl enable rh-php72 'pecl install ssdeep'
echo "extension=ssdeep.so" |sudo tee /etc/opt/rh/rh-php72/php.d/99-ssdeep.ini
# Install gnupg extension
sudo yum install gpgme-devel -y
sudo scl enable rh-php72 'pecl install gnupg'
echo "extension=gnupg.so" |sudo tee /etc/opt/rh/rh-php72/php.d/99-gnupg.ini
sudo systemctl restart rh-php72-php-fpm.service
sudo systemctl restart php74-php-fpm.service
# If you have not yet set a timezone in php.ini
echo 'date.timezone = "Asia/Tokyo"' |sudo tee /etc/opt/rh/rh-php72/php.d/timezone.ini
echo 'date.timezone = "Asia/Tokyo"' |sudo tee /etc/opt/remi/php74/php.d/timezone.ini
# Recommended: Change some PHP settings in /etc/opt/rh/rh-php72/php.ini
# Recommended: Change some PHP settings in /etc/opt/remi/php74/php.ini
# max_execution_time = 300
# memory_limit = 2048M
# upload_max_filesize = 50M
@ -2251,71 +2246,40 @@ installCake_RHEL ()
do
sudo sed -i "s/^\($key\).*/\1 = $(eval echo \${$key})/" $PHP_INI
done
sudo systemctl restart rh-php72-php-fpm.service
sudo sed -i "s/^\(session.sid_length\).*/\1 = $(eval echo \${session0sid_length})/" $PHP_INI
sudo sed -i "s/^\(session.use_strict_mode\).*/\1 = $(eval echo \${session0use_strict_mode})/" $PHP_INI
sudo systemctl restart php74-php-fpm.service
# To use the scheduler worker for scheduled tasks, do the following:
sudo cp -fa $PATH_TO_MISP/INSTALL/setup/config.php $PATH_TO_MISP/app/Plugin/CakeResque/Config/config.php
}
prepareDB_RHEL () {
RUN_MYSQL="/usr/bin/scl enable rh-mariadb102"
# Enable, start and secure your mysql database server
sudo systemctl enable --now rh-mariadb102-mariadb.service
echo [mysqld] |sudo tee /etc/opt/rh/rh-mariadb102/my.cnf.d/bind-address.cnf
echo bind-address=127.0.0.1 |sudo tee -a /etc/opt/rh/rh-mariadb102/my.cnf.d/bind-address.cnf
sudo systemctl restart rh-mariadb102-mariadb
sudo systemctl enable --now mariadb.service
echo [mysqld] |sudo tee /etc/my.cnf.d/bind-address.cnf
echo bind-address=127.0.0.1 |sudo tee -a /etc/my.cnf.d/bind-address.cnf
sudo systemctl restart mariadb
sudo yum install expect -y
# Kill the anonymous users
sudo mysql -h $DBHOST -e "DROP USER IF EXISTS ''@'localhost'"
# Because our hostname varies we'll use some Bash magic here.
sudo mysql -h $DBHOST -e "DROP USER IF EXISTS ''@'$(hostname)'"
# Kill off the demo database
sudo mysql -h $DBHOST -e "DROP DATABASE IF EXISTS test"
# No root remote logins
sudo mysql -h $DBHOST -e "DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1')"
# Make sure that NOBODY can access the server without a password
sudo mysqladmin -h $DBHOST -u "${DBUSER_ADMIN}" password "${DBPASSWORD_ADMIN}"
# Make our changes take effect
sudo mysql -h $DBHOST -e "FLUSH PRIVILEGES"
## The following needs some thoughts about scl enable foo
#if [[ ! -e /var/opt/rh/rh-mariadb102/lib/mysql/misp/users.ibd ]]; then
# We ask interactively your password if not run as root
pw=""
if [[ "$EUID" -ne 0 ]]; then
read -s -p "Enter sudo password: " pw
fi
expect -f - <<-EOF
set timeout 10
spawn sudo scl enable rh-mariadb102 mysql_secure_installation
expect {
"*sudo*" {
send "$pw\r"
exp_continue
}
"Enter current password for root (enter for none):" {
send -- "\r"
}
}
expect "Set root password?"
send -- "y\r"
expect "New password:"
send -- "${DBPASSWORD_ADMIN}\r"
expect "Re-enter new password:"
send -- "${DBPASSWORD_ADMIN}\r"
expect "Remove anonymous users?"
send -- "y\r"
expect "Disallow root login remotely?"
send -- "y\r"
expect "Remove test database and access to it?"
send -- "y\r"
expect "Reload privilege tables now?"
send -- "y\r"
expect eof
EOF
sudo yum remove tcl expect -y
sudo systemctl restart rh-mariadb102-mariadb
scl enable rh-mariadb102 "mysql -h $DBHOST -u $DBUSER_ADMIN -p$DBPASSWORD_ADMIN -e 'CREATE DATABASE $DBNAME;'"
scl enable rh-mariadb102 "mysql -h $DBHOST -u $DBUSER_ADMIN -p$DBPASSWORD_ADMIN -e \"GRANT USAGE on *.* to $DBUSER_MISP@localhost IDENTIFIED by '$DBPASSWORD_MISP';\""
scl enable rh-mariadb102 "mysql -h $DBHOST -u $DBUSER_ADMIN -p$DBPASSWORD_ADMIN -e \"GRANT ALL PRIVILEGES on $DBNAME.* to '$DBUSER_MISP'@'localhost';\""
scl enable rh-mariadb102 "mysql -h $DBHOST -u $DBUSER_ADMIN -p$DBPASSWORD_ADMIN -e 'FLUSH PRIVILEGES;'"
$SUDO_WWW cat $PATH_TO_MISP/INSTALL/MYSQL.sql | sudo scl enable rh-mariadb102 "mysql -h $DBHOST -u $DBUSER_MISP -p$DBPASSWORD_MISP $DBNAME"
sudo mysql -h $DBHOST -u "${DBUSER_ADMIN}" -p"${DBPASSWORD_ADMIN}" -e "CREATE DATABASE ${DBNAME};"
sudo mysql -h $DBHOST -u "${DBUSER_ADMIN}" -p"${DBPASSWORD_ADMIN}" -e "CREATE USER '${DBUSER_MISP}'@'localhost' IDENTIFIED BY '${DBPASSWORD_MISP}';"
sudo mysql -h $DBHOST -u "${DBUSER_ADMIN}" -p"${DBPASSWORD_ADMIN}" -e "GRANT USAGE ON *.* to '${DBUSER_MISP}'@'localhost';"
sudo mysql -h $DBHOST -u "${DBUSER_ADMIN}" -p"${DBPASSWORD_ADMIN}" -e "GRANT ALL PRIVILEGES on ${DBNAME}.* to '${DBUSER_MISP}'@'localhost';"
sudo mysql -h $DBHOST -u "${DBUSER_ADMIN}" -p"${DBPASSWORD_ADMIN}" -e "FLUSH PRIVILEGES;"
# Import the empty MISP database from MYSQL.sql
${SUDO_WWW} cat ${PATH_TO_MISP}/INSTALL/MYSQL.sql | mysql -h $DBHOST -u "${DBUSER_MISP}" -p"${DBPASSWORD_MISP}" ${DBNAME}
}
apacheConfig_RHEL () {
@ -2350,13 +2314,12 @@ apacheConfig_RHEL () {
sudo chcon -t httpd_sys_rw_content_t $PATH_TO_MISP/app/files/scripts/tmp
sudo chcon -t httpd_sys_rw_content_t $PATH_TO_MISP/app/Plugin/CakeResque/tmp
sudo chcon -t httpd_sys_script_exec_t $PATH_TO_MISP/app/Console/cake
sudo chcon -t httpd_sys_script_exec_t $PATH_TO_MISP/app/Console/worker/*.sh
sudo chcon -t httpd_sys_script_exec_t $PATH_TO_MISP/app/files/scripts/*.py
sudo chcon -t httpd_sys_script_exec_t $PATH_TO_MISP/app/files/scripts/*/*.py
sudo chcon -t httpd_sys_script_exec_t $PATH_TO_MISP/app/files/scripts/lief/build/api/python/lief.so
sudo sh -c "chcon -t httpd_sys_script_exec_t $PATH_TO_MISP/app/Console/worker/*.sh"
sudo sh -c "chcon -t httpd_sys_script_exec_t $PATH_TO_MISP/app/files/scripts/*.py"
sudo sh -c "chcon -t httpd_sys_script_exec_t $PATH_TO_MISP/app/files/scripts/*/*.py"
sudo chcon -t httpd_sys_script_exec_t $PATH_TO_MISP/app/Vendor/pear/crypt_gpg/scripts/crypt-gpg-pinentry
sudo chcon -R -t bin_t $PATH_TO_MISP/venv/bin/*
find $PATH_TO_MISP/venv -type f -name "*.so*" -or -name "*.so.*" | xargs sudo chcon -t lib_t
sudo sh -c "chcon -R -t bin_t $PATH_TO_MISP/venv/bin/*"
sudo find $PATH_TO_MISP/venv -type f -name "*.so*" -or -name "*.so.*" | xargs sudo chcon -t lib_t
# Only run these if you want to be able to update MISP from the web interface
sudo chcon -R -t httpd_sys_rw_content_t $PATH_TO_MISP/.git
sudo chcon -R -t httpd_sys_rw_content_t $PATH_TO_MISP/app/tmp
@ -2478,6 +2441,7 @@ configMISP_RHEL () {
# If you want to be able to change configuration parameters from the webinterface:
sudo chown $WWW_USER:$WWW_USER $PATH_TO_MISP/app/Config/config.php
sudo chmod 660 $PATH_TO_MISP/app/Config/config.php
sudo chcon -t httpd_sys_rw_content_t $PATH_TO_MISP/app/Config/config.php
# Generate a GPG encryption key.
@ -2512,13 +2476,13 @@ EOF
configWorkersRHEL () {
echo "[Unit]
Description=MISP background workers
After=rh-mariadb102-mariadb.service rh-redis32-redis.service rh-php72-php-fpm.service
After=mariadb.service redis.service php74-php-fpm.service
[Service]
Type=forking
User=$WWW_USER
Group=$WWW_USER
ExecStart=/usr/bin/scl enable rh-php72 rh-redis32 rh-mariadb102 $PATH_TO_MISP/app/Console/worker/start.sh
ExecStart=$PATH_TO_MISP/app/Console/worker/start.sh
Restart=always
RestartSec=10
@ -3288,6 +3252,7 @@ x86_64-debian-stretch
x86_64-debian-buster
x86_64-ubuntu-bionic
x86_64-ubuntu-focal
x86_64-ubuntu-hirsute
x86_64-kali-2020.4
armv6l-raspbian-stretch
armv7l-raspbian-stretch
@ -3322,6 +3287,11 @@ if [[ "${FLAVOUR}" == "ubuntu" ]]; then
echo "Please report bugs/issues here: https://github.com/MISP/MISP/issues"
installSupported PHP="7.4" && exit || exit
fi
if [[ "${RELEASE}" == "21.04" ]]; then
echo "Install on Ubuntu 21.04 LTS fully supported."
echo "Please report bugs/issues here: https://github.com/MISP/MISP/issues"
installSupported PHP="7.4" && exit || exit
fi
if [[ "${RELEASE}" == "18.10" ]]; then
echo "Install on Ubuntu 18.10 partially supported, bye."
echo "Please report bugs/issues here: https://github.com/MISP/MISP/issues"

View File

@ -1,5 +1,5 @@
; Generated by RHash v1.3.9 on 2021-02-03 at 15:13.46
; Generated by RHash v1.3.9 on 2021-03-25 at 12:56.17
; Written by Kravchenko Aleksey (Akademgorodok) - http://rhash.sf.net/
;
; 137499 15:13.46 2021-02-03 INSTALL.sh
INSTALL.sh 5645164D7C2701EC0E0FF7D33CD8263D41B27947 803115D518C0EF187B041B942B0298CEAF329F4ADD07DB405B508D6E7ABB5D45 6B8019972E761EDC6E8CD1335E8B280376315DD762F6C98AE36C149D7960A0FC5D3C6E3F228DCB2949BDCB747942B557 ABE5D6541D23895E863BAB8BE306E54058897823EA703E6A8A225097ED8CA24F365441E8E98851576E5289B34D81C351C1A1A2520A0865BFE0D37AD77D018282
; 137691 12:56.17 2021-03-25 INSTALL.sh
INSTALL.sh 5694A8F77384677CA3DC84FB5A5F3C06D6FFF03F 5F3A9B04BEEE449E96F4A698F3FA497390E46E2AD1DBDDED37F54E29FED76221 ABCB35B681F9A5E3568A055465976EC0996C0CC2FD8A39384E05D90413D8300B7356AAE23A540912D7D9907BECCDCD9F 54712D3100DAF92EA6201D86941222F6877B772533D048C8F758332D9B45418B64AA767A0D78C8A39E491BE114F139FFEF5A2E0436EA8503AA593556E56C0992

View File

@ -1 +1 @@
5645164d7c2701ec0e0ff7d33cd8263d41b27947 INSTALL.sh
5694a8f77384677ca3dc84fb5a5f3c06d6fff03f INSTALL.sh

View File

@ -1 +1 @@
803115d518c0ef187b041b942b0298ceaf329f4add07db405b508d6e7abb5d45 INSTALL.sh
5f3a9b04beee449e96f4a698f3fa497390e46e2ad1dbdded37f54e29fed76221 INSTALL.sh

View File

@ -1 +1 @@
6b8019972e761edc6e8cd1335e8b280376315dd762f6c98ae36c149d7960a0fc5d3c6e3f228dcb2949bdcb747942b557 INSTALL.sh
abcb35b681f9a5e3568a055465976ec0996c0cc2fd8a39384e05d90413d8300b7356aae23a540912d7d9907beccdcd9f INSTALL.sh

View File

@ -1 +1 @@
abe5d6541d23895e863bab8be306e54058897823ea703e6a8a225097ed8ca24f365441e8e98851576e5289b34d81c351c1a1a2520a0865bfe0d37ad77d018282 INSTALL.sh
54712d3100daf92ea6201d86941222f6877b772533d048c8f758332d9b45418b64aa767a0d78c8a39e491be114f139ffef5a2e0436ea8503aa593556e56c0992 INSTALL.sh

View File

@ -787,6 +787,7 @@ x86_64-debian-stretch
x86_64-debian-buster
x86_64-ubuntu-bionic
x86_64-ubuntu-focal
x86_64-ubuntu-hirsute
x86_64-kali-2020.4
armv6l-raspbian-stretch
armv7l-raspbian-stretch
@ -821,6 +822,11 @@ if [[ "${FLAVOUR}" == "ubuntu" ]]; then
echo "Please report bugs/issues here: https://github.com/MISP/MISP/issues"
installSupported PHP="7.4" && exit || exit
fi
if [[ "${RELEASE}" == "21.04" ]]; then
echo "Install on Ubuntu 21.04 LTS fully supported."
echo "Please report bugs/issues here: https://github.com/MISP/MISP/issues"
installSupported PHP="7.4" && exit || exit
fi
if [[ "${RELEASE}" == "18.10" ]]; then
echo "Install on Ubuntu 18.10 partially supported, bye."
echo "Please report bugs/issues here: https://github.com/MISP/MISP/issues"

View File

@ -243,7 +243,8 @@ CREATE TABLE IF NOT EXISTS event_reports (
`deleted` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (id),
CONSTRAINT u_uuid UNIQUE (uuid),
INDEX `name` (`name`)
INDEX `name` (`name`),
INDEX `event_id` (`event_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
@ -455,7 +456,7 @@ CREATE TABLE IF NOT EXISTS `galaxy_clusters` (
`collection_uuid` varchar(255) COLLATE utf8_bin NOT NULL,
`type` varchar(255) COLLATE utf8_bin NOT NULL,
`value` text COLLATE utf8_bin NOT NULL,
`tag_name` varchar(255) COLLATE utf8_bin NOT NULL DEFAULT '',
`tag_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
`description` text COLLATE utf8_bin NOT NULL,
`galaxy_id` int(11) NOT NULL,
`source` varchar(255) COLLATE utf8_bin NOT NULL DEFAULT '',

View File

@ -7,10 +7,10 @@ require {
type httpd_sys_content_t;
type httpd_sys_rw_content_t;
class dir { ioctl read getattr lock search open remove_name };
class file { unlink write };
class file { unlink write rename };
}
#============= logrotate_t ==============
allow logrotate_t httpd_sys_content_t:dir { ioctl read getattr lock search open };
allow logrotate_t httpd_sys_rw_content_t:dir { ioctl read getattr lock search open };
allow httpd_t httpd_log_t:dir remove_name;
allow { httpd_t httpd_sys_script_t } httpd_log_t:file { unlink write };
allow { httpd_t httpd_sys_script_t } httpd_log_t:file { unlink write rename };

2
PyMISP

@ -1 +1 @@
Subproject commit 5b97b7d0158906cd0f646a7273a3ca5b1828cd15
Subproject commit 51edb8ab33c5ee6bd3b9b05ea5809299f37c4fbe

View File

@ -20,6 +20,10 @@ MISP - Threat Intelligence Sharing Platform
<td>Twitter</td>
<td><a href="https://twitter.com/MISPProject"><img src="https://img.shields.io/twitter/follow/MISPProject.svg?style=social&label=Follow" /></a></td>
</tr>
<tr>
<td>Localization</td>
<td><a href="https://crowdin.com/project/misp"><img src="https://badges.crowdin.net/misp/localized.svg" /></a></td>
</tr>
<tr>
<td>Contributors</td>
<td><img src="https://img.shields.io/github/contributors/MISP/MISP.svg" /></td>

View File

@ -1 +1 @@
{"major":2, "minor":4, "hotfix":137}
{"major":2, "minor":4, "hotfix":141}

View File

@ -169,15 +169,37 @@ class AdminShell extends AppShell
}
}
# FIXME: Make Taxonomy->update() return a status string on API if successful
public function updateTaxonomies()
{
$this->ConfigLoad->execute();
$result = $this->Taxonomy->update();
if ($result) {
echo 'Taxonomies updated' . PHP_EOL;
} else {
echo 'Could not update Taxonomies' . PHP_EOL;
$successes = count(!empty($result['success']) ? $result['success'] : []);
$fails = count(!empty($result['fails']) ? $result['fails'] : []);
$message = '';
if ($successes == 0 && $fails == 0) {
$message = __('All taxonomies are up to date already.');
} elseif ($successes == 0 && $fails > 0) {
$message = __('Could not update any of the taxonomies.');
} elseif ($successes > 0 ) {
$message = __('Successfully updated %s taxonomies.', $successes);
if ($fails != 0) {
$message .= __(' However, could not update %s taxonomies.', $fails);
}
}
echo $message . PHP_EOL;
}
public function enableTaxonomyTags()
{
if (empty($this->args[0]) || !is_numeric($this->args[0])) {
echo 'Usage: ' . APP . '/cake ' . 'Admin enableTaxonomyTags [taxonomy_id]' . PHP_EOL;
} else {
$result = $this->Taxonomy->addTags(intval($this->args[0]));
if ($result) {
echo 'Taxonomy tags enabled' . PHP_EOL;
} else {
echo 'Could not enable taxonomy tags' . PHP_EOL;
}
}
}
@ -214,19 +236,24 @@ class AdminShell extends AppShell
if (empty($user)) {
echo 'User with ID: ' . $userId . ' not found' . PHP_EOL;
$result = $this->ObjectTemplate->update();
if ($result) {
echo 'Object templates updated' . PHP_EOL;
} else {
echo 'Could not update object templates' . PHP_EOL;
}
} else {
$result = $this->ObjectTemplate->update($user, false,false);
if ($result) {
echo 'Object templates updated' . PHP_EOL;
} else {
echo 'Could not update object templates' . PHP_EOL;
}
$successes = count(!empty($result['success']) ? $result['success'] : []);
$fails = count(!empty($result['fails']) ? $result['fails'] : []);
$message = '';
if ($successes == 0 && $fails == 0) {
$message = __('All object templates are up to date already.');
} elseif ($successes == 0 && $fails > 0) {
$message = __('Could not update any of the object templates.');
} elseif ($successes > 0 ) {
$message = __('Successfully updated %s object templates.', $successes);
if ($fails != 0) {
$message .= __(' However, could not update %s object templates.', $fails);
}
}
echo $message . PHP_EOL;
}
}
@ -666,4 +693,11 @@ class AdminShell extends AppShell
$this->Job->saveField('message', 'Job done.');
$this->Job->saveField('status', 4);
}
public function updatesDone()
{
$blocking = !empty($this->args[0]);
$done = $this->AdminSetting->updatesDone($blocking);
$this->out($done ? 'True' : 'False');
}
}

View File

@ -0,0 +1,49 @@
<?php
class DevShell extends AppShell {
public $uses = [];
public function cleanFeedDefault() {
$this->out(__('Massaging the feed metadata file.'));
$data = file_get_contents(APP . 'files/feed-metadata/defaults.json');
if (empty($data)) {
$this->stdout->styles('error');
$this->out(__('Could not read the defaults.json file at %s. Exiting', APP . 'files/feed-metadata/defaults.json'));
} else {
$data = json_decode($data, true);
$validFields = [
'Feed' => [
'name', 'provider', 'url', 'rules', 'enabled', 'distribution',
'default', 'source_format', 'fixed_event', 'delta_merge',
'publish', 'override_ids', 'settings', 'input_source',
'delete_local_file', 'lookup_visible'
],
'Tag' => [
'name', 'colour', 'exportable', 'hide_tag'
]
];
foreach ($data as $k => $feedData) {
$temp = [];
foreach ($validFields as $scope => $fieldNames) {
foreach ($fieldNames as $fieldName) {
if (isset($feedData[$scope][$fieldName])) {
$temp[$scope][$fieldName] = $feedData[$scope][$fieldName];
}
}
}
$data[$k] = $temp;
}
if (!empty($data)) {
file_put_contents(APP . 'files/feed-metadata/defaults.json', json_encode($data, JSON_PRETTY_PRINT));
$this->out(__(
'Done. The feed definitions contain %s feeds and can be found at %s.',
count($data),
APP . 'files/feed-metadata/defaults.json'
));
} else {
$this->stdout->styles('error');
$this->out(__('Something went wrong.'));
}
}
}
}

View File

@ -29,16 +29,20 @@ class EventShell extends AppShell
],
)
));
$parser->addSubcommand('testEventNotificationEmail', [
'help' => __('Generate event notification email in EML format.'),
'arguments' => [
'event_id' => ['help' => __('Event ID'), 'required' => true],
'user_id' => ['help' => __('User ID'), 'required' => true],
],
]);
return $parser;
}
public function import()
{
list($userId, $path) = $this->args;
$user = $this->User->getAuthUser($userId);
if (empty($user)) {
$this->error("User with ID $userId does not exists.");
}
$user = $this->getUser($userId);
if (!file_exists($path)) {
$this->error("File '$path' does not exists.");
@ -113,7 +117,7 @@ class EventShell extends AppShell
$timeStart = time();
$userId = $this->args[0];
$id = $this->args[1];
$user = $this->User->getAuthUser($userId);
$user = $this->getUser($userId);
$this->Job->id = $id;
$export_type = $this->args[2];
file_put_contents('/tmp/test', $export_type);
@ -165,7 +169,7 @@ class EventShell extends AppShell
$this->ConfigLoad->execute();
$timeStart = time();
$userId = $this->args[0];
$user = $this->User->getAuthUser($userId);
$user = $this->getUser($userId);
$id = $this->args[1];
$this->Job->id = $id;
$this->Job->saveField('progress', 1);
@ -204,10 +208,7 @@ class EventShell extends AppShell
$jobId = $this->args[1];
$eventId = $this->args[2];
$oldpublish = $this->args[3];
$user = $this->User->getUserById($userId);
if (empty($user)) {
die("Invalid user ID '$userId' provided.");
}
$user = $this->getUser($userId);
$this->Event->sendAlertEmail($eventId, $user, $oldpublish, $jobId);
}
@ -220,10 +221,7 @@ class EventShell extends AppShell
$userId = $this->args[3];
$processId = $this->args[4];
$user = $this->User->getUserById($userId);
if (empty($user)) {
die("Invalid user ID '$userId' provided.");
}
$user = $this->getUser($userId);
$result = $this->Event->sendContactEmail($id, $message, $all, $user);
$this->Job->saveStatus($processId, $result);
}
@ -305,10 +303,7 @@ class EventShell extends AppShell
$passAlong = $this->args[1];
$jobId = $this->args[2];
$userId = $this->args[3];
$user = $this->User->getAuthUser($userId);
if (empty($user)) {
die("Invalid user ID '$userId' provided.");
}
$user = $this->getUser($userId);
$job = $this->Job->read(null, $jobId);
$this->Event->Behaviors->unload('SysLogLogable.SysLogLogable');
$result = $this->Event->publish($id, $passAlong);
@ -321,7 +316,6 @@ class EventShell extends AppShell
}
$this->Job->save($job);
$log = ClassRegistry::init('Log');
$log->create();
$log->createLogEntry($user, 'publish', 'Event', $id, 'Event (' . $id . '): published.', 'published () => (1)');
}
@ -332,10 +326,7 @@ class EventShell extends AppShell
$passAlong = $this->args[1];
$jobId = $this->args[2];
$userId = $this->args[3];
$user = $this->User->getAuthUser($userId);
if (empty($user)) {
die("Invalid user ID '$userId' provided.");
}
$user = $this->getUser($userId);
$job = $this->Job->read(null, $jobId);
$this->Event->Behaviors->unload('SysLogLogable.SysLogLogable');
$result = $this->Event->publish_sightings($id, $passAlong);
@ -348,7 +339,6 @@ class EventShell extends AppShell
}
$this->Job->save($job);
$log = ClassRegistry::init('Log');
$log->create();
$log->createLogEntry($user, 'publish_sightings', 'Event', $id, 'Sightings for event (' . $id . '): published.', 'publish_sightings updated');
}
@ -359,7 +349,7 @@ class EventShell extends AppShell
$jobId = $this->args[1];
$userId = $this->args[2];
$passAlong = $this->args[3];
$user = $this->User->getAuthUser($userId);
$user = $this->getUser($userId);
$job = $this->Job->read(null, $jobId);
$this->GalaxyCluster = ClassRegistry::init('GalaxyCluster');
$result = $this->GalaxyCluster->publish($clusterId, $passAlong=$passAlong);
@ -372,7 +362,6 @@ class EventShell extends AppShell
}
$this->Job->save($job);
$log = ClassRegistry::init('Log');
$log->create();
$log->createLogEntry($user, 'publish', 'GalaxyCluster', $clusterId, 'GalaxyCluster (' . $clusterId . '): published.', 'published () => (1)');
}
@ -383,8 +372,7 @@ class EventShell extends AppShell
die('Usage: ' . $this->Server->command_line_functions['enrichment'] . PHP_EOL);
}
$userId = $this->args[0];
$user = $this->User->getAuthUser($userId);
if (empty($user)) die('Invalid user.');
$user = $this->getUser($userId);
$eventId = $this->args[1];
$modulesRaw = $this->args[2];
try {
@ -425,7 +413,6 @@ class EventShell extends AppShell
echo $job['Job']['message'] . PHP_EOL;
$this->Job->save($job);
$log = ClassRegistry::init('Log');
$log->create();
$log->createLogEntry($user, 'enrichment', 'Event', $eventId, 'Event (' . $eventId . '): enriched.', 'enriched () => (1)');
}
@ -484,4 +471,45 @@ class EventShell extends AppShell
$job['Job']['message'] = __('Recovery complete. Event #%s recovered, using %s log entries.', $id, $result);
$this->Job->save($job);
}
public function testEventNotificationEmail()
{
list($eventId, $userId) = $this->args;
$user = $this->getUser($userId);
$eventForUser = $this->Event->fetchEvent($user, [
'eventid' => $eventId,
'includeAllTags' => true,
'includeEventCorrelations' => true,
'noEventReports' => true,
'noSightings' => true,
'metadata' => Configure::read('MISP.event_alert_metadata_only') ?: false,
]);
if (empty($eventForUser)) {
$this->error("Event with ID $eventId not exists or given user don't have permission to access it.");
}
$emailTemplate = $this->Event->prepareAlertEmail($eventForUser[0], $user);
App::uses('SendEmail', 'Tools');
App::uses('GpgTool', 'Tools');
$sendEmail = new SendEmail(GpgTool::initializeGpg());
$sendEmail->setTransport('Debug');
$result = $sendEmail->sendToUser(['User' => $user], null, $emailTemplate);
echo $result['contents']['headers'] . "\n\n" . $result['contents']['message'] . "\n";
}
/**
* @param int $userId
* @return array
*/
private function getUser($userId)
{
$user = $this->User->getAuthUser($userId);
if (empty($user)) {
$this->error("User with ID $userId does not exists.");
}
return $user;
}
}

View File

@ -14,6 +14,26 @@ class ServerShell extends AppShell
public $uses = array('Server', 'Task', 'Job', 'User', 'Feed');
public $tasks = array('ConfigLoad');
public function list()
{
$this->ConfigLoad->execute();
$res = ['servers'=>[]];
$servers = $this->Server->find('all', [
'fields' => ['Server.id', 'Server.name', 'Server.url'],
'recursive' => 0
]);
foreach ($servers as $server) {
echo sprintf(
'%sServer #%s :: %s :: %s',
PHP_EOL,
$server['Server']['id'],
$server['Server']['name'],
$server['Server']['url']
);
}
echo PHP_EOL;
}
public function listServers()
{
$this->ConfigLoad->execute();

View File

@ -25,8 +25,8 @@ class AppController extends Controller
public $helpers = array('OrgImg', 'FontAwesome', 'UserName', 'DataPathCollector');
private $__queryVersion = '122';
public $pyMispVersion = '2.4.137';
private $__queryVersion = '127';
public $pyMispVersion = '2.4.140';
public $phpmin = '7.2';
public $phprec = '7.4';
public $phptoonew = '8.0';
@ -79,6 +79,7 @@ class AppController extends Controller
),
'Security',
'ACL',
'CompressedRequestHandler',
'RestResponse',
'Flash',
'Toolbox',
@ -93,26 +94,19 @@ class AppController extends Controller
public function beforeFilter()
{
$this->_setupBaseurl();
$this->Auth->loginRedirect = $this->baseurl. '/users/routeafterlogin';
$this->Auth->loginRedirect = $this->baseurl . '/users/routeafterlogin';
$customLogout = Configure::read('Plugin.CustomAuth_custom_logout');
$this->Auth->logoutRedirect = $customLogout ?: ($this->baseurl . '/users/login');
$this->__sessionMassage();
if (Configure::read('Security.allow_cors')) {
// Add CORS headers
$this->response->cors($this->request,
explode(',', Configure::read('Security.cors_origins')),
['*'],
['Origin', 'Content-Type', 'Authorization', 'Accept']);
if ($this->request->is('options')) {
// Stop here!
// CORS only needs the headers
$this->response->send();
$this->_stop();
}
// If server is running behind reverse proxy, PHP will not recognize that user is accessing site by HTTPS connection.
// By setting `Security.force_https` to `true`, session cookie will be set as Secure and CSP headers will upgrade insecure requests.
if (Configure::read('Security.force_https')) {
$_SERVER['HTTPS'] = 'on';
}
$this->__cors();
if (Configure::read('Security.check_sec_fetch_site_header')) {
$secFetchSite = $this->request->header('Sec-Fetch-Site');
if ($secFetchSite !== false && $secFetchSite !== 'same-origin' && ($this->request->is('post') || $this->request->is('put') || $this->request->is('ajax'))) {
@ -122,7 +116,10 @@ class AppController extends Controller
if (Configure::read('Security.disable_browser_cache')) {
$this->response->disableCache();
}
$this->response->header('X-XSS-Protection', '1; mode=block');
if (!$this->_isRest()) {
$this->__contentSecurityPolicy();
$this->response->header('X-XSS-Protection', '1; mode=block');
}
if (!empty($this->params['named']['sql'])) {
$this->sql_dump = intval($this->params['named']['sql']);
@ -229,6 +226,10 @@ class AppController extends Controller
$this->Security->csrfCheck = false;
}
if ($this->__loginByAuthKey() === false || $this->Auth->user() === null) {
if ($this->__loginByAuthKey() === null) {
$this->loadModel('Log');
$this->Log->createLogEntry('SYSTEM', 'auth_fail', 'User', 0, "Failed API authentication. No authkey was provided.");
}
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)) {
@ -313,11 +314,11 @@ class AppController extends Controller
$this->__accessMonitor($user);
} else {
$pre_auth_actions = array('login', 'register', 'getGpgPublicKey');
$preAuthActions = array('login', 'register', 'getGpgPublicKey');
if (!empty(Configure::read('Security.email_otp_enabled'))) {
$pre_auth_actions[] = 'email_otp';
$preAuthActions[] = 'email_otp';
}
if (!$this->_isControllerAction(['users' => $pre_auth_actions])) {
if (!$this->_isControllerAction(['users' => $preAuthActions, 'servers' => ['cspReport']])) {
if (!$this->request->is('ajax')) {
$this->Session->write('pre_login_requested_url', $this->here);
}
@ -442,22 +443,9 @@ class AppController extends Controller
} else {
// User not authenticated correctly
// reset the session information
$redis = $this->User->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);
if ($this->_shouldLog($authKeyToStore)) {
$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->Log->createLogEntry('SYSTEM', 'auth_fail', 'User', 0, "Failed authentication using API key ($authKeyToStore)");
}
$this->Session->destroy();
}
@ -544,8 +532,10 @@ class AppController extends Controller
}
if ($user['disabled']) {
$this->Log = ClassRegistry::init('Log');
$this->Log->createLogEntry($user, 'auth_fail', 'User', $user['id'], 'Login attempt by disabled user.');
if ($this->_shouldLog('disabled:' . $user['id'])) {
$this->Log = ClassRegistry::init('Log');
$this->Log->createLogEntry($user, 'auth_fail', 'User', $user['id'], 'Login attempt by disabled user.');
}
$this->Auth->logout();
if ($this->_isRest()) {
@ -561,11 +551,33 @@ class AppController extends Controller
if (isset($user['authkey_expiration']) && $user['authkey_expiration']) {
$time = isset($_SERVER['REQUEST_TIME']) ? $_SERVER['REQUEST_TIME'] : time();
if ($user['authkey_expiration'] < $time) {
if ($this->_shouldLog('expired:' . $user['authkey_id'])) {
$this->Log = ClassRegistry::init('Log');
$this->Log->createLogEntry($user, 'auth_fail', 'User', $user['id'], "Login attempt by expired auth key {$user['authkey_id']}.");
}
$this->Auth->logout();
throw new ForbiddenException('Auth key is expired');
}
}
if (!empty($user['allowed_ips'])) {
App::uses('CidrTool', 'Tools');
$cidrTool = new CidrTool($user['allowed_ips']);
$remoteIp = $this->_remoteIp();
if ($remoteIp === null) {
$this->Auth->logout();
throw new ForbiddenException('Auth key is limited to IP address, but IP address not found');
}
if (!$cidrTool->contains($remoteIp)) {
if ($this->_shouldLog('not_allowed_ip:' . $user['authkey_id'] . ':' . $remoteIp)) {
$this->Log = ClassRegistry::init('Log');
$this->Log->createLogEntry($user, 'auth_fail', 'User', $user['id'], "Login attempt from not allowed IP address for auth key {$user['authkey_id']}.");
}
$this->Auth->logout();
throw new ForbiddenException('It is not possible to use this Auth key from your IP address');
}
}
$isUserRequest = !$this->_isRest() && !$this->request->is('ajax') && !$this->_isAutomation();
// Next checks makes sense just for user direct HTTP request, so skip REST and AJAX calls
if (!$isUserRequest) {
@ -628,7 +640,7 @@ class AppController extends Controller
return;
}
$remoteAddress = trim($_SERVER['REMOTE_ADDR']);
$remoteAddress = $this->_remoteIp();
$pipe = $redis->multi(Redis::PIPELINE);
// keep for 30 days
@ -676,10 +688,69 @@ class AppController extends Controller
$change .= PHP_EOL . 'Request body: ' . $payload;
}
$this->Log = ClassRegistry::init('Log');
try {
$this->Log->createLogEntry($user, 'request', 'User', $user['id'], 'Paranoid log entry', $change);
} catch (Exception $e) {
// When `MISP.log_skip_db_logs_completely` is enabled, Log::createLogEntry method throws exception
$this->Log->createLogEntry($user, 'request', 'User', $user['id'], 'Paranoid log entry', $change);
}
}
/**
* Generate Content-Security-Policy HTTP header
*/
private function __contentSecurityPolicy()
{
$default = [
'default-src' => "'self' data: 'unsafe-inline' 'unsafe-eval'",
'style-src' => "'self' 'unsafe-inline'",
'object-src' => "'none'",
'frame-ancestors' => "'none'",
'worker-src' => "'none'",
'child-src' => "'none'",
'frame-src' => "'none'",
'base-uri' => "'self'",
'img-src' => "'self' data:",
'font-src' => "'self'",
'form-action' => "'self'",
'connect-src' => "'self'",
'manifest-src' => "'none'",
'report-uri' => '/servers/cspReport',
];
if (env('HTTPS')) {
$default['upgrade-insecure-requests'] = null;
}
$custom = Configure::read('Security.csp');
if ($custom === false) {
return;
}
if (is_array($custom)) {
$default = $custom + $default;
}
$header = [];
foreach ($default as $key => $value) {
if ($value !== false) {
if ($value === null) {
$header[] = $key;
} else {
$header[] = "$key $value";
}
}
}
$headerName = Configure::read('Security.csp_enforce') ? 'Content-Security-Policy' : 'Content-Security-Policy-Report-Only';
$this->response->header($headerName, implode('; ', $header));
}
private function __cors()
{
if (Configure::read('Security.allow_cors')) {
// Add CORS headers
$this->response->cors($this->request,
explode(',', Configure::read('Security.cors_origins')),
['*'],
['Origin', 'Content-Type', 'Authorization', 'Accept']);
if ($this->request->is('options')) {
// Stop here!
// CORS only needs the headers
$this->response->send();
$this->_stop();
}
}
}
@ -737,7 +808,7 @@ class AppController extends Controller
* Configure the debugMode view parameter
*/
protected function _setupDebugMode() {
$this->set('debugMode', (Configure::read('debug') > 1) ? 'debugOn' : 'debugOff');
$this->set('debugMode', (Configure::read('debug') >= 1) ? 'debugOn' : 'debugOff');
}
/*
@ -1212,7 +1283,7 @@ class AppController extends Controller
private function __sessionMassage()
{
if (!empty(Configure::read('MISP.uuid'))) {
if (empty(Configure::read('Session.cookie')) && !empty(Configure::read('MISP.uuid'))) {
Configure::write('Session.cookie', 'MISP-' . Configure::read('MISP.uuid'));
}
if (!empty(Configure::read('Session.cookieTimeout')) || !empty(Configure::read('Session.timeout'))) {
@ -1346,6 +1417,18 @@ class AppController extends Controller
}
$elementCounter = 0;
$renderView = false;
$responseType = empty($this->$scope->validFormats[$returnFormat][0]) ? 'json' : $this->$scope->validFormats[$returnFormat][0];
// halt execution if we were to query for items above the ID. Blocks the endless caching bug
if (!empty($filters['page']) && !empty($filters['returnFormat']) && $filters['returnFormat'] === 'cache') {
if ($this->__cachingOverflow($filters, $scope)) {
$filename = $this->RestSearch->getFilename($filters, $scope, $responseType);
return $this->RestResponse->viewData('', $responseType, false, true, $filename, [
'X-Result-Count' => 0,
'X-Export-Module-Used' => $returnFormat,
'X-Response-Format' => $responseType
]);
}
}
$final = $this->$scope->restSearch($user, $returnFormat, $filters, false, false, $elementCounter, $renderView);
if (!empty($renderView) && !empty($final)) {
$this->layout = false;
@ -1355,12 +1438,29 @@ class AppController extends Controller
}
$this->render('/Events/module_views/' . $renderView);
} else {
$responseType = $this->$scope->validFormats[$returnFormat][0];
$filename = $this->RestSearch->getFilename($filters, $scope, $responseType);
return $this->RestResponse->viewData($final, $responseType, false, true, $filename, array('X-Result-Count' => $elementCounter, 'X-Export-Module-Used' => $returnFormat, 'X-Response-Format' => $responseType));
}
}
/**
* Halt execution if we were to query for items above the ID. Blocks the endless caching bug.
*
* @param array $filters
* @param string $scope
* @return bool
*/
private function __cachingOverflow($filters, $scope)
{
$offset = ($filters['page'] * (empty($filters['limit']) ? 60 : $filters['limit'])) + 1;
$max_id = $this->$scope->query(sprintf('SELECT max(id) as max_id from %s;', Inflector::tableize($scope)));
$max_id = intval($max_id[0][0]['max_id']);
if ($max_id < $offset) {
return true;
}
return false;
}
/**
* Returns true if user can modify given event.
*
@ -1376,10 +1476,10 @@ class AppController extends Controller
if ($this->userRole['perm_site_admin']) {
return true;
}
if ($this->userRole['perm_modify_org'] && $event['Event']['orgc_id'] == $this->Auth->user('org_id')) {
if ($this->userRole['perm_modify_org'] && $event['Event']['orgc_id'] == $this->Auth->user()['org_id']) {
return true;
}
if ($this->userRole['perm_modify'] && $event['Event']['user_id'] == $this->Auth->user('id')) {
if ($this->userRole['perm_modify'] && $event['Event']['user_id'] == $this->Auth->user()['id']) {
return true;
}
return false;
@ -1423,17 +1523,50 @@ class AppController extends Controller
throw new RuntimeException("User with ID {$sessionUser['id']} not exists.");
}
if (isset($sessionUser['authkey_id'])) {
// Reload authkey
$this->loadModel('AuthKey');
if (!$this->AuthKey->exists($sessionUser['authkey_id'])) {
$authKey = $this->AuthKey->find('first', [
'conditions' => ['id' => $sessionUser['authkey_id'], 'user_id' => $user['id']],
'fields' => ['id', 'expiration', 'allowed_ips'],
'recursive' => -1,
]);
if (empty($authKey)) {
throw new RuntimeException("Auth key with ID {$sessionUser['authkey_id']} not exists.");
}
$user['authkey_id'] = $authKey['AuthKey']['id'];
$user['authkey_expiration'] = $authKey['AuthKey']['expiration'];
$user['allowed_ips'] = $authKey['AuthKey']['allowed_ips'];
}
foreach (['authkey_id', 'authkey_expiration', 'logged_by_authkey'] as $copy) {
if (isset($sessionUser[$copy])) {
$user[$copy] = $sessionUser[$copy];
}
if (isset($sessionUser['logged_by_authkey'])) {
$user['logged_by_authkey'] = $sessionUser['logged_by_authkey'];
}
$this->Auth->login($user);
return $user;
}
/**
* @return string|null
*/
protected function _remoteIp()
{
$ipHeader = Configure::read('MISP.log_client_ip_header') ?: 'REMOTE_ADDR';
return isset($_SERVER[$ipHeader]) ? trim($_SERVER[$ipHeader]) : null;
}
/**
* @param string $key
* @return bool Returns true if the same log defined by $key was not stored in last hour
*/
protected function _shouldLog($key)
{
if (Configure::read('Security.log_each_individual_auth_fail')) {
return true;
}
$redis = $this->User->setupRedis();
if ($redis && !$redis->exists('misp:auth_fail_throttling:' . $key)) {
$redis->setex('misp:auth_fail_throttling:' . $key, 3600, 1);
return true;
}
return false;
}
}

View File

@ -998,6 +998,7 @@ class AttributesController extends AppController
'includeAllTags' => false,
'includeAttributeUuid' => true,
'flatten' => true,
'deleted' => [0, 1]
);
if ($this->_isRest()) {
@ -1505,7 +1506,6 @@ class AttributesController extends AppController
'request' => $this->request,
'named_params' => $this->params['named'],
'paramArray' => $paramArray,
'ordered_url_params' => @compact($paramArray),
'additional_delimiters' => PHP_EOL
);
$exception = false;
@ -2582,9 +2582,10 @@ class AttributesController extends AppController
}
$totalAttributes = $this->Attribute->find('count', array());
$attributes = $this->Attribute->find('all', array(
'recursive' => -1,
'fields' => array($type, 'COUNT(id) as attribute_count'),
'group' => array($type)
'recursive' => -1,
'fields' => array($type, 'COUNT(id) as attribute_count'),
'group' => array($type),
'order' => ''
));
$results = array();
foreach ($attributes as $attribute) {
@ -2677,7 +2678,12 @@ class AttributesController extends AppController
}
}
} else {
$tag = $this->Event->EventTag->Tag->find('first', array('recursive' => -1, 'conditions' => $conditions));
$conditions = array('LOWER(Tag.name)' => strtolower(trim($tag_id)));
if (!$this->_isSiteAdmin()) {
$conditions['Tag.org_id'] = array('0', $this->Auth->user('org_id'));
$conditions['Tag.user_id'] = array('0', $this->Auth->user('id'));
}
$tag = $this->Attribute->AttributeTag->Tag->find('first', array('recursive' => -1, 'conditions' => $conditions));
if (empty($tag)) {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Invalid Tag.')), 'status'=>200, 'type' => 'json'));
}
@ -2708,15 +2714,15 @@ class AttributesController extends AppController
} else {
$attribute = $attributes[0];
}
if (!$this->__canModifyTag($attribute, $local)) {
$fails++;
continue;
}
$eventId = $attribute['Attribute']['event_id'];
$event = $this->Attribute->Event->find('first', array(
'conditions' => array('Event.id' => $eventId),
'recursive' => -1
));
if (!$this->__canModifyTag($event, $local)) {
$fails++;
continue;
}
if (!$this->_isRest()) {
$this->Attribute->Event->insertLock($this->Auth->user(), $eventId);
}

View File

@ -71,8 +71,34 @@ class AuthKeysController extends AppController
public function edit($id)
{
$this->set('metaGroup', 'admin');
$this->set('metaAction', 'authkeys_edit');
$this->CRUD->edit($id, [
'conditions' => $this->__prepareConditions(),
'afterFind' => function (array $authKey) {
unset($authKey['AuthKey']['authkey']);
if (is_array($authKey['AuthKey']['allowed_ips'])) {
$authKey['AuthKey']['allowed_ips'] = implode("\n", $authKey['AuthKey']['allowed_ips']);
}
$authKey['AuthKey']['expiration'] = date('Y-m-d H:i:s', $authKey['AuthKey']['expiration']);
return $authKey;
},
'fields' => ['comment', 'allowed_ips', 'expiration'],
]);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->set('dropdownData', [
'user' => $this->User->find('list', [
'sort' => ['username' => 'asc'],
'conditions' => ['id' => $this->request->data['AuthKey']['user_id']],
])
]);
$this->set('menuData', [
'menuList' => $this->_isSiteAdmin() ? 'admin' : 'globalActions',
'menuItem' => 'authKeyAdd',
]);
$this->set('edit', true);
$this->set('validity', Configure::read('Security.advanced_authkeys_validity'));
$this->render('add');
}
public function add($user_id = false)

View File

@ -262,7 +262,9 @@ class ACLComponent extends Component
'viewEventAttributes' => array('*'),
'viewGraph' => array('*'),
'viewGalaxyMatrix' => array('*'),
'xml' => array('*')
'xml' => array('*'),
'addEventLock' => ['perm_auth'],
'removeEventLock' => ['perm_auth'],
),
'favouriteTags' => array(
'toggle' => array('*'),
@ -346,7 +348,9 @@ class ACLComponent extends Component
'view' => array('*'),
),
'galaxyElements' => array(
'index' => array('*')
'delete' => array('perm_galaxy_editor'),
'flattenJson' => array('perm_galaxy_editor'),
'index' => array('*'),
),
'jobs' => array(
'cache' => array('*'),
@ -412,6 +416,7 @@ class ACLComponent extends Component
'edit' => array('perm_object_template'),
'delete' => array('perm_object_template'),
'getToggleField' => array(),
'getRaw' => array('perm_object_template'),
'objectChoice' => array('*'),
'objectMetaChoice' => array('perm_add'),
'view' => array('*'),
@ -492,7 +497,7 @@ class ACLComponent extends Component
'getSubmodulesStatus' => array(),
'getSubmoduleQuickUpdateForm' => array(),
'getWorkers' => array(),
'getVersion' => array('*'),
'getVersion' => array('perm_auth'),
'idTranslator' => ['OR' => [
'host_org_user',
'perm_site_admin',
@ -500,7 +505,7 @@ class ACLComponent extends Component
'import' => array(),
'index' => array(),
'ondemandAction' => array(),
'postTest' => array('perm_sync'),
'postTest' => array('*'),
'previewEvent' => array(),
'previewIndex' => array(),
'compareServers' => [],
@ -528,6 +533,7 @@ class ACLComponent extends Component
'uploadFile' => array(),
'viewDeprecatedFunctionUse' => array(),
'killAllWorkers' => ['perm_site_admin'],
'cspReport' => ['*'],
),
'shadowAttributes' => array(
'accept' => array('perm_add'),
@ -542,6 +548,7 @@ class ACLComponent extends Component
'generateCorrelation' => array(),
'index' => array('*'),
'view' => array('*'),
'viewPicture' => array('*'),
),
'sharingGroups' => array(
'add' => array('perm_sharing_group'),
@ -680,7 +687,7 @@ class ACLComponent extends Component
'register' => array('*'),
'registrations' => array('perm_site_admin'),
'resetAllSyncAuthKeys' => array(),
'resetauthkey' => array('*'),
'resetauthkey' => ['AND' => ['self_management_enabled', 'perm_auth']],
'request_API' => array('*'),
'routeafterlogin' => array('*'),
'statistics' => array('*'),
@ -720,6 +727,7 @@ class ACLComponent extends Component
),
'eventGraph' => array(
'view' => array('*'),
'viewPicture' => array('*'),
'add' => array('perm_add'),
'delete' => array('perm_modify'),
)

View File

@ -145,13 +145,21 @@ class CRUDComponent extends Component
if (empty($id)) {
throw new NotFoundException(__('Invalid %s.', $modelName));
}
$data = $this->Controller->{$modelName}->find('first',
isset($params['get']) ? $params['get'] : [
'recursive' => -1,
'conditions' => [
'id' => $id
]
]);
$query = isset($params['get']) ? $params['get'] : [
'recursive' => -1,
'conditions' => [
'id' => $id
],
];
if (!empty($params['conditions'])) {
$query['conditions']['AND'][] = $params['conditions'];
}
/** @var Model $model */
$model = $this->Controller->{$modelName};
$data = $model->find('first', $query);
if (isset($params['afterFind'])) {
$data = $params['afterFind']($data);
}
if ($this->Controller->request->is('post') || $this->Controller->request->is('put')) {
$input = $this->Controller->request->data;
if (empty($input[$modelName])) {
@ -171,7 +179,10 @@ class CRUDComponent extends Component
$data[$modelName][$field] = $fieldData;
}
}
if ($this->Controller->{$modelName}->save($data)) {
if (isset($params['beforeSave'])) {
$data = $params['beforeSave']($data);
}
if ($model->save($data)) {
$message = __('%s updated.', $modelName);
if ($this->Controller->IndexFilter->isRest()) {
$this->Controller->restResponsePayload = $this->Controller->RestResponse->viewData($data, 'json');
@ -182,7 +193,9 @@ class CRUDComponent extends Component
}
} else {
if ($this->Controller->IndexFilter->isRest()) {
$controllerName = $this->Controller->params['controller'];
$actionName = $this->Controller->params['action'];
$this->Controller->restResponsePayload = $this->Controller->RestResponse->saveFailResponse($controllerName, $actionName, false, $model->validationErrors, 'json');
}
}
} else {

View File

@ -0,0 +1,145 @@
<?php
class CompressedRequestHandlerComponent extends Component
{
// Maximum size of uncompressed data to prevent zip bombs
const MAX_SIZE = 1024 * 1024 * 100;
public function startup(Controller $controller)
{
$contentEncoding = CakeRequest::header('CONTENT_ENCODING');
if (!empty($contentEncoding)) {
if ($contentEncoding === 'br') {
$controller->request->setInput($this->decodeBrotliEncodedContent($controller));
} else if ($contentEncoding === 'gzip') {
$controller->request->setInput($this->decodeGzipEncodedContent($controller));
} else {
throw new MethodNotAllowedException("Unsupported content encoding '$contentEncoding'.");
}
}
}
/**
* @return array
*/
public function supportedEncodings()
{
$supportedEncodings = [];
if (function_exists('gzdecode') || function_exists('inflate_init')) {
$supportedEncodings[] = 'gzip';
}
if (function_exists('brotli_uncompress') || function_exists('brotli_uncompress_init')) {
$supportedEncodings[] = 'br';
}
return $supportedEncodings;
}
/**
* @return string
* @throws Exception
* @see CakeRequest::_readInput()
*/
private function decodeGzipEncodedContent(Controller $controller)
{
if (function_exists('inflate_init')) {
// Decompress data on the fly if supported
$resource = inflate_init(ZLIB_ENCODING_GZIP);
if ($resource === false) {
throw new Exception('GZIP incremental uncompress init failed.');
}
$uncompressed = '';
foreach ($this->streamInput() as $data) {
$uncompressedChunk = inflate_add($resource, $data);
if ($uncompressedChunk === false) {
throw new MethodNotAllowedException('Invalid compressed data.');
}
$uncompressed .= $uncompressedChunk;
if (strlen($uncompressed) > self::MAX_SIZE) {
throw new Exception("Uncompressed data are bigger than is limit.");
}
}
$uncompressedChunk = inflate_add($resource, '', ZLIB_FINISH);
if ($uncompressedChunk === false) {
throw new MethodNotAllowedException('Invalid compressed data.');
}
return $uncompressed . $uncompressedChunk;
} else if (function_exists('gzdecode')) {
$decoded = gzdecode($controller->request->input(), self::MAX_SIZE);
if ($decoded === false) {
throw new MethodNotAllowedException('Invalid compressed data.');
}
if (strlen($decoded) >= self::MAX_SIZE) {
throw new Exception("Uncompressed data are bigger than is limit.");
}
return $decoded;
} else {
throw new MethodNotAllowedException("This server doesn't support GZIP compressed requests.");
}
}
/**
* @param Controller $controller
* @return string
* @throws Exception
* @see CakeRequest::_readInput()
*/
private function decodeBrotliEncodedContent(Controller $controller)
{
if (function_exists('brotli_uncompress_init')) {
// Decompress data on the fly if supported
$resource = brotli_uncompress_init();
if ($resource === false) {
throw new Exception('Brotli incremental uncompress init failed.');
}
$uncompressed = '';
foreach ($this->streamInput() as $data) {
$uncompressedChunk = brotli_uncompress_add($resource, $data, BROTLI_PROCESS);
if ($uncompressedChunk === false) {
throw new MethodNotAllowedException('Invalid compressed data.');
}
$uncompressed .= $uncompressedChunk;
if (strlen($uncompressed) > self::MAX_SIZE) {
throw new Exception("Uncompressed data are bigger than is limit.");
}
}
$uncompressedChunk = brotli_uncompress_add($resource, '', BROTLI_FINISH);
if ($uncompressedChunk === false) {
throw new MethodNotAllowedException('Invalid compressed data.');
}
return $uncompressed . $uncompressedChunk;
} else if (function_exists('brotli_uncompress')) {
$decoded = brotli_uncompress($controller->request->input(), self::MAX_SIZE);
if ($decoded === false) {
throw new MethodNotAllowedException('Invalid compressed data.');
}
if (strlen($decoded) >= self::MAX_SIZE) {
throw new Exception("Uncompressed data are bigger than is limit.");
}
return $decoded;
} else {
throw new MethodNotAllowedException("This server doesn't support brotli compressed requests.");
}
}
/**
* @param int $chunkSize
* @return Generator<string>
* @throws Exception
*/
private function streamInput($chunkSize = 8192)
{
$fh = fopen('php://input', 'rb');
if ($fh === false) {
throw new Exception("Could not open PHP input for reading.");
}
while (!feof($fh)) {
$data = fread($fh, $chunkSize);
if ($data === false) {
throw new Exception("Could not read PHP input.");
}
yield $data;
}
fclose($fh);
}
}

View File

@ -48,7 +48,15 @@ class RestResponseComponent extends Component
'mandatory' => array('returnFormat'),
'optional' => array('page', 'limit', 'value' , 'type', 'category', 'org', 'tags', 'date', 'last', 'eventid', 'withAttachments', 'uuid', 'publish_timestamp', 'timestamp', 'attribute_timestamp', 'enforceWarninglist', 'to_ids', 'deleted', 'includeEventUuid', 'includeEventTags', 'event_timestamp', 'threat_level_id', 'eventinfo', 'includeProposals', 'includeDecayScore', 'includeFullModel', 'decayingModel', 'excludeDecayed', 'score', 'first_seen', 'last_seen'),
'params' => array()
)
),
'addTag' => array(
'description' => "Add a tag or a tag collection to an attribute.",
'mandatory' => array('attribute', 'tag')
),
'removeTag' => array(
'description' => "Remove a tag from an attribute.",
'mandatory' => array('attribute', 'tag')
),
),
'Community' => array(
'requestAccess' => array(
@ -80,7 +88,15 @@ class RestResponseComponent extends Component
'mandatory' => array('returnFormat'),
'optional' => array('page', 'limit', 'value', 'type', 'category', 'org', 'tag', 'tags', 'searchall', 'date', 'last', 'eventid', 'withAttachments', 'metadata', 'uuid', 'published', 'publish_timestamp', 'timestamp', 'enforceWarninglist', 'sgReferenceOnly', 'eventinfo', 'excludeLocalTags', 'threat_level_id'),
'params' => array()
)
),
'addTag' => array(
'description' => "Add a tag or a tag collection to an event.",
'mandatory' => array('event', 'tag')
),
'removeTag' => array(
'description' => "Remove a tag from an event.",
'mandatory' => array('event', 'tag')
),
),
'EventGraph' => array(
'add' => array(
@ -270,10 +286,9 @@ class RestResponseComponent extends Component
'optional' => array('name', 'colour', 'exportable', 'hide_tag', 'org_id', 'user_id'),
'params' => array('tag_id')
),
'removeTag' => array(
'description' => "POST a request object in JSON format to this API to create detach a tag from an event. #FIXME Function does not exists",
'mandatory' => array('event', 'tag'),
'params' => array('tag_id')
'removeTagFromObject' => array(
'description' => "Untag an event or attribute. Tag can be the id or the name.",
'mandatory' => array('uuid', 'tag')
),
'attachTagToObject' => array(
'description' => "Attach a Tag to an object, refenced by an UUID. Tag can either be a tag id or a tag name.",
@ -532,7 +547,9 @@ class RestResponseComponent extends Component
$response['sql_dump'] = $this->Log->getDataSource()->getLog(false, false);
}
}
$response = json_encode($response, JSON_PRETTY_PRINT);
// Do not pretty print response for automatic tools
$flags = $this->isAutomaticTool() ? JSON_UNESCAPED_UNICODE : JSON_PRETTY_PRINT;
$response = json_encode($response, $flags);
} else {
if ($dumpSql) {
$this->Log = ClassRegistry::init('Log');
@ -582,6 +599,16 @@ class RestResponseComponent extends Component
return $cakeResponse;
}
/**
* Detect if request comes from automatic tool, like other MISP instance or PyMISP
* @return bool
*/
public function isAutomaticTool()
{
$userAgent = CakeRequest::header('User-Agent');
return $userAgent && (substr($userAgent, 0, 6) === 'PyMISP' || substr($userAgent, 0, 4) === 'MISP');
}
private function __generateURL($action, $controller, $id)
{
$controller = Inflector::underscore(Inflector::pluralize($controller));
@ -932,6 +959,13 @@ class RestResponseComponent extends Component
'values' => array(1 => 'True', 0 => 'False' ),
'help' => __('Should the warning list be enforced. Adds `blocked` field for matching attributes')
),
'event' => array(
'input' => 'number',
'type' => 'integer',
'operators' => array('equal', 'not_equal'),
'validation' => array('min' => 0, 'step' => 1),
'help' => __('Event id')
),
'event_id' => array(
'input' => 'number',
'type' => 'integer',
@ -1659,12 +1693,6 @@ class RestResponseComponent extends Component
'operators' => array('equal'),
'help' => __('Not supported (warninglist->checkvalues) expect an array')
),
'event' => array(
'input' => 'text',
'type' => 'string',
'operators' => array('equal'),
'help' => __('Not supported (removeTag)')
),
'push_rules' => array(
'input' => 'text',
'type' => 'string',
@ -1789,6 +1817,9 @@ class RestResponseComponent extends Component
case "last_seen":
$this->__overwriteSeen($scope, $action, $fieldsConstraint[$field]);
break;
case "attribute":
$this->__overwriteAttribute($scope, $action, $fieldsConstraint[$field]);
break;
default:
break;
}
@ -1806,8 +1837,6 @@ class RestResponseComponent extends Component
private function __overwriteReturnFormat($scope, $action, &$field) {
switch($scope) {
case "Attribute":
$field['values'] = array_keys(ClassRegistry::init($scope)->validFormats);
break;
case "Event":
$field['values'] = array_keys(ClassRegistry::init($scope)->validFormats);
break;
@ -1872,6 +1901,11 @@ class RestResponseComponent extends Component
$field['help'] = __('Also supports array of tags');
}
}
private function __overwriteAttribute($scope, $action, &$field){
if ($action == 'addTag' || $action == 'removeTag') {
$field['help'] = __('Attribute id');
}
}
private function __overwriteNationality($scope, $action, &$field) {
$field['values'] = ClassRegistry::init("Organisation")->getCountries();
}
@ -1880,10 +1914,13 @@ class RestResponseComponent extends Component
}
private function __overwriteRoleId($scope, $action, &$field) {
$this->{$scope} = ClassRegistry::init("Role");
$roles = $this->{$scope}->find('column', array(
'fields' => array('name')
$roles = $this->{$scope}->find('list', array(
'fields' => array('id', 'name')
));
$field['values'] = $roles;
$field['values'] = [];
foreach ($roles as $id => $name) {
$field['values'][] = ['label' => $name, 'value' => $id];
}
}
private function __overwriteSeen($scope, $action, &$field) {
if ($action == 'restSearch') {

View File

@ -1,6 +1,9 @@
<?php
App::uses('AppController', 'Controller');
/**
* @property Dashboard $Dashboard
*/
class DashboardsController extends AppController
{
public $components = array('Session', 'RequestHandler');
@ -147,47 +150,50 @@ class DashboardsController extends AppController
public function renderWidget($widget_id, $force = false)
{
if ($this->request->is('post')) {
if (empty($this->request->data['data'])) {
$this->request->data = array('data' => $this->request->data);
}
if (empty($this->request->data['data'])) {
throw new MethodNotAllowedException(__('You need to specify the widget to use along with the configuration.'));
}
$value = $this->request->data['data'];
$dashboardWidget = $this->Dashboard->loadWidget($this->Auth->user(), $value['widget']);
$this->layout = false;
$this->set('title', $dashboardWidget->title);
$redis = $this->Dashboard->setupRedis();
$org_scope = $this->_isSiteAdmin() ? 0 : $this->Auth->user('org_id');
$lookup_hash = hash('sha256', $value['widget'] . $value['config']);
$data = $redis->get('misp:dashboard:' . $org_scope . ':' . $lookup_hash);
if (!isset($dashboardWidget->cacheLifetime)) {
$dashboardWidget->cacheLifetime = false;
}
if (empty($dashboardWidget->cacheLifetime) || empty($data)) {
$data = $dashboardWidget->handler($this->Auth->user(), json_decode($value['config'], true));
if (!empty($dashboardWidget->cacheLifetime)) {
$redis->set('misp:dashboard:' . $org_scope . ':' . $lookup_hash, json_encode(array('data' => $data)));
$redis->expire('misp:dashboard:' . $org_scope . ':' . $lookup_hash, $dashboardWidget->cacheLifetime);
}
} else {
$data = json_decode($data, true)['data'];
}
$valueConfig = json_decode($value['config'], true);
$config = array(
'render' => $dashboardWidget->render,
'autoRefreshDelay' => empty($dashboardWidget->autoRefreshDelay) ? false : $dashboardWidget->autoRefreshDelay,
'widget_config' => empty($valueConfig['widget_config']) ? array() : $valueConfig['widget_config']
);
$this->set('widget_id', $widget_id);
$this->set('data', $data);
$this->set('config', $config);
$this->render('widget_loader');
} else {
if (!$this->request->is('post')) {
throw new MethodNotAllowedException(__('This endpoint can only be reached via POST requests.'));
}
@session_write_close(); // allow concurrent AJAX requests (session hold lock by default)
if (empty($this->request->data['data'])) {
$this->request->data = array('data' => $this->request->data);
}
if (empty($this->request->data['data'])) {
throw new MethodNotAllowedException(__('You need to specify the widget to use along with the configuration.'));
}
$value = $this->request->data['data'];
$valueConfig = json_decode($value['config'], true);
$dashboardWidget = $this->Dashboard->loadWidget($this->Auth->user(), $value['widget']);
$redis = $this->Dashboard->setupRedis();
$org_scope = $this->_isSiteAdmin() ? 0 : $this->Auth->user('org_id');
$lookup_hash = hash('sha256', $value['widget'] . $value['config']);
$cacheKey = 'misp:dashboard:' . $org_scope . ':' . $lookup_hash;
$data = $redis->get($cacheKey);
if (!isset($dashboardWidget->cacheLifetime)) {
$dashboardWidget->cacheLifetime = false;
}
if (empty($dashboardWidget->cacheLifetime) || empty($data)) {
$data = $dashboardWidget->handler($this->Auth->user(), $valueConfig);
if (!empty($dashboardWidget->cacheLifetime)) {
$redis->setex($cacheKey, $dashboardWidget->cacheLifetime, json_encode(array('data' => $data)));
}
} else {
$data = json_decode($data, true)['data'];
}
$config = array(
'render' => $dashboardWidget->render,
'autoRefreshDelay' => empty($dashboardWidget->autoRefreshDelay) ? false : $dashboardWidget->autoRefreshDelay,
'widget_config' => empty($valueConfig['widget_config']) ? array() : $valueConfig['widget_config']
);
$this->layout = false;
$this->set('title', $dashboardWidget->title);
$this->set('widget_id', $widget_id);
$this->set('data', $data);
$this->set('config', $config);
$this->render('widget_loader');
}
public function import()
@ -392,7 +398,7 @@ class DashboardsController extends AppController
$element['User']['email'] = '';
}
}
$this->set('passedArgs', $this->passedArgs);
$this->set('passedArgs', json_encode($this->passedArgs));
$this->set('data', $data);
}
}

View File

@ -50,6 +50,37 @@ class EventGraphController extends AppController
return $this->RestResponse->viewData($eventGraphs, $this->response->type());
}
public function viewPicture($event_id, $graph_id)
{
$this->loadModel('Event');
$event = $this->Event->fetchSimpleEvent($this->Auth->user(), $event_id);
if (empty($event)) {
throw new NotFoundException('Invalid event');
}
$conditions = [
'EventGraph.event_id' => $event['Event']['id'],
'EventGraph.org_id' => $this->Auth->user('org_id'),
'EventGraph.id' => $graph_id,
];
$eventGraph = $this->EventGraph->find('first', array(
'conditions' => $conditions,
'contain' => array(
'User' => array(
'fields' => array(
'User.email'
)
)
)
));
if (empty($eventGraph)) {
throw new MethodNotAllowedException('Invalid event graph');
}
$eventGraph = $eventGraph;
$imageData = $this->EventGraph->getPictureData($eventGraph);
return new CakeResponse(array('body' => $imageData, 'type' => 'png'));
}
public function add($event_id = false)
{
if ($this->request->is('get')) {

View File

@ -124,12 +124,14 @@ class EventReportsController extends AppController
{
$report = $this->EventReport->fetchIfAuthorized($this->Auth->user(), $id, 'delete', $throwErrors=true, $full=false);
if ($this->request->is('post')) {
$errors = $this->EventReport->deleteReport($this->Auth->user(), $report, $hard=$hard);
if (!empty($this->request->data['hard'])) {
$hard = true;
}
$errors = $this->EventReport->deleteReport($this->Auth->user(), $report, $hard);
$redirectTarget = $this->referer();
if (empty($errors)) {
$successMessage = __('Event Report %s %s deleted', $id, $hard ? __('hard') : __('soft'));
$report = $hard ? null : $this->EventReport->simpleFetchById($this->Auth->user(), $id);
return $this->__getSuccessResponseBasedOnContext($successMessage, $report, 'delete', $id, $redirectTarget);
return $this->__getSuccessResponseBasedOnContext($successMessage, null, 'delete', $id, $redirectTarget);
} else {
$errorMessage = __('Event Report %s could not be %s deleted.%sReasons: %s', $id, $hard ? __('hard') : __('soft'), PHP_EOL, json_encode($errors));
return $this->__getFailResponseBasedOnContext($errorMessage, array(), 'edit', $id, $redirectTarget);
@ -153,8 +155,7 @@ class EventReportsController extends AppController
$redirectTarget = $this->referer();
if (empty($errors)) {
$successMessage = __('Event Report %s restored', $id);
$report = $this->EventReport->simpleFetchById($this->Auth->user(), $id);
return $this->__getSuccessResponseBasedOnContext($successMessage, $report, 'restore', $id, $redirectTarget);
return $this->__getSuccessResponseBasedOnContext($successMessage, null, 'restore', $id, $redirectTarget);
} else {
$errorMessage = __('Event Report %s could not be %s restored.%sReasons: %s', $id, PHP_EOL, json_encode($errors));
return $this->__getFailResponseBasedOnContext($errorMessage, array(), 'restore', $id, $redirectTarget);

View File

@ -590,13 +590,7 @@ class EventsController extends AppController
$searchTermInternal = $searchTerm;
if ($searchTerm == 'threatlevel') {
$searchTermInternal = 'threat_level_id';
$threatLevels = $this->Event->ThreatLevel->find('all', array(
'recursive' => -1,
'fields' => array('id', 'name'),
));
foreach ($threatLevels as $tl) {
$terms[$tl['ThreatLevel']['id']] = $tl['ThreatLevel']['name'];
}
$terms = $this->Event->ThreatLevel->list();
} elseif ($searchTerm == 'analysis') {
$terms = $this->Event->analysisLevels;
} else {
@ -767,7 +761,7 @@ class EventsController extends AppController
}
}
}
$events = $this->GalaxyCluster->attachClustersToEventIndex($this->Auth->user(), $events, false, false);
$events = $this->GalaxyCluster->attachClustersToEventIndex($this->Auth->user(), $events, false);
}
foreach ($events as $key => $event) {
if (empty($event['SharingGroup']['name'])) {
@ -830,7 +824,7 @@ class EventsController extends AppController
if (Configure::read('MISP.showDiscussionsCountOnIndex')) {
$events = $this->Event->attachDiscussionsCountToEvents($this->Auth->user(), $events);
}
$events = $this->GalaxyCluster->attachClustersToEventIndex($this->Auth->user(), $events, true, false);
$events = $this->GalaxyCluster->attachClustersToEventIndex($this->Auth->user(), $events, true);
if ($this->params['ext'] === 'csv') {
App::uses('CsvExport', 'Export');
@ -838,27 +832,7 @@ class EventsController extends AppController
return $this->RestResponse->viewData($export->eventIndex($events), 'csv');
}
$user = $this->Auth->user();
$user = $this->Event->User->fillKeysToUser($user);
if (empty($user['gpgkey']) && Configure::read('GnuPG.onlyencrypted')) {
// No GnuPG
if (Configure::read('SMIME.enabled') && empty($user['certif_public'])) {
// No GnuPG and No SMIME
$this->Flash->info(__('No X.509 certificate or GnuPG key set in your profile. To receive emails, submit your public certificate or GnuPG key in your profile.'));
} elseif (!Configure::read('SMIME.enabled')) {
$this->Flash->info(__('No GnuPG key set in your profile. To receive emails, submit your public key in your profile.'));
}
} elseif ($this->Auth->user('autoalert') && empty($user['gpgkey']) && Configure::read('GnuPG.bodyonlyencrypted')) {
// No GnuPG & autoalert
if ($this->Auth->user('autoalert') && Configure::read('SMIME.enabled') && empty($user['certif_public'])) {
// No GnuPG and No SMIME & autoalert
$this->Flash->info(__('No X.509 certificate or GnuPG key set in your profile. To receive attributes in emails, submit your public certificate or GnuPG key in your profile.'));
} elseif (!Configure::read('SMIME.enabled')) {
$this->Flash->info(__('No GnuPG key set in your profile. To receive attributes in emails, submit your public key in your profile.'));
}
}
$this->__noKeyNotification();
$this->set('events', $events);
$this->set('eventDescriptions', $this->Event->fieldDescriptions);
$this->set('analysisLevels', $this->Event->analysisLevels);
@ -876,6 +850,34 @@ class EventsController extends AppController
}
}
private function __noKeyNotification()
{
$onlyEncrypted = Configure::read('GnuPG.onlyencrypted');
$bodyOnlyEncrypted = Configure::read('GnuPG.bodyonlyencrypted');
if (!$onlyEncrypted && !$bodyOnlyEncrypted) {
return;
}
$user = $this->Event->User->fillKeysToUser($this->Auth->user());
if (!empty($user['gpgkey'])) {
return; // use has PGP key
}
if ($onlyEncrypted) {
if (Configure::read('SMIME.enabled') && empty($user['certif_public'])) {
$this->Flash->info(__('No X.509 certificate or PGP key set in your profile. To receive emails, submit your public certificate or PGP key in your profile.'));
} elseif (!Configure::read('SMIME.enabled')) {
$this->Flash->info(__('No PGP key set in your profile. To receive emails, submit your public key in your profile.'));
}
} elseif ($bodyOnlyEncrypted && $user['autoalert']) {
if (Configure::read('SMIME.enabled') && empty($user['certif_public'])) {
$this->Flash->info(__('No X.509 certificate or PGP key set in your profile. To receive attributes in emails, submit your public certificate or PGP key in your profile.'));
} elseif (!Configure::read('SMIME.enabled')) {
$this->Flash->info(__('No PGP key set in your profile. To receive attributes in emails, submit your public key in your profile.'));
}
}
}
public function filterEventIndex()
{
$passedArgsArray = array();
@ -1025,7 +1027,17 @@ class EventsController extends AppController
if (isset($filters['focus'])) {
$this->set('focus', $filters['focus']);
}
$conditions = array('eventid' => $id);
$conditions = [
'eventid' => $id,
'includeFeedCorrelations' => true,
'includeWarninglistHits' => true,
'fetchFullClusters' => false,
'includeAllTags' => true,
'includeGranularCorrelations' => true,
'includeEventCorrelations' => false,
'noEventReports' => true, // event reports for view are loaded dynamically
'noSightings' => true,
];
if (isset($filters['extended'])) {
$conditions['extended'] = 1;
$this->set('extended', 1);
@ -1048,21 +1060,11 @@ class EventsController extends AppController
if (isset($filters['toIDS']) && $filters['toIDS'] != 0) {
$conditions['to_ids'] = $filters['toIDS'] == 2 ? 0 : 1;
}
$conditions['includeFeedCorrelations'] = true;
$conditions['includeWarninglistHits'] = true;
if (!isset($filters['includeServerCorrelations'])) {
$conditions['includeServerCorrelations'] = 1;
if ($this->_isRest()) {
$conditions['includeServerCorrelations'] = 0;
}
} else {
$conditions['includeServerCorrelations'] = $filters['includeServerCorrelations'];
}
$conditions['includeAllTags'] = true;
$conditions['includeGranularCorrelations'] = 1;
$conditions['includeEventCorrelations'] = false;
$conditions['noEventReports'] = true; // event reports for view are loaded dynamically
$conditions['noSightings'] = true;
if (!empty($filters['includeRelatedTags'])) {
$this->set('includeRelatedTags', 1);
$conditions['includeRelatedTags'] = 1;
@ -1224,6 +1226,11 @@ class EventsController extends AppController
$this->render('/Elements/eventattribute');
}
/**
* @param array $event
* @param bool $continue
* @param int $fromEvent
*/
private function __viewUI($event, $continue, $fromEvent)
{
$this->loadModel('Taxonomy');
@ -1278,9 +1285,9 @@ class EventsController extends AppController
// set the pivot data
$this->helpers[] = 'Pivot';
if ($continue) {
$data = $this->__continuePivoting($event['Event']['id'], $event['Event']['info'], $event['Event']['date'], $fromEvent);
$this->__continuePivoting($event['Event']['id'], $event['Event']['info'], $event['Event']['date'], $fromEvent);
} else {
$data = $this->__startPivoting($event['Event']['id'], $event['Event']['info'], $event['Event']['date']);
$this->__startPivoting($event['Event']['id'], $event['Event']['info'], $event['Event']['date']);
}
$pivot = $this->Session->read('pivot_thread');
$this->__arrangePivotVertical($pivot);
@ -1479,7 +1486,7 @@ class EventsController extends AppController
$this->set('includeSightingdb', (!empty($filters['includeSightingdb']) && Configure::read('Plugin.Sightings_sighting_db_enable')));
$this->set('relatedEventCorrelationCount', $relatedEventCorrelationCount);
$this->set('oldest_timestamp', $oldest_timestamp);
$this->set('required_taxonomies', $this->Event->getRequiredTaxonomies());
$this->set('missingTaxonomies', $this->Event->missingTaxonomies($event));
$this->set('orgTable', $orgTable);
$this->set('currentUri', $attributeUri);
$this->set('filters', $filters);
@ -1512,6 +1519,7 @@ class EventsController extends AppController
$conditions['includeAllTags'] = true;
$conditions['noEventReports'] = true; // event reports for view are loaded dynamically
$conditions['noSightings'] = true;
$conditions['fetchFullClusters'] = false;
}
$deleted = 0;
if (isset($this->params['named']['deleted'])) {
@ -1612,37 +1620,74 @@ class EventsController extends AppController
}
if ($this->_isRest()) {
$this->set('event', $event);
} else {
$this->set('deleted', isset($deleted) ? ($deleted == 2 ? 0 : 1) : 0);
$this->set('includeRelatedTags', (!empty($this->params['named']['includeRelatedTags'])) ? 1 : 0);
$this->set('includeDecayScore', (!empty($this->params['named']['includeDecayScore'])) ? 1 : 0);
if ($this->_isSiteAdmin() && $event['Event']['orgc_id'] !== $this->Auth->user('org_id')) {
$this->Flash->info(__('You are currently logged in as a site administrator and about to edit an event not belonging to your organisation. This goes against the sharing model of MISP. Use a normal user account for day to day work.'));
}
$this->__viewUI($event, $continue, $fromEvent);
return $this->__restResponse($event);
}
$this->set('deleted', isset($deleted) ? ($deleted == 2 ? 0 : 1) : 0);
$this->set('includeRelatedTags', (!empty($this->params['named']['includeRelatedTags'])) ? 1 : 0);
$this->set('includeDecayScore', (!empty($this->params['named']['includeDecayScore'])) ? 1 : 0);
if ($this->_isSiteAdmin() && $event['Event']['orgc_id'] !== $this->Auth->user('org_id')) {
$this->Flash->info(__('You are currently logged in as a site administrator and about to edit an event not belonging to your organisation. This goes against the sharing model of MISP. Use a normal user account for day to day work.'));
}
$this->__viewUI($event, $continue, $fromEvent);
}
/**
* @param int $id
* @param string $info
* @param string $date
*/
private function __startPivoting($id, $info, $date)
{
$this->Session->write('pivot_thread', null);
$initial_pivot = array('id' => $id, 'info' => $info, 'date' => $date, 'depth' => 0, 'height' => 0, 'children' => array(), 'deletable' => true);
$this->Session->write('pivot_thread', $initial_pivot);
$initialPivot = [
'id' => $id,
'info' => $info,
'date' => $date,
'depth' => 0,
'height' => 0,
'children' => [],
'deletable' => true,
];
$this->Session->write('pivot_thread', $initialPivot);
}
/**
* @param int $id
* @param string $info
* @param string $date
* @param int $fromEvent
*/
private function __continuePivoting($id, $info, $date, $fromEvent)
{
$pivot = $this->Session->read('pivot_thread');
$newPivot = array('id' => $id, 'info' => $info, 'date' => $date, 'depth' => null, 'children' => array(), 'deletable' => true);
if (!is_array($pivot)) {
$this->__startPivoting($id, $info, $date);
return;
}
$newPivot = [
'id' => $id,
'info' => $info,
'date' => $date,
'depth' => null,
'children' => [],
'deletable' => true,
];
if (!$this->__checkForPivot($pivot, $id)) {
$pivot = $this->__insertPivot($pivot, $fromEvent, $newPivot, 0);
}
$this->Session->write('pivot_thread', $pivot);
}
private function __insertPivot($pivot, $oldId, $newPivot, $depth)
/**
* @param array $pivot
* @param int $oldId
* @param array $newPivot
* @param int $depth
* @return array
*/
private function __insertPivot(array $pivot, $oldId, array $newPivot, $depth)
{
$depth++;
if ($pivot['id'] == $oldId) {
@ -1658,7 +1703,12 @@ class EventsController extends AppController
return $pivot;
}
private function __checkForPivot($pivot, $id)
/**
* @param array $pivot
* @param int $id
* @return bool
*/
private function __checkForPivot(array $pivot, $id)
{
if ($id == $pivot['id']) {
return true;
@ -1741,6 +1791,9 @@ class EventsController extends AppController
// search for all attributes in object
foreach ($event['Object'] as $k => $object) {
if ($this->__valueInFieldAttribute($object, ['id', 'uuid', 'name', 'comment'], $searchFor)) {
continue;
}
foreach ($object['Attribute'] as $k2 => $attribute) {
if (!$this->__valueInFieldAttribute($attribute, $filterValue, $searchFor)) {
unset($event['Object'][$k]['Attribute'][$k2]);
@ -1914,9 +1967,7 @@ class EventsController extends AppController
if (!empty($validationErrors)) {
$event['errors'] = $validationErrors;
}
$this->set('event', $event);
$this->render('view');
return true;
return $this->__restResponse($event);
} else {
// redirect to the view of the newly created event
$this->Flash->success(__('The event has been saved'));
@ -2168,9 +2219,12 @@ class EventsController extends AppController
if (empty($source_event)) {
throw new NotFoundException(__('Invalid source event.'));
}
$recovered_uuids = [];
foreach ($source_event[0]['Attribute'] as &$attribute) {
unset($attribute['id']);
$originalUUID = $attribute['uuid'];
$attribute['uuid'] = CakeText::uuid();
$recovered_uuids[$originalUUID] = $attribute['uuid'];
unset($attribute['ShadowAttribute']);
$attribute['Tag'] = [];
foreach ($attribute['AttributeTag'] as $aT) {
@ -2181,10 +2235,14 @@ class EventsController extends AppController
}
foreach ($source_event[0]['Object'] as &$object) {
unset($object['id']);
$originalUUID = $object['uuid'];
$object['uuid'] = CakeText::uuid();
$recovered_uuids[$originalUUID] = $object['uuid'];
foreach ($object['Attribute'] as &$attribute) {
unset($attribute['id']);
$originalUUID = $attribute['uuid'];
$attribute['uuid'] = CakeText::uuid();
$recovered_uuids[$originalUUID] = $attribute['uuid'];
unset($attribute['ShadowAttribute']);
$attribute['Tag'] = [];
foreach ($attribute['AttributeTag'] as $aT) {
@ -2194,21 +2252,39 @@ class EventsController extends AppController
unset($attribute['AttributeTag']);
}
}
foreach ($source_event[0]['Object'] as &$object) {
foreach ($object['ObjectReference'] as &$reference) {
if (isset($recovered_uuids[$object['uuid']])) {
$reference['object_uuid'] = $recovered_uuids[$object['uuid']];
}
if (isset($recovered_uuids[$reference['referenced_uuid']])) {
$reference['referenced_uuid'] = $recovered_uuids[$reference['referenced_uuid']];
}
}
}
foreach ($source_event[0]['EventReport'] as &$report) {
unset($report['id'], $report['event_id']);
$report['uuid'] = CakeText::uuid();
}
$results = [
'results' => [
'Object' => $source_event[0]['Object'],
'Attribute' => $source_event[0]['Attribute']
'Attribute' => $source_event[0]['Attribute'],
'EventReport' => $source_event[0]['EventReport']
]
];
if ($this->_isRest()) {
$this->loadModel('Log');
$save_results = ['attributes' => 0, 'objects' => 0];
$save_results = ['attributes' => 0, 'objects' => 0, 'eventReports' => 0];
foreach ($results['results']['Attribute'] as $attribute) {
$this->Event->Attribute->captureAttribute($attribute, $target_id, $this->Auth->user(), false, $this->Log);
}
foreach ($results['results']['Object'] as $object) {
$this->Event->Object->captureObject($object, $target_id, $this->Auth->user(), $this->Log);
}
foreach ($results['results']['EventReport'] as $report) {
$this->Event->EventReport->captureReport($this->Auth->user(), $report, $target_id);
}
$event = $this->Event->fetchEvent(
$this->Auth->user(),
[
@ -2279,9 +2355,7 @@ class EventsController extends AppController
$metadata = $this->request->param('named.metadata');
$results = $this->Event->fetchEvent($this->Auth->user(), ['eventid' => $id, 'metadata' => $metadata]);
$event = $results[0];
$this->set('event', $event);
$this->render('view');
return true;
return $this->__restResponse($event);
} else {
$message = 'Error';
if ($this->_isRest()) {
@ -3699,9 +3773,6 @@ class EventsController extends AppController
public function filterEventIdsForPush()
{
if (!$this->userRole['perm_sync']) {
throw new MethodNotAllowedException(__('You do not have the permission to do that.'));
}
if ($this->request->is('post')) {
$incomingIDs = array();
$incomingEvents = array();
@ -3714,16 +3785,16 @@ class EventsController extends AppController
'recursive' => -1,
'fields' => array('Event.uuid', 'Event.timestamp', 'Event.locked'),
));
foreach ($events as $k => $v) {
if ($v['Event']['timestamp'] >= $incomingEvents[$v['Event']['uuid']]) {
unset($incomingEvents[$v['Event']['uuid']]);
foreach ($events as $event) {
if ($event['Event']['timestamp'] >= $incomingEvents[$event['Event']['uuid']]) {
unset($incomingEvents[$event['Event']['uuid']]);
continue;
}
if ($v['Event']['locked'] == 0) {
unset($incomingEvents[$v['Event']['uuid']]);
if ($event['Event']['locked'] == 0) {
unset($incomingEvents[$event['Event']['uuid']]);
}
}
$this->set('result', array_keys($incomingEvents));
return $this->RestResponse->viewData(array_keys($incomingEvents), $this->response->type());
}
}
@ -4506,16 +4577,23 @@ class EventsController extends AppController
if ($scope == 'event') {
$eventId = $scope_id;
} elseif ($scope == 'attribute') {
$attribute = $this->Event->Attribute->fetchAttributes($this->Auth->user(), array(
'conditions' => array('Attribute.id' => $scope_id),
'fields' => array('event_id'),
'flatten' => 1,
));
if (empty($attribute)) {
throw new Exception("Invalid Attribute.");
if ($scope_id == 'selected') {
if (empty($this->params['named']['eventid'])) {
throw new Exception("Invalid Event.");
}
$eventId = $this->params['named']['eventid'];
} else {
$attribute = $this->Event->Attribute->fetchAttributes($this->Auth->user(), array(
'conditions' => array('Attribute.id' => $scope_id),
'fields' => array('event_id'),
'flatten' => 1,
));
if (empty($attribute)) {
throw new Exception("Invalid Attribute.");
}
$attribute = $attribute[0];
$eventId = $attribute['Attribute']['event_id'];
}
$attribute = $attribute[0];
$eventId = $attribute['Attribute']['event_id'];
} elseif ($scope == 'tag_collection') {
$eventId = 0; // no event_id for tag_collection, consider all events
} else {
@ -4641,8 +4719,7 @@ class EventsController extends AppController
$this->loadModel('GalaxyCluster');
$clusters = $this->GalaxyCluster->fetchGalaxyClusters($this->Auth->user(), array('conditions' => array('GalaxyCluster.id' => $clusterIds)), $full=true);
App::uses('ClusterRelationsGraphTool', 'Tools');
$grapher = new ClusterRelationsGraphTool();
$grapher->construct($this->Auth->user(), $this->GalaxyCluster);
$grapher = new ClusterRelationsGraphTool($this->Auth->user(), $this->GalaxyCluster);
$relations = $grapher->getNetwork($clusters, $keepNotLinkedClusters=true, $includeReferencingRelation=true);
if ($this->_isRest()) {
return $this->RestResponse->viewData($relations, $this->response->type());
@ -4682,8 +4759,7 @@ class EventsController extends AppController
);
$this->set('events', $this->paginate());
$threat_levels = $this->Event->ThreatLevel->find('all');
$this->set('threatLevels', Set::combine($threat_levels, '{n}.ThreatLevel.id', '{n}.ThreatLevel.name'));
$this->set('threatLevels', $this->Event->ThreatLevel->list());
$this->set('eventDescriptions', $this->Event->fieldDescriptions);
$this->set('analysisLevels', $this->Event->analysisLevels);
$this->set('distributionLevels', $this->Event->distributionLevels);
@ -4910,6 +4986,7 @@ class EventsController extends AppController
if (!$event) {
throw new NotFoundException(__('Invalid event.'));
}
$mayModify = $this->__canModifyEvent($event);
$eventId = $event['Event']['id'];
$this->loadModel('Module');
@ -5080,7 +5157,7 @@ class EventsController extends AppController
$this->set('module', $module);
$this->set('eventId', $eventId);
$this->set('event', $event);
$this->set('mayModify', $this->__canModifyEvent($event));
$this->set('mayModify', $mayModify);
}
public function exportModule($module, $id, $standard = false)
@ -5141,7 +5218,7 @@ class EventsController extends AppController
if ($this->request->is('Post')) {
if (Configure::read('Plugin.ZeroMQ_enable')) {
$pubSubTool = $this->Event->getPubSubTool();
$event = $this->Event->fetchEvent($this->Auth->user(), array('eventid' => $id));
$event = $this->Event->fetchEvent($this->Auth->user(), array('eventid' => $id, 'includeAllTags' => true));
if (!empty($event)) {
$pubSubTool->publishEvent($event[0]);
$success = 1;
@ -5188,7 +5265,7 @@ class EventsController extends AppController
$kafkaPubTopic = Configure::read('Plugin.Kafka_event_publish_notifications_topic');
if (!empty($event['Event']['published']) && Configure::read('Plugin.Kafka_event_publish_notifications_enable') && !empty($kafkaPubTopic)) {
$kafkaPubTool = $this->Event->getKafkaPubTool();
$params = array('eventid' => $id);
$params = array('eventid' => $id, 'includeAllTags' => true);
if (Configure::read('Plugin.Kafka_include_attachments')) {
$params['includeAttachments'] = 1;
}
@ -5284,9 +5361,43 @@ class EventsController extends AppController
}
}
public function addEventLock($id)
{
if (!$this->request->is('post')) {
throw new MethodNotAllowedException('This endpoint requires a POST request.');
}
$event = $this->Event->fetchSimpleEvent($this->Auth->user(), $id);
if (empty($event)) {
throw new MethodNotAllowedException(__('Invalid Event'));
}
if (!$this->__canModifyEvent($event)) {
throw new UnauthorizedException(__('You do not have permission to do that.'));
}
$this->loadModel('EventLock');
$lockId = $this->EventLock->insertLockApi($event['Event']['id'], $this->Auth->user());
return $this->RestResponse->viewData(['lock_id' => $lockId], $this->response->type());
}
public function removeEventLock($id, $lockId)
{
if (!$this->request->is('post')) {
throw new MethodNotAllowedException('This endpoint requires a POST request.');
}
$event = $this->Event->fetchSimpleEvent($this->Auth->User(), $id);
if (empty($event)) {
throw new MethodNotAllowedException(__('Invalid Event'));
}
$this->loadModel('EventLock');
$deleted = $this->EventLock->deleteApiLock($event['Event']['id'], $lockId, $this->Auth->user());
return $this->RestResponse->viewData(['deleted' => $deleted], $this->response->type());
}
public function checkLocks($id)
{
$this->loadModel('EventLock');
$event = $this->Event->find('first', array(
'recursive' => -1,
'conditions' => array('Event.id' => $id),
@ -5294,29 +5405,34 @@ class EventsController extends AppController
));
$locks = array();
if (!empty($event) && ($event['Event']['orgc_id'] == $this->Auth->user('org_id') || $this->_isSiteAdmin())) {
$this->loadModel('EventLock');
$locks = $this->EventLock->checkLock($this->Auth->user(), $id);
}
if (!empty($locks)) {
$temp = $locks;
$locks = array();
foreach ($temp as $t) {
if ($t['User']['id'] !== $this->Auth->user('id')) {
if ($t['type'] === 'user' && $t['User']['id'] !== $this->Auth->user('id')) {
if (!$this->_isSiteAdmin() && $t['User']['org_id'] != $this->Auth->user('org_id')) {
continue;
$locks[] = __('another user');
} else {
$locks[] = $t['User']['email'];
}
$locks[] = $t['User']['email'];
} else if ($t['type'] === 'job') {
$locks[] = __('background job');
} else if ($t['type'] === 'api') {
$locks[] = __('external tool');
}
}
}
// TODO: i18n
if (!empty($locks)) {
$message = sprintf('Warning: Your view on this event might not be up to date as it is currently being edited by: %s', implode(', ', $locks));
$this->set('message', $message);
$this->layout = false;
$this->render('/Events/ajax/event_lock');
} else {
if (empty($locks)) {
return $this->RestResponse->viewData('', $this->response->type(), false, true);
}
$message = __('Warning: Your view on this event might not be up to date as it is currently being edited by: %s', implode(', ', $locks));
$this->set('message', $message);
$this->layout = false;
$this->render('/Events/ajax/event_lock');
}
public function getEditStrategy($id)
@ -5596,14 +5712,14 @@ class EventsController extends AppController
$job = ClassRegistry::init('Job');
$job->create();
$data = array(
'worker' => $this->Event->__getPrioWorkerIfPossible(),
'job_type' => $job_type,
'job_input' => sprintf('Event ID: %s', $id),
'status' => 0,
'retries' => 0,
'org_id' => 0,
'org' => 'ADMIN',
'message' => $message
'worker' => 'prio',
'job_type' => $job_type,
'job_input' => sprintf('Event ID: %s', $id),
'status' => 0,
'retries' => 0,
'org_id' => 0,
'org' => 'ADMIN',
'message' => $message
);
$job->save($data);
$jobId = $job->id;
@ -5681,4 +5797,37 @@ class EventsController extends AppController
}
return $this->RestResponse->viewData($allConflicts);
}
/**
* @param array $event
* @return CakeResponseTmp
* @throws Exception
*/
private function __restResponse(array $event)
{
$tmpFile = new TmpFileTool();
if ($this->request->is('json')) {
App::uses('JSONConverterTool', 'Tools');
$converter = new JSONConverterTool();
if ($this->RestResponse->isAutomaticTool()) {
foreach ($converter->streamConvert($event) as $part) {
$tmpFile->write($part);
}
} else {
$tmpFile->write($converter->convert($event));
}
$format = 'json';
} elseif ($this->request->is('xml')) {
App::uses('XMLConverterTool', 'Tools');
$converter = new XMLConverterTool();
foreach ($converter->frameCollection($converter->convert($event)) as $chunk) {
$tmpFile->write($chunk);
}
$format = 'xml';
} else {
throw new Exception("Invalid format, only JSON or XML is supported.");
}
return $this->RestResponse->viewData($tmpFile, $format, false, true);
}
}

View File

@ -606,26 +606,30 @@ class FeedsController extends AppController
public function previewIndex($feedId)
{
$this->Feed->id = $feedId;
if (!$this->Feed->exists()) {
$feed = $this->Feed->find('first', [
'conditions' => ['id' => $feedId],
'recursive' => -1,
]);
if (empty($feed)) {
throw new NotFoundException(__('Invalid feed.'));
}
$this->Feed->read();
if (!empty($this->Feed->data['Feed']['settings'])) {
$this->Feed->data['Feed']['settings'] = json_decode($this->Feed->data['Feed']['settings'], true);
if (!empty($feed['Feed']['settings'])) {
$feed['Feed']['settings'] = json_decode($feed['Feed']['settings'], true);
}
$params = array();
if ($this->request->is('post')) {
$params = $this->request->data['Feed'];
}
if ($this->Feed->data['Feed']['source_format'] == 'misp') {
return $this->__previewIndex($this->Feed->data, $params);
} elseif (in_array($this->Feed->data['Feed']['source_format'], array('freetext', 'csv'))) {
return $this->__previewFreetext($this->Feed->data);
if ($feed['Feed']['source_format'] === 'misp') {
return $this->__previewIndex($feed, $params);
} elseif (in_array($feed['Feed']['source_format'], ['freetext', 'csv'], true)) {
return $this->__previewFreetext($feed);
} else {
throw new Exception("Invalid feed format `{$feed['Feed']['source_format']}`.");
}
}
private function __previewIndex($feed, $filterParams = array())
private function __previewIndex(array $feed, $filterParams = array())
{
$urlparams = '';
App::uses('CustomPaginationTool', 'Tools');
@ -690,8 +694,7 @@ class FeedsController extends AppController
}
$this->set('events', $events);
$this->loadModel('Event');
$threat_levels = $this->Event->ThreatLevel->find('all');
$this->set('threatLevels', Set::combine($threat_levels, '{n}.ThreatLevel.id', '{n}.ThreatLevel.name'));
$this->set('threatLevels', $this->Event->ThreatLevel->list());
$this->set('eventDescriptions', $this->Event->fieldDescriptions);
$this->set('analysisLevels', $this->Event->analysisLevels);
$this->set('distributionLevels', $this->Event->distributionLevels);
@ -704,7 +707,7 @@ class FeedsController extends AppController
$this->set('passedArgsArray', $passedArgs);
}
private function __previewFreetext($feed)
private function __previewFreetext(array $feed)
{
if (isset($this->passedArgs['page'])) {
$currentPage = $this->passedArgs['page'];
@ -759,13 +762,15 @@ class FeedsController extends AppController
public function previewEvent($feedId, $eventUuid, $all = false)
{
$this->Feed->id = $feedId;
if (!$this->Feed->exists()) {
$feed = $this->Feed->find('first', [
'conditions' => ['id' => $feedId],
'recursive' => -1,
]);
if (empty($feed)) {
throw new NotFoundException(__('Invalid feed.'));
}
$this->Feed->read();
try {
$event = $this->Feed->downloadEventFromFeed($this->Feed->data, $eventUuid);
$event = $this->Feed->downloadEventFromFeed($feed, $eventUuid);
} catch (Exception $e) {
throw new Exception(__('Could not download the selected Event'), 0, $e);
}
@ -782,11 +787,11 @@ class FeedsController extends AppController
$params = $this->Event->rearrangeEventForView($event, $this->passedArgs, $all);
$this->params->params['paging'] = array('Feed' => $params);
$this->set('event', $event);
$this->set('feed', $this->Feed->data);
$this->set('feed', $feed);
$this->loadModel('Event');
$dataForView = array(
'Attribute' => array('attrDescriptions' => 'fieldDescriptions', 'distributionDescriptions' => 'distributionDescriptions', 'distributionLevels' => 'distributionLevels'),
'Event' => array('eventDescriptions' => 'fieldDescriptions', 'analysisLevels' => 'analysisLevels')
'Attribute' => array('attrDescriptions' => 'fieldDescriptions', 'distributionDescriptions' => 'distributionDescriptions', 'distributionLevels' => 'distributionLevels'),
'Event' => array('eventDescriptions' => 'fieldDescriptions', 'analysisLevels' => 'analysisLevels')
);
foreach ($dataForView as $m => $variables) {
if ($m === 'Event') {
@ -798,8 +803,7 @@ class FeedsController extends AppController
$this->set($alias, $currentModel->{$variable});
}
}
$threat_levels = $this->Event->ThreatLevel->find('all');
$this->set('threatLevels', Set::combine($threat_levels, '{n}.ThreatLevel.id', '{n}.ThreatLevel.name'));
$this->set('threatLevels', $this->Event->ThreatLevel->list());
} else {
if ($event === 'blocked') {
throw new MethodNotAllowedException(__('This event is blocked by the Feed filters.'));

View File

@ -267,6 +267,7 @@ class GalaxiesController extends AppController
{
$mitreAttackGalaxyId = $this->Galaxy->getMitreAttackGalaxyId();
$local = !empty($this->params['named']['local']) ? $this->params['named']['local'] : '0';
$eventid = !empty($this->params['named']['eventid']) ? $this->params['named']['eventid'] : '0';
$conditions = $namespace === '0' ? array() : array('namespace' => $namespace);
$galaxies = $this->Galaxy->find('all', array(
'recursive' => -1,
@ -278,14 +279,14 @@ class GalaxiesController extends AppController
$items = array(
array(
'name' => __('All clusters'),
'value' => $this->baseurl . "/galaxies/selectCluster/" . h($target_id) . '/' . h($target_type) . '/0'. '/local:' . $local
'value' => $this->baseurl . "/galaxies/selectCluster/" . h($target_id) . '/' . h($target_type) . '/0'. '/local:' . $local . '/eventid:' . $eventid
)
);
foreach ($galaxies as $galaxy) {
if (!isset($galaxy['Galaxy']['kill_chain_order']) || $noGalaxyMatrix) {
$items[] = array(
'name' => h($galaxy['Galaxy']['name']),
'value' => $this->baseurl . "/galaxies/selectCluster/" . $target_id . '/' . $target_type . '/' . $galaxy['Galaxy']['id'] . '/local:' . $local,
'value' => $this->baseurl . "/galaxies/selectCluster/" . $target_id . '/' . $target_type . '/' . $galaxy['Galaxy']['id'] . '/local:' . $local . '/eventid:' . $eventid,
'template' => array(
'preIcon' => 'fa-' . $galaxy['Galaxy']['icon'],
'name' => $galaxy['Galaxy']['name'],
@ -295,12 +296,14 @@ class GalaxiesController extends AppController
} else { // should use matrix instead
$param = array(
'name' => $galaxy['Galaxy']['name'],
'value' => $this->baseurl . "/galaxies/selectCluster/" . $target_id . '/' . $target_type . '/' . $galaxy['Galaxy']['id'] . '/local:' . $local . '/eventid:' . $eventid,
'functionName' => sprintf(
"getMatrixPopup('%s', '%s', '%s/local:%s')",
"getMatrixPopup('%s', '%s', '%s/local:%s/eventid:%s')",
$target_type,
$target_id,
$galaxy['Galaxy']['id'],
$local
$local,
$eventid
),
'isPill' => true,
'isMatrix' => true
@ -325,15 +328,17 @@ class GalaxiesController extends AppController
'order' => array('namespace asc')
));
$local = !empty($this->params['named']['local']) ? '1' : '0';
$eventid = !empty($this->params['named']['eventid']) ? $this->params['named']['eventid'] : '0';
$items = array();
$noGalaxyMatrix = $noGalaxyMatrix ? '1' : '0';
$items[] = array(
'name' => __('All namespaces'),
'value' => $this->baseurl . "/galaxies/selectGalaxy/" . $target_id . '/' . $target_type . '/0' . '/' . $noGalaxyMatrix . '/local:' . $local
'value' => $this->baseurl . "/galaxies/selectGalaxy/" . $target_id . '/' . $target_type . '/0' . '/' . $noGalaxyMatrix . '/local:' . $local . '/eventid:' . $eventid
);
foreach ($namespaces as $namespace) {
$items[] = array(
'name' => $namespace,
'value' => $this->baseurl . "/galaxies/selectGalaxy/" . $target_id . '/' . $target_type . '/' . $namespace . '/' . $noGalaxyMatrix . '/local:' . $local
'value' => $this->baseurl . "/galaxies/selectGalaxy/" . $target_id . '/' . $target_type . '/' . $namespace . '/' . $noGalaxyMatrix . '/local:' . $local . '/eventid:' . $eventid
);
}
@ -553,8 +558,8 @@ class GalaxiesController extends AppController
if (empty($clusters)) {
throw new MethodNotAllowedException('Invalid Galaxy.');
}
$this->Galaxy->GalaxyCluster->attachExtendByInfo($this->Auth->user(), $clusters);
foreach ($clusters as $k => $cluster) {
$clusters[$k] = $this->Galaxy->GalaxyCluster->attachExtendByInfo($this->Auth->user(), $clusters[$k]);
$clusters[$k] = $this->Galaxy->GalaxyCluster->attachExtendFromInfo($this->Auth->user(), $clusters[$k]);
}
$galaxy = $this->Galaxy->find('first', array(
@ -581,8 +586,7 @@ class GalaxiesController extends AppController
'conditions' => array('Galaxy.id' => $galaxyId)
));
App::uses('ClusterRelationsGraphTool', 'Tools');
$grapher = new ClusterRelationsGraphTool();
$grapher->construct($this->Auth->user(), $this->Galaxy->GalaxyCluster);
$grapher = new ClusterRelationsGraphTool($this->Auth->user(), $this->Galaxy->GalaxyCluster);
$relations = $grapher->getNetwork($clusters);
if ($this->_isRest()) {
return $this->RestResponse->viewData($relations, $this->response->type());

View File

@ -1,6 +1,9 @@
<?php
App::uses('AppController', 'Controller');
/**
* @property GalaxyClusterRelation $GalaxyClusterRelation
*/
class GalaxyClusterRelationsController extends AppController
{
public $components = array('Session', 'RequestHandler');
@ -145,6 +148,7 @@ class GalaxyClusterRelationsController extends AppController
if (empty($errors)) {
$message = __('Relationship added.');
$this->GalaxyClusterRelation->SourceCluster->touchTimestamp($clusterSource['SourceCluster']['id']);
$this->GalaxyClusterRelation->SourceCluster->unpublish($clusterSource['SourceCluster']['id']);
} else {
$message = __('Relationship could not be added.');
@ -174,8 +178,7 @@ class GalaxyClusterRelationsController extends AppController
}
}
}
$existingRelations = $this->GalaxyClusterRelation->getExistingRelationships();
$this->set('existingRelations', $existingRelations);
$this->set('existingRelations', $this->GalaxyClusterRelation->getExistingRelationships());
$this->set('distributionLevels', $distributionLevels);
$this->set('initialDistribution', $initialDistribution);
$this->set('sharingGroups', $sgs);
@ -240,6 +243,7 @@ class GalaxyClusterRelationsController extends AppController
if (empty($errors)) {
$message = __('Relationship added.');
$this->GalaxyClusterRelation->SourceCluster->touchTimestamp($clusterSource['SourceCluster']['id']);
$this->GalaxyClusterRelation->SourceCluster->unpublish($clusterSource['SourceCluster']['id']);
} else {
$message = __('Relationship could not be added.');
@ -264,6 +268,7 @@ class GalaxyClusterRelationsController extends AppController
}
}
$this->request->data = $existingRelation;
$this->set('existingRelations', $this->GalaxyClusterRelation->getExistingRelationships());
$this->set('distributionLevels', $distributionLevels);
$this->set('initialDistribution', $initialDistribution);
$this->set('sharingGroups', $sgs);
@ -282,6 +287,7 @@ class GalaxyClusterRelationsController extends AppController
$clusterSource = $this->GalaxyClusterRelation->SourceCluster->fetchIfAuthorized($this->Auth->user(), $relation['GalaxyClusterRelation']['galaxy_cluster_uuid'], array('edit', 'publish'), $throwErrors=true, $full=false);
$result = $this->GalaxyClusterRelation->delete($id, true);
if ($result) {
$this->GalaxyClusterRelation->SourceCluster->touchTimestamp($clusterSource['SourceCluster']['id']);
$this->GalaxyClusterRelation->SourceCluster->unpublish($clusterSource['SourceCluster']['id']);
$message = __('Galaxy cluster relationship successfuly deleted.');
if ($this->_isRest()) {

View File

@ -56,7 +56,7 @@ class GalaxyClustersController extends AppController
$contextConditions['GalaxyCluster.deleted'] = true;
}
$this->set('passedArgsArray', array('context' => $filters['context'], 'searchall' => isset($filters['searchall']) ? $filters['searchall'] : ''));
$this->set('passedArgs', json_encode(array('context' => $filters['context'], 'searchall' => isset($filters['searchall']) ? $filters['searchall'] : '')));
$this->set('context', $filters['context']);
$searchConditions = array();
if (empty($filters['searchall'])) {
@ -96,62 +96,64 @@ class GalaxyClustersController extends AppController
)
);
return $this->RestResponse->viewData($clusters, $this->response->type());
} else {
$this->paginate['conditions']['AND'][] = $contextConditions;
$this->paginate['conditions']['AND'][] = $searchConditions;
$this->paginate['conditions']['AND'][] = $aclConditions;
$this->paginate['contain'] = array_merge($this->paginate['contain'], array('Org', 'Orgc', 'SharingGroup', 'GalaxyClusterRelation', 'TargetingClusterRelation'));
$clusters = $this->paginate();
$tagIds = array();
foreach ($clusters as $k => $cluster) {
$clusters[$k] = $this->GalaxyCluster->attachExtendByInfo($this->Auth->user(), $clusters[$k]);
$clusters[$k] = $this->GalaxyCluster->attachExtendFromInfo($this->Auth->user(), $clusters[$k]);
$clusters[$k]['GalaxyCluster']['relation_counts'] = array(
'out' => count($clusters[$k]['GalaxyClusterRelation']),
'in' => count($clusters[$k]['TargetingClusterRelation']),
);
if (isset($cluster['Tag']['id'])) {
$tagIds[] = $cluster['Tag']['id'];
$clusters[$k]['GalaxyCluster']['tag_id'] = $cluster['Tag']['id'];
}
$clusters[$k]['GalaxyCluster']['synonyms'] = array();
foreach ($cluster['GalaxyElement'] as $element) {
$clusters[$k]['GalaxyCluster']['synonyms'][] = $element['value'];
}
$clusters[$k]['GalaxyCluster']['event_count'] = 0; // real number is assigned later
}
$eventCountsForTags = $this->GalaxyCluster->Tag->EventTag->countForTags($tagIds, $this->Auth->user());
$this->loadModel('Sighting');
$csvForTags = $this->Sighting->tagsSparkline($tagIds, $this->Auth->user(), '0');
foreach ($clusters as $k => $cluster) {
if (isset($cluster['GalaxyCluster']['tag_id'])) {
if (isset($csvForTags[$cluster['GalaxyCluster']['tag_id']])) {
$clusters[$k]['csv'] = $csvForTags[$cluster['GalaxyCluster']['tag_id']];
}
if (isset($eventCountsForTags[$cluster['GalaxyCluster']['tag_id']])) {
$clusters[$k]['GalaxyCluster']['event_count'] = $eventCountsForTags[$cluster['GalaxyCluster']['tag_id']];
}
}
}
$customClusterCount = $this->GalaxyCluster->fetchGalaxyClusters($this->Auth->user(), [
'count' => true,
'conditions' => [
'AND' => [$searchConditions, $aclConditions],
'GalaxyCluster.default' => 0,
]
]);
$this->loadModel('Attribute');
$distributionLevels = $this->Attribute->distributionLevels;
unset($distributionLevels[5]);
$this->set('distributionLevels', $distributionLevels);
$this->set('list', $clusters);
$this->set('galaxy_id', $galaxyId);
$this->set('custom_cluster_count', $customClusterCount);
}
$this->paginate['conditions']['AND'][] = $contextConditions;
$this->paginate['conditions']['AND'][] = $searchConditions;
$this->paginate['conditions']['AND'][] = $aclConditions;
$this->paginate['contain'] = array_merge($this->paginate['contain'], array('Org', 'Orgc', 'SharingGroup', 'GalaxyClusterRelation', 'TargetingClusterRelation'));
$clusters = $this->paginate();
$this->GalaxyCluster->attachExtendByInfo($this->Auth->user(), $clusters);
$tagIds = array();
foreach ($clusters as $k => $cluster) {
$clusters[$k] = $this->GalaxyCluster->attachExtendFromInfo($this->Auth->user(), $clusters[$k]);
$clusters[$k]['GalaxyCluster']['relation_counts'] = array(
'out' => count($clusters[$k]['GalaxyClusterRelation']),
'in' => count($clusters[$k]['TargetingClusterRelation']),
);
if (isset($cluster['Tag']['id'])) {
$tagIds[] = $cluster['Tag']['id'];
$clusters[$k]['GalaxyCluster']['tag_id'] = $cluster['Tag']['id'];
}
$clusters[$k]['GalaxyCluster']['synonyms'] = array();
foreach ($cluster['GalaxyElement'] as $element) {
$clusters[$k]['GalaxyCluster']['synonyms'][] = $element['value'];
}
$clusters[$k]['GalaxyCluster']['event_count'] = 0; // real number is assigned later
}
$eventCountsForTags = $this->GalaxyCluster->Tag->EventTag->countForTags($tagIds, $this->Auth->user());
$this->loadModel('Sighting');
$csvForTags = $this->Sighting->tagsSparkline($tagIds, $this->Auth->user(), '0');
foreach ($clusters as $k => $cluster) {
if (isset($cluster['GalaxyCluster']['tag_id'])) {
if (isset($csvForTags[$cluster['GalaxyCluster']['tag_id']])) {
$clusters[$k]['csv'] = $csvForTags[$cluster['GalaxyCluster']['tag_id']];
}
if (isset($eventCountsForTags[$cluster['GalaxyCluster']['tag_id']])) {
$clusters[$k]['GalaxyCluster']['event_count'] = $eventCountsForTags[$cluster['GalaxyCluster']['tag_id']];
}
}
}
$customClusterCount = $this->GalaxyCluster->fetchGalaxyClusters($this->Auth->user(), [
'count' => true,
'conditions' => [
'AND' => [$searchConditions, $aclConditions],
'GalaxyCluster.default' => 0,
]
]);
$this->loadModel('Attribute');
$distributionLevels = $this->Attribute->distributionLevels;
unset($distributionLevels[5]);
$this->set('distributionLevels', $distributionLevels);
$this->set('list', $clusters);
$this->set('galaxy_id', $galaxyId);
$this->set('custom_cluster_count', $customClusterCount);
if ($this->request->is('ajax')) {
$this->layout = 'ajax';
$this->render('ajax/index');
@ -179,7 +181,9 @@ class GalaxyClustersController extends AppController
if ($this->_isRest()) {
return $this->RestResponse->viewData($cluster, $this->response->type());
} else {
$cluster = $this->GalaxyCluster->attachExtendByInfo($this->Auth->user(), $cluster);
$clusters = [$cluster];
$this->GalaxyCluster->attachExtendByInfo($this->Auth->user(), $clusters);
$cluster = $clusters[0];
$cluster = $this->GalaxyCluster->attachExtendFromInfo($this->Auth->user(), $cluster);
$this->set('id', $id);
$this->set('galaxy', ['Galaxy' => $cluster['GalaxyCluster']['Galaxy']]);
@ -391,7 +395,9 @@ class GalaxyClustersController extends AppController
if (empty($cluster['GalaxyCluster']['authors'])) {
$cluster['GalaxyCluster']['authors'] = [];
} else {
} else if (is_array($cluster['GalaxyCluster']['authors'])) {
// This is as intended, move on
}else {
$decoded = json_decode($cluster['GalaxyCluster']['authors'], true);
if (is_null($decoded)) { // authors might be comma separated
$decoded = array_map('trim', explode(',', $cluster['GalaxyCluster']['authors']));
@ -703,6 +709,9 @@ class GalaxyClustersController extends AppController
{
$cluster = $this->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $id, 'delete', $throwErrors=true, $full=false);
if ($this->request->is('post')) {
if (!empty($this->request->data['hard'])) {
$hard = true;
}
$result = $this->GalaxyCluster->deleteCluster($cluster['GalaxyCluster']['id'], $hard=$hard);
$galaxyId = $cluster['GalaxyCluster']['galaxy_id'];
if ($result) {
@ -712,7 +721,7 @@ class GalaxyClustersController extends AppController
$hard ? __(' and added to the block list') : ''
);
if ($this->_isRest()) {
return $this->RestResponse->saveSuccessResponse('GalaxyCluster', 'delete', $cluster['GalaxyCluster']['id'], $this->response->type());
return $this->RestResponse->saveSuccessResponse('GalaxyCluster', 'delete', $cluster['GalaxyCluster']['id'], $this->response->type(), $message);
} else {
$this->Flash->success($message);
$this->redirect(array('controller' => 'galaxies', 'action' => 'view', $galaxyId));
@ -720,7 +729,7 @@ class GalaxyClustersController extends AppController
} else {
$message = __('Galaxy cluster could not be %s deleted.', $hard ? __('hard') : __('soft'));
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('GalaxyCluster', 'delete', $cluster['GalaxyCluster']['id'], $message, $this->response->type());
return $this->RestResponse->saveFailResponse('GalaxyCluster', 'delete', $cluster['GalaxyCluster']['id'], $message, $this->response->type(), $message);
} else {
$this->Flash->error($message);
$this->redirect(array('controller' => 'galaxies', 'action' => 'view', $galaxyId));
@ -967,7 +976,7 @@ class GalaxyClustersController extends AppController
if (!$this->request->is('ajax')) {
throw new MethodNotAllowedException('This function can only be reached via AJAX.');
}
$cluster = $this->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $id, 'view', $throwErrors=true, $full=true);
$cluster = $this->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $id, 'view', true, true);
$existingRelations = $this->GalaxyCluster->GalaxyClusterRelation->getExistingRelationships();
$cluster = $this->GalaxyCluster->attachClusterToRelations($this->Auth->user(), $cluster);
@ -978,12 +987,8 @@ class GalaxyClustersController extends AppController
$this->set('existingRelations', $existingRelations);
$this->set('cluster', $cluster);
$relations = $this->GalaxyCluster->GalaxyClusterRelation->fetchRelations($this->Auth->user(), array(
'conditions' => array(
'GalaxyClusterRelation.galaxy_cluster_uuid' => $cluster['GalaxyCluster']['uuid']
),
'contain' => array('SharingGroup', 'TargetCluster', 'GalaxyClusterRelationTag' => array('Tag'))
));
$relations = $cluster['GalaxyCluster']['GalaxyClusterRelation'];
$this->set('passedArgs', json_encode([]));
$this->set('relations', $relations);
$this->set('tree', $tree);
$this->loadModel('Attribute');

View File

@ -16,14 +16,102 @@ class GalaxyElementsController extends AppController
public function index($clusterId)
{
$filters = $this->IndexFilter->harvestParameters(array('context', 'searchall'));
$aclConditions = $this->GalaxyElement->buildClusterConditions($this->Auth->user(), $clusterId);
$this->paginate['conditions'] = [$aclConditions];
if (empty($filters['context'])) {
$filters['context'] = 'all';
}
$searchConditions = array();
if (empty($filters['searchall'])) {
$filters['searchall'] = '';
}
if (strlen($filters['searchall']) > 0) {
$searchall = '%' . strtolower($filters['searchall']) . '%';
$searchConditions = array(
'OR' => array(
'LOWER(GalaxyElement.key) LIKE' => $searchall,
'LOWER(GalaxyElement.value) LIKE' => $searchall,
),
);
}
$this->paginate['conditions'] = ['AND' => [$aclConditions, $searchConditions]];
$this->paginate['contain'] = ['GalaxyCluster' => ['fields' => ['id', 'distribution', 'org_id']]];
$clusters = $this->paginate();
$this->set('list', $clusters);
$elements = $this->paginate();
$this->set('elements', $elements);
$this->set('clusterId', $clusterId);
$this->set('context', $filters['context']);
$this->set('passedArgs', json_encode([
'context' => $filters['context'],
'searchall' => isset($filters['searchall']) ? $filters['searchall'] : ''
]));
$cluster = $this->GalaxyElement->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $clusterId, array('edit', 'delete'), false, false);
$canModify = !empty($cluster['authorized']);
$canModify = true;
$this->set('canModify', $canModify);
if ($filters['context'] == 'JSONView') {
$expanded = $this->GalaxyElement->getExpandedJSONFromElements($elements);
$this->set('JSONElements', $expanded);
}
if ($this->request->is('ajax')) {
$this->layout = 'ajax';
$this->render('ajax/index');
}
}
public function delete($elementId)
{
$element = $this->GalaxyElement->find('first', array('conditions' => array('GalaxyElement.id' => $elementId)));
if (empty($element)) {
throw new Exception(__('Element not found'));
}
$this->set('element', $element);
$clusterId = $element['GalaxyElement']['galaxy_cluster_id'];
$cluster = $this->GalaxyElement->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $clusterId, array('edit'), true, false);
if ($this->request->is('post')) {
$deleteResult = $this->GalaxyElement->delete($elementId);
if ($deleteResult) {
$this->GalaxyElement->GalaxyCluster->editCluster($this->Auth->user(), $cluster, [], false);
$message = __('Galaxy element %s deleted', $elementId);
$this->Flash->success($message);
} else {
$message = __('Could not delete galaxy element');
$this->Flash->error($message);
}
$this->redirect($this->referer());
} else {
if (!$this->request->is('ajax')) {
throw new MethodNotAllowedException(__('This function can only be reached via AJAX.'));
} else {
$this->layout = 'ajax';
$this->set('elementId', $elementId);
$this->render('ajax/delete');
}
}
}
public function flattenJson($clusterId)
{
$cluster = $this->GalaxyElement->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $clusterId, array('edit'), true, false);
if ($this->request->is('post') || $this->request->is('put')) {
$json = $this->GalaxyElement->jsonDecode($this->request->data['GalaxyElement']['jsonData']);
$flattened = Hash::flatten($json);
$newElements = [];
foreach ($flattened as $k => $v) {
$newElements[] = ['key' => $k, 'value' => $v];
}
$cluster['GalaxyCluster']['GalaxyElement'] = $newElements;
$errors = $this->GalaxyElement->GalaxyCluster->editCluster($this->Auth->user(), $cluster, [], false);
if (empty($errors)) {
return $this->RestResponse->saveSuccessResponse('GalaxyElement', 'flattenJson', $clusterId, false);
} else {
$message = implode(', ', $errors);
return $this->RestResponse->saveFailResponse('GalaxyElement', 'flattenJson', $clusterId, $message, false);
}
}
$this->set('clusterId', $clusterId);
if ($this->request->is('ajax')) {
$this->layout = 'ajax';
$this->render('ajax/flattenJson');
}
}
}

View File

@ -108,7 +108,7 @@ class LogsController extends AppController
}
// Shows a minimalistic history for the currently selected event
public function event_index($id)
public function event_index($id, $org = null)
{
$this->loadModel('Event');
$event = $this->Event->fetchEvent($this->Auth->user(), array(
@ -183,6 +183,10 @@ class LogsController extends AppController
);
}
if ($org) {
$conditions['org'] = $org;
}
$this->paginate['fields'] = array('title', 'created', 'model', 'model_id', 'action', 'change', 'org', 'email');
$this->paginate['conditions'] = $conditions;
@ -194,7 +198,7 @@ class LogsController extends AppController
'fields' => array('User.email')
));
foreach ($list as $k => $item) {
if (!in_array($item['Log']['email'], $orgEmails)) {
if (!in_array($item['Log']['email'], $orgEmails, true)) {
$list[$k]['Log']['email'] = '';
}
}
@ -367,6 +371,8 @@ class LogsController extends AppController
'EventTag',
'Feed',
'DecayingModel',
'EventGraph',
'EventReport',
'MispObject',
'Organisation',
'Post',

View File

@ -41,7 +41,7 @@ class ObjectReferencesController extends AppController
'recursive' => -1,
'contain' => array(
'Event' => array(
'fields' => array('Event.id', 'Event.orgc_id', 'Event.user_id')
'fields' => array('Event.id', 'Event.orgc_id', 'Event.user_id', 'Event.extends_uuid')
)
)
));
@ -54,7 +54,7 @@ class ObjectReferencesController extends AppController
if (!isset($this->request->data['ObjectReference'])) {
$this->request->data['ObjectReference'] = $this->request->data;
}
list($referenced_id, $referenced_uuid, $referenced_type) = $this->ObjectReference->getReferencedInfo($this->request->data['ObjectReference']['referenced_uuid'], $object);
list($referenced_id, $referenced_uuid, $referenced_type) = $this->ObjectReference->getReferencedInfo($this->request->data['ObjectReference']['referenced_uuid'], $object, true, $this->Auth->user());
$relationship_type = empty($this->request->data['ObjectReference']['relationship_type']) ? '' : $this->request->data['ObjectReference']['relationship_type'];
if (!empty($this->request->data['ObjectReference']['relationship_type_select']) && $this->request->data['ObjectReference']['relationship_type_select'] !== 'custom') {
$relationship_type = $this->request->data['ObjectReference']['relationship_type_select'];
@ -66,6 +66,7 @@ class ObjectReferencesController extends AppController
'comment' => !empty($this->request->data['ObjectReference']['comment']) ? $this->request->data['ObjectReference']['comment'] : '',
'event_id' => $object['Event']['id'],
'object_uuid' => $object['Object']['uuid'],
'source_uuid' => $object['Object']['uuid'],
'object_id' => $objectId,
'referenced_type' => $referenced_type,
'uuid' => CakeText::uuid()
@ -96,8 +97,16 @@ class ObjectReferencesController extends AppController
if ($this->_isRest()) {
return $this->RestResponse->describe('ObjectReferences', 'add', false, $this->response->type());
} else {
$event = $this->ObjectReference->Object->Event->find('first', array(
'conditions' => array('Event.id' => $object['Event']['id']),
$events = $this->ObjectReference->Object->Event->find('all', array(
'conditions' => array(
'OR' => array(
'Event.id' => $object['Event']['id'],
'AND' => array(
'Event.uuid' => $object['Event']['extends_uuid'],
$this->ObjectReference->Object->Event->createEventConditions($this->Auth->user())
)
),
),
'recursive' => -1,
'fields' => array('Event.id'),
'contain' => array(
@ -115,6 +124,13 @@ class ObjectReferencesController extends AppController
)
)
));
if (!empty($events)) {
$event = $events[0];
}
for ($i=1; $i < count($events); $i++) {
$event['Attribute'] = array_merge($event['Attribute'], $events[$i]['Attribute']);
$event['Object'] = array_merge($event['Object'], $events[$i]['Object']);
}
$toRearrange = array('Attribute', 'Object');
foreach ($toRearrange as $d) {
if (!empty($event[$d])) {
@ -200,5 +216,30 @@ class ObjectReferencesController extends AppController
public function view($id)
{
if (Validation::uuid($id)) {
$temp = $this->ObjectReference->find('first', array(
'recursive' => -1,
'fields' => array('ObjectReference.id'),
'conditions' => array('ObjectReference.uuid' => $id)
));
if (empty($temp)) {
throw new NotFoundException('Invalid object reference');
}
$id = $temp['ObjectReference']['id'];
} elseif (!is_numeric($id)) {
throw new NotFoundException(__('Invalid object reference'));
}
$objectReference = $this->ObjectReference->find('first', array(
'conditions' => array('ObjectReference.id' => $id),
'recursive' => -1,
));
if (empty($objectReference)) {
throw new NotFoundException(__('Invalid object reference.'));
}
$event = $this->ObjectReference->Object->Event->fetchSimpleEvent($this->Auth->user(), $objectReference['ObjectReference']['event_id'], ['contain' => ['Orgc']]);
if (!$event) {
throw new NotFoundException(__('Invalid event'));
}
return $this->RestResponse->viewData($objectReference, 'application/json');
}
}

View File

@ -298,4 +298,13 @@ class ObjectTemplatesController extends AppController
$this->layout = 'ajax';
$this->render('ajax/getToggleField');
}
public function getRaw($uuidOrName)
{
$template = $this->ObjectTemplate->getRawFromDisk($uuidOrName);
if (empty($template)) {
throw new NotFoundException(__('Template not found'));
}
return $this->RestResponse->viewData($template, $this->response->type());
}
}

View File

@ -376,13 +376,14 @@ class ObjectsController extends AppController
'ObjectTemplateElement'
)
));
if (empty($template) && !$this->_isRest()) {
$this->Flash->error('Object cannot be edited, no valid template found.');
if (empty($template) && !$this->_isRest() && !$update_template_available) {
$this->Flash->error('Object cannot be edited, no valid template found. ', ['params' => ['url' => sprintf('/objects/edit/%s/1/0', $id), 'urlName' => __('Force update anyway')]]);
$this->redirect(array('controller' => 'events', 'action' => 'view', $object['Object']['event_id']));
}
$templateData = $this->MispObject->resolveUpdatedTemplate($template, $object, $update_template_available);
$this->set('updateable_attribute', $templateData['updateable_attribute']);
$this->set('not_updateable_attribute', $templateData['not_updateable_attribute']);
$this->set('original_template_unkown', $templateData['original_template_unkown']);
if (!empty($this->Session->read('object_being_created')) && !empty($this->params['named']['cur_object_tmp_uuid'])) {
$revisedObjectData = $this->Session->read('object_being_created');
if ($this->params['named']['cur_object_tmp_uuid'] == $revisedObjectData['cur_object_tmp_uuid']) { // ensure that the passed session data is for the correct object

View File

@ -2,6 +2,7 @@
App::uses('AppController', 'Controller');
App::uses('Xml', 'Utility');
App::uses('AttachmentTool', 'Tools');
App::uses('SecurityAudit', 'Tools');
/**
* @property Server $Server
@ -34,8 +35,11 @@ class ServersController extends AppController
public function beforeFilter()
{
$this->Auth->allow(['cspReport']); // cspReport must work without authentication
parent::beforeFilter();
$this->Security->unlockedActions[] = 'getApiInfo';
$this->Security->unlockedActions[] = 'cspReport';
// permit reuse of CSRF tokens on some pages.
switch ($this->request->params['action']) {
case 'push':
@ -120,8 +124,7 @@ class ServersController extends AppController
}
$this->loadModel('Event');
$threat_levels = $this->Event->ThreatLevel->find('list', ['fields' => ['id', 'name']]);
$this->set('threatLevels', $threat_levels);
$this->set('threatLevels', $this->Event->ThreatLevel->list());
App::uses('CustomPaginationTool', 'Tools');
$customPagination = new CustomPaginationTool();
$params = $customPagination->createPaginationRules($events, $this->passedArgs, $this->alias);
@ -178,6 +181,7 @@ class ServersController extends AppController
$this->loadModel('Event');
$params = $this->Event->rearrangeEventForView($event, $this->passedArgs, $all);
$this->__removeGalaxyClusterTags($event);
$this->params->params['paging'] = array('Server' => $params);
$this->set('event', $event);
$this->set('server', $server);
@ -198,11 +202,30 @@ class ServersController extends AppController
$this->set($alias, $currentModel->{$variable});
}
}
$threat_levels = $this->Event->ThreatLevel->find('list', ['fields' => ['id', 'name']]);
$this->set('threatLevels', $threat_levels);
$this->set('threatLevels', $this->Event->ThreatLevel->list());
$this->set('title_for_layout', __('Remote event preview'));
}
private function __removeGalaxyClusterTags(array &$event)
{
$galaxyTagIds = [];
foreach ($event['Galaxy'] as $galaxy) {
foreach ($galaxy['GalaxyCluster'] as $galaxyCluster) {
$galaxyTagIds[$galaxyCluster['tag_id']] = true;
}
}
if (empty($galaxyTagIds)) {
return;
}
foreach ($event['Tag'] as $k => $eventTag) {
if (isset($galaxyTagIds[$eventTag['id']])) {
unset($event['Tag'][$k]);
}
}
}
public function compareServers()
{
list($servers, $overlap) = $this->Server->serverEventsOverlap();
@ -938,253 +961,254 @@ class ServersController extends AppController
public function serverSettings($tab=false)
{
if (!$this->_isSiteAdmin()) {
throw new MethodNotAllowedException();
if (!$this->request->is('get')) {
throw new MethodNotAllowedException('Just GET method is allowed.');
}
if ($this->request->is('Get')) {
$tabs = array(
'MISP' => array('count' => 0, 'errors' => 0, 'severity' => 5),
'Encryption' => array('count' => 0, 'errors' => 0, 'severity' => 5),
'Proxy' => array('count' => 0, 'errors' => 0, 'severity' => 5),
'Security' => array('count' => 0, 'errors' => 0, 'severity' => 5),
'Plugin' => array('count' => 0, 'errors' => 0, 'severity' => 5)
);
$writeableErrors = array(0 => __('OK'), 1 => __('not found'), 2 => __('is not writeable'));
$readableErrors = array(0 => __('OK'), 1 => __('not readable'));
$gpgErrors = array(0 => __('OK'), 1 => __('FAIL: settings not set'), 2 => __('FAIL: Failed to load GnuPG'), 3 => __('FAIL: Issues with the key/passphrase'), 4 => __('FAIL: sign failed'));
$proxyErrors = array(0 => __('OK'), 1 => __('not configured (so not tested)'), 2 => __('Getting URL via proxy failed'));
$zmqErrors = array(0 => __('OK'), 1 => __('not enabled (so not tested)'), 2 => __('Python ZeroMQ library not installed correctly.'), 3 => __('ZeroMQ script not running.'));
$stixOperational = array(0 => __('Some of the libraries related to STIX are not installed. Make sure that all libraries listed below are correctly installed.'), 1 => __('OK'));
$stixVersion = array(0 => __('Incorrect STIX version installed, found $current, expecting $expected'), 1 => __('OK'));
$stix2Version = array(0 => __('Incorrect STIX2 version installed, found $current, expecting $expected'), 1 => __('OK'));
$cyboxVersion = array(0 => __('Incorrect CyBox version installed, found $current, expecting $expected'), 1 => __('OK'));
$mixboxVersion = array(0 => __('Incorrect mixbox version installed, found $current, expecting $expected'), 1 => __('OK'));
$maecVersion = array(0 => __('Incorrect maec version installed, found $current, expecting $expected'), 1 => __('OK'));
$pymispVersion = array(0 => __('Incorrect PyMISP version installed, found $current, expecting $expected'), 1 => __('OK'));
$sessionErrors = array(0 => __('OK'), 1 => __('High'), 2 => __('Alternative setting used'), 3 => __('Test failed'));
$moduleErrors = array(0 => __('OK'), 1 => __('System not enabled'), 2 => __('No modules found'));
$tabs = array(
'MISP' => array('count' => 0, 'errors' => 0, 'severity' => 5),
'Encryption' => array('count' => 0, 'errors' => 0, 'severity' => 5),
'Proxy' => array('count' => 0, 'errors' => 0, 'severity' => 5),
'Security' => array('count' => 0, 'errors' => 0, 'severity' => 5),
'Plugin' => array('count' => 0, 'errors' => 0, 'severity' => 5)
);
$writeableErrors = array(0 => __('OK'), 1 => __('not found'), 2 => __('is not writeable'));
$readableErrors = array(0 => __('OK'), 1 => __('not readable'));
$gpgErrors = array(0 => __('OK'), 1 => __('FAIL: settings not set'), 2 => __('FAIL: Failed to load GnuPG'), 3 => __('FAIL: Issues with the key/passphrase'), 4 => __('FAIL: sign failed'));
$proxyErrors = array(0 => __('OK'), 1 => __('not configured (so not tested)'), 2 => __('Getting URL via proxy failed'));
$zmqErrors = array(0 => __('OK'), 1 => __('not enabled (so not tested)'), 2 => __('Python ZeroMQ library not installed correctly.'), 3 => __('ZeroMQ script not running.'));
$stixOperational = array(0 => __('Some of the libraries related to STIX are not installed. Make sure that all libraries listed below are correctly installed.'), 1 => __('OK'));
$stixVersion = array(0 => __('Incorrect STIX version installed, found $current, expecting $expected'), 1 => __('OK'));
$stix2Version = array(0 => __('Incorrect STIX2 version installed, found $current, expecting $expected'), 1 => __('OK'));
$cyboxVersion = array(0 => __('Incorrect CyBox version installed, found $current, expecting $expected'), 1 => __('OK'));
$mixboxVersion = array(0 => __('Incorrect mixbox version installed, found $current, expecting $expected'), 1 => __('OK'));
$maecVersion = array(0 => __('Incorrect maec version installed, found $current, expecting $expected'), 1 => __('OK'));
$pymispVersion = array(0 => __('Incorrect PyMISP version installed, found $current, expecting $expected'), 1 => __('OK'));
$sessionErrors = array(0 => __('OK'), 1 => __('High'), 2 => __('Alternative setting used'), 3 => __('Test failed'));
$moduleErrors = array(0 => __('OK'), 1 => __('System not enabled'), 2 => __('No modules found'));
$finalSettings = $this->Server->serverSettingsRead();
$issues = array(
'errors' => array(
0 => array(
'value' => 0,
'description' => __('MISP will not operate correctly or will be unsecure until these issues are resolved.')
),
1 => array(
'value' => 0,
'description' => __('Some of the features of MISP cannot be utilised until these issues are resolved.')
),
2 => array(
'value' => 0,
'description' => __('There are some optional tweaks that could be done to improve the looks of your MISP instance.')
),
$finalSettings = $this->Server->serverSettingsRead();
$issues = array(
'errors' => array(
0 => array(
'value' => 0,
'description' => __('MISP will not operate correctly or will be unsecure until these issues are resolved.')
),
'deprecated' => array(),
'overallHealth' => 3,
);
$dumpResults = array();
$tempArray = array();
foreach ($finalSettings as $k => $result) {
if ($result['level'] == 3) {
$issues['deprecated']++;
1 => array(
'value' => 0,
'description' => __('Some of the features of MISP cannot be utilised until these issues are resolved.')
),
2 => array(
'value' => 0,
'description' => __('There are some optional tweaks that could be done to improve the looks of your MISP instance.')
),
),
'deprecated' => array(),
'overallHealth' => 3,
);
$dumpResults = array();
$tempArray = array();
foreach ($finalSettings as $k => $result) {
if ($result['level'] == 3) {
$issues['deprecated']++;
}
$tabs[$result['tab']]['count']++;
if (isset($result['error']) && $result['level'] < 3) {
$issues['errors'][$result['level']]['value']++;
if ($result['level'] < $issues['overallHealth']) {
$issues['overallHealth'] = $result['level'];
}
$tabs[$result['tab']]['count']++;
if (isset($result['error']) && $result['level'] < 3) {
$issues['errors'][$result['level']]['value']++;
if ($result['level'] < $issues['overallHealth']) {
$issues['overallHealth'] = $result['level'];
}
$tabs[$result['tab']]['errors']++;
if ($result['level'] < $tabs[$result['tab']]['severity']) {
$tabs[$result['tab']]['severity'] = $result['level'];
}
}
if (isset($result['optionsSource']) && is_callable($result['optionsSource'])) {
$result['options'] = $result['optionsSource']();
}
$dumpResults[] = $result;
if ($result['tab'] == $tab) {
if (isset($result['subGroup'])) {
$tempArray[$result['subGroup']][] = $result;
} else {
$tempArray['general'][] = $result;
}
$tabs[$result['tab']]['errors']++;
if ($result['level'] < $tabs[$result['tab']]['severity']) {
$tabs[$result['tab']]['severity'] = $result['level'];
}
}
$finalSettings = $tempArray;
// Diagnostics portion
$diagnostic_errors = 0;
App::uses('File', 'Utility');
App::uses('Folder', 'Utility');
if ($tab === 'files') {
$files = $this->__manageFiles();
$this->set('files', $files);
if (isset($result['optionsSource']) && is_callable($result['optionsSource'])) {
$result['options'] = $result['optionsSource']();
}
// Only run this check on the diagnostics tab
if ($tab === 'diagnostics' || $tab === 'download' || $this->_isRest()) {
$php_ini = php_ini_loaded_file();
$this->set('php_ini', $php_ini);
$attachmentTool = new AttachmentTool();
try {
$advanced_attachments = $attachmentTool->checkAdvancedExtractionStatus($this->Server->getPythonVersion());
} catch (Exception $e) {
$this->log($e->getMessage(), LOG_NOTICE);
$advanced_attachments = false;
$dumpResults[] = $result;
if ($result['tab'] == $tab) {
if (isset($result['subGroup'])) {
$tempArray[$result['subGroup']][] = $result;
} else {
$tempArray['general'][] = $result;
}
$this->set('advanced_attachments', $advanced_attachments);
// check if the current version of MISP is outdated or not
$version = $this->__checkVersion();
$this->set('version', $version);
$gitStatus = $this->Server->getCurrentGitStatus();
$this->set('branch', $gitStatus['branch']);
$this->set('commit', $gitStatus['commit']);
$this->set('latestCommit', $gitStatus['latestCommit']);
$phpSettings = array(
'max_execution_time' => array(
'explanation' => 'The maximum duration that a script can run (does not affect the background workers). A too low number will break long running scripts like comprehensive API exports',
'recommended' => 300,
'unit' => false
),
'memory_limit' => array(
'explanation' => 'The maximum memory that PHP can consume. It is recommended to raise this number since certain exports can generate a fair bit of memory usage',
'recommended' => 2048,
'unit' => 'M'
),
'upload_max_filesize' => array(
'explanation' => 'The maximum size that an uploaded file can be. It is recommended to raise this number to allow for the upload of larger samples',
'recommended' => 50,
'unit' => 'M'
),
'post_max_size' => array(
'explanation' => 'The maximum size of a POSTed message, this has to be at least the same size as the upload_max_filesize setting',
'recommended' => 50,
'unit' => 'M'
)
);
foreach ($phpSettings as $setting => $settingArray) {
$phpSettings[$setting]['value'] = ini_get($setting);
if ($settingArray['unit']) {
$phpSettings[$setting]['value'] = intval(rtrim($phpSettings[$setting]['value'], $phpSettings[$setting]['unit']));
} else {
$phpSettings[$setting]['value'] = intval($phpSettings[$setting]['value']);
}
}
$this->set('phpSettings', $phpSettings);
if ($version && (!$version['upToDate'] || $version['upToDate'] == 'older')) {
$diagnostic_errors++;
}
// check if the STIX and Cybox libraries are working and the correct version using the test script stixtest.py
$stix = $this->Server->stixDiagnostics($diagnostic_errors, $stixVersion, $cyboxVersion, $mixboxVersion, $maecVersion, $stix2Version, $pymispVersion);
$yaraStatus = $this->Server->yaraDiagnostics($diagnostic_errors);
// if GnuPG is set up in the settings, try to encrypt a test message
$gpgStatus = $this->Server->gpgDiagnostics($diagnostic_errors);
// if the message queue pub/sub is enabled, check whether the extension works
$zmqStatus = $this->Server->zmqDiagnostics($diagnostic_errors);
// if Proxy is set up in the settings, try to connect to a test URL
$proxyStatus = $this->Server->proxyDiagnostics($diagnostic_errors);
// get the DB diagnostics
$dbDiagnostics = $this->Server->dbSpaceUsage();
$dbSchemaDiagnostics = $this->Server->dbSchemaDiagnostic();
$redisInfo = $this->Server->redisInfo();
$moduleTypes = array('Enrichment', 'Import', 'Export', 'Cortex');
foreach ($moduleTypes as $type) {
$moduleStatus[$type] = $this->Server->moduleDiagnostics($diagnostic_errors, $type);
}
// check the size of the session table
$sessionCount = 0;
$sessionStatus = $this->Server->sessionDiagnostics($diagnostic_errors, $sessionCount);
$this->set('sessionCount', $sessionCount);
$this->loadModel('AttachmentScan');
try {
$attachmentScan = ['status' => true, 'software' => $this->AttachmentScan->diagnostic()];
} catch (Exception $e) {
$attachmentScan = ['status' => false, 'error' => $e->getMessage()];
}
$view = compact('gpgStatus', 'sessionErrors', 'proxyStatus', 'sessionStatus', 'zmqStatus', 'stixVersion', 'cyboxVersion', 'mixboxVersion', 'maecVersion', 'stix2Version', 'pymispVersion', 'moduleStatus', 'yaraStatus', 'gpgErrors', 'proxyErrors', 'zmqErrors', 'stixOperational', 'stix', 'moduleErrors', 'moduleTypes', 'dbDiagnostics', 'dbSchemaDiagnostics', 'redisInfo', 'attachmentScan');
} else {
$view = [];
}
// check whether the files are writeable
$writeableDirs = $this->Server->writeableDirsDiagnostics($diagnostic_errors);
$writeableFiles = $this->Server->writeableFilesDiagnostics($diagnostic_errors);
$readableFiles = $this->Server->readableFilesDiagnostics($diagnostic_errors);
$extensions = $this->Server->extensionDiagnostics();
// check if the encoding is not set to utf8
$dbEncodingStatus = $this->Server->databaseEncodingDiagnostics($diagnostic_errors);
$view = array_merge($view, compact('diagnostic_errors', 'tabs', 'tab', 'issues', 'finalSettings', 'writeableErrors', 'readableErrors', 'writeableDirs', 'writeableFiles', 'readableFiles', 'extensions', 'dbEncodingStatus'));
$this->set($view);
$workerIssueCount = 4;
$worker_array = array();
if (Configure::read('MISP.background_jobs')) {
$workerIssueCount = 0;
$worker_array = $this->Server->workerDiagnostics($workerIssueCount);
}
$this->set('worker_array', $worker_array);
if ($tab == 'download' || $this->_isRest()) {
foreach ($dumpResults as $key => $dr) {
unset($dumpResults[$key]['description']);
}
$dump = array(
'version' => $version,
'phpSettings' => $phpSettings,
'gpgStatus' => $gpgErrors[$gpgStatus['status']],
'proxyStatus' => $proxyErrors[$proxyStatus],
'zmqStatus' => $zmqStatus,
'stix' => $stix,
'moduleStatus' => $moduleStatus,
'writeableDirs' => $writeableDirs,
'writeableFiles' => $writeableFiles,
'readableFiles' => $readableFiles,
'dbDiagnostics' => $dbDiagnostics,
'dbSchemaDiagnostics' => $dbSchemaDiagnostics,
'redisInfo' => $redisInfo,
'finalSettings' => $dumpResults,
'extensions' => $extensions,
'workers' => $worker_array
);
foreach ($dump['finalSettings'] as $k => $v) {
if (!empty($v['redacted'])) {
$dump['finalSettings'][$k]['value'] = '*****';
}
}
$this->response->body(json_encode($dump, JSON_PRETTY_PRINT));
$this->response->type('json');
$this->response->download('MISP.report.json');
return $this->response;
}
$priorities = array(0 => 'Critical', 1 => 'Recommended', 2 => 'Optional', 3 => 'Deprecated');
$this->set('priorities', $priorities);
$this->set('workerIssueCount', $workerIssueCount);
$priorityErrorColours = array(0 => 'red', 1 => 'yellow', 2 => 'green');
$this->set('priorityErrorColours', $priorityErrorColours);
$this->set('phpversion', phpversion());
$this->set('phpmin', $this->phpmin);
$this->set('phprec', $this->phprec);
$this->set('phptoonew', $this->phptoonew);
$this->set('pythonmin', $this->pythonmin);
$this->set('pythonrec', $this->pythonrec);
$this->set('pymisp', $this->pymisp);
}
$finalSettings = $tempArray;
// Diagnostics portion
$diagnostic_errors = 0;
App::uses('File', 'Utility');
App::uses('Folder', 'Utility');
if ($tab === 'files') {
$files = $this->Server->grabFiles();
$this->set('files', $files);
}
// Only run this check on the diagnostics tab
if ($tab === 'diagnostics' || $tab === 'download' || $this->_isRest()) {
$php_ini = php_ini_loaded_file();
$this->set('php_ini', $php_ini);
$attachmentTool = new AttachmentTool();
try {
$advanced_attachments = $attachmentTool->checkAdvancedExtractionStatus($this->Server->getPythonVersion());
} catch (Exception $e) {
$this->log($e->getMessage(), LOG_NOTICE);
$advanced_attachments = false;
}
$this->set('advanced_attachments', $advanced_attachments);
// check if the current version of MISP is outdated or not
$version = $this->__checkVersion();
$this->set('version', $version);
$gitStatus = $this->Server->getCurrentGitStatus();
$this->set('branch', $gitStatus['branch']);
$this->set('commit', $gitStatus['commit']);
$this->set('latestCommit', $gitStatus['latestCommit']);
$phpSettings = array(
'max_execution_time' => array(
'explanation' => 'The maximum duration that a script can run (does not affect the background workers). A too low number will break long running scripts like comprehensive API exports',
'recommended' => 300,
'unit' => 'seconds',
),
'memory_limit' => array(
'explanation' => 'The maximum memory that PHP can consume. It is recommended to raise this number since certain exports can generate a fair bit of memory usage',
'recommended' => 2048,
'unit' => 'MB'
),
'upload_max_filesize' => array(
'explanation' => 'The maximum size that an uploaded file can be. It is recommended to raise this number to allow for the upload of larger samples',
'recommended' => 50,
'unit' => 'MB'
),
'post_max_size' => array(
'explanation' => 'The maximum size of a POSTed message, this has to be at least the same size as the upload_max_filesize setting',
'recommended' => 50,
'unit' => 'MB'
)
);
foreach ($phpSettings as $setting => $settingArray) {
$phpSettings[$setting]['value'] = $this->Server->getIniSetting($setting);
if ($phpSettings[$setting]['value'] && $settingArray['unit'] && $settingArray['unit'] === 'MB') {
// convert basic unit to M
$phpSettings[$setting]['value'] = (int) floor($phpSettings[$setting]['value'] / 1024 / 1024);
}
}
$this->set('phpSettings', $phpSettings);
if ($version && (!$version['upToDate'] || $version['upToDate'] == 'older')) {
$diagnostic_errors++;
}
// check if the STIX and Cybox libraries are working and the correct version using the test script stixtest.py
$stix = $this->Server->stixDiagnostics($diagnostic_errors, $stixVersion, $cyboxVersion, $mixboxVersion, $maecVersion, $stix2Version, $pymispVersion);
$yaraStatus = $this->Server->yaraDiagnostics($diagnostic_errors);
// if GnuPG is set up in the settings, try to encrypt a test message
$gpgStatus = $this->Server->gpgDiagnostics($diagnostic_errors);
// if the message queue pub/sub is enabled, check whether the extension works
$zmqStatus = $this->Server->zmqDiagnostics($diagnostic_errors);
// if Proxy is set up in the settings, try to connect to a test URL
$proxyStatus = $this->Server->proxyDiagnostics($diagnostic_errors);
// get the DB diagnostics
$dbDiagnostics = $this->Server->dbSpaceUsage();
$dbSchemaDiagnostics = $this->Server->dbSchemaDiagnostic();
$redisInfo = $this->Server->redisInfo();
$moduleTypes = array('Enrichment', 'Import', 'Export', 'Cortex');
foreach ($moduleTypes as $type) {
$moduleStatus[$type] = $this->Server->moduleDiagnostics($diagnostic_errors, $type);
}
// check the size of the session table
$sessionCount = 0;
$sessionStatus = $this->Server->sessionDiagnostics($diagnostic_errors, $sessionCount);
$this->set('sessionCount', $sessionCount);
$this->loadModel('AttachmentScan');
try {
$attachmentScan = ['status' => true, 'software' => $this->AttachmentScan->diagnostic()];
} catch (Exception $e) {
$attachmentScan = ['status' => false, 'error' => $e->getMessage()];
}
$securityAudit = (new SecurityAudit())->run($this->Server);
$view = compact('gpgStatus', 'sessionErrors', 'proxyStatus', 'sessionStatus', 'zmqStatus', 'stixVersion', 'cyboxVersion', 'mixboxVersion', 'maecVersion', 'stix2Version', 'pymispVersion', 'moduleStatus', 'yaraStatus', 'gpgErrors', 'proxyErrors', 'zmqErrors', 'stixOperational', 'stix', 'moduleErrors', 'moduleTypes', 'dbDiagnostics', 'dbSchemaDiagnostics', 'redisInfo', 'attachmentScan', 'securityAudit');
} else {
$view = [];
}
// check whether the files are writeable
$writeableDirs = $this->Server->writeableDirsDiagnostics($diagnostic_errors);
$writeableFiles = $this->Server->writeableFilesDiagnostics($diagnostic_errors);
$readableFiles = $this->Server->readableFilesDiagnostics($diagnostic_errors);
$extensions = $this->Server->extensionDiagnostics();
// check if the encoding is not set to utf8
$dbEncodingStatus = $this->Server->databaseEncodingDiagnostics($diagnostic_errors);
$view = array_merge($view, compact('diagnostic_errors', 'tabs', 'tab', 'issues', 'finalSettings', 'writeableErrors', 'readableErrors', 'writeableDirs', 'writeableFiles', 'readableFiles', 'extensions', 'dbEncodingStatus'));
$this->set($view);
$workerIssueCount = 4;
$worker_array = array();
if (Configure::read('MISP.background_jobs')) {
$workerIssueCount = 0;
$worker_array = $this->Server->workerDiagnostics($workerIssueCount);
}
$this->set('worker_array', $worker_array);
if ($tab === 'download' || $this->_isRest()) {
foreach ($dumpResults as $key => $dr) {
unset($dumpResults[$key]['description']);
}
$dump = array(
'version' => $version,
'phpSettings' => $phpSettings,
'gpgStatus' => $gpgErrors[$gpgStatus['status']],
'proxyStatus' => $proxyErrors[$proxyStatus],
'zmqStatus' => $zmqStatus,
'stix' => $stix,
'moduleStatus' => $moduleStatus,
'writeableDirs' => $writeableDirs,
'writeableFiles' => $writeableFiles,
'readableFiles' => $readableFiles,
'dbDiagnostics' => $dbDiagnostics,
'dbSchemaDiagnostics' => $dbSchemaDiagnostics,
'redisInfo' => $redisInfo,
'finalSettings' => $dumpResults,
'extensions' => $extensions,
'workers' => $worker_array
);
foreach ($dump['finalSettings'] as $k => $v) {
if (!empty($v['redacted'])) {
$dump['finalSettings'][$k]['value'] = '*****';
}
}
$this->response->body(json_encode($dump, JSON_PRETTY_PRINT));
$this->response->type('json');
$this->response->download('MISP.report.json');
return $this->response;
}
$priorities = array(0 => 'Critical', 1 => 'Recommended', 2 => 'Optional', 3 => 'Deprecated');
$this->set('priorities', $priorities);
$this->set('workerIssueCount', $workerIssueCount);
$priorityErrorColours = array(0 => 'red', 1 => 'yellow', 2 => 'green');
$this->set('priorityErrorColours', $priorityErrorColours);
$this->set('phpversion', phpversion());
$this->set('phpmin', $this->phpmin);
$this->set('phprec', $this->phprec);
$this->set('phptoonew', $this->phptoonew);
$this->set('pythonmin', $this->pythonmin);
$this->set('pythonrec', $this->pythonrec);
$this->set('pymisp', $this->pymisp);
}
public function startWorker($type)
@ -1250,9 +1274,6 @@ class ServersController extends AppController
private function __checkVersion()
{
if (!$this->_isSiteAdmin()) {
throw new MethodNotAllowedException();
}
App::uses('SyncTool', 'Tools');
$syncTool = new SyncTool();
try {
@ -1342,17 +1363,20 @@ class ServersController extends AppController
continue;
}
$exception = null;
try {
$remote_event = $this->Event->downloadEventFromServer($local_event['Event']['uuid'], $server, null, true);
$remote_event_id = $remote_event[0]['id'];
$remoteEvent = $this->Event->downloadEventFromServer($local_event['Event']['uuid'], $server, null, true);
} catch (Exception $e) {
$remote_event_id = null;
$remoteEvent = null;
$exception = $e->getMessage();
}
$remoteEventId = isset($remoteEvent[0]['id']) ? $remoteEvent[0]['id'] : null;
$remote_events[] = array(
"server_id" => $server['Server']['id'],
"server_name" => $server['Server']['name'],
"url" => isset($remote_event_id) ? $server['Server']['url']."/events/view/".$remote_event_id : $server['Server']['url'],
"remote_id" => isset($remote_event_id) ? $remote_event_id : false
"url" => isset($remoteEventId) ? $server['Server']['url'] . "/events/view/" . $remoteEventId : $server['Server']['url'],
"remote_id" => isset($remoteEventId) ? $remoteEventId : false,
"exception" => $exception,
);
}
@ -1362,10 +1386,8 @@ class ServersController extends AppController
$this->set('title_for_layout', __('Event ID translator'));
}
public function getSubmodulesStatus() {
if (!$this->_isSiteAdmin()) {
throw new MethodNotAllowedException();
}
public function getSubmodulesStatus()
{
$this->set('submodules', $this->Server->getSubmodulesGitStatus());
$this->render('ajax/submoduleStatus');
}
@ -1384,9 +1406,6 @@ class ServersController extends AppController
public function serverSettingsEdit($setting_name, $id = false, $forceSave = false)
{
if (!$this->_isSiteAdmin()) {
throw new MethodNotAllowedException();
}
if (!isset($setting_name)) {
throw new MethodNotAllowedException();
}
@ -1518,15 +1537,6 @@ class ServersController extends AppController
$this->redirect(array('controller' => 'servers', 'action' => 'serverSettings', 'workers'));
}
private function __manageFiles()
{
if (!$this->_isSiteAdmin()) {
throw new MethodNotAllowedException();
}
$files = $this->Server->grabFiles();
return $files;
}
public function deleteFile($type, $filename)
{
if (!$this->_isSiteAdmin()) {
@ -1652,10 +1662,6 @@ class ServersController extends AppController
public function testConnection($id = false)
{
if (!$this->Auth->user('Role')['perm_sync'] && !$this->Auth->user('Role')['perm_site_admin']) {
throw new MethodNotAllowedException('You don\'t have permission to do that.');
}
$server = $this->Server->find('first', ['conditions' => ['Server.id' => $id]]);
if (!$server) {
throw new NotFoundException(__('Invalid server'));
@ -1704,23 +1710,17 @@ class ServersController extends AppController
$result['status'] = 8;
return new CakeResponse(array('body'=> json_encode($result), 'type' => 'json'));
}
return new CakeResponse(
array(
'body'=> json_encode(
array(
'status' => 1,
'local_version' => implode('.', $local_version),
'version' => implode('.', $version),
'mismatch' => $mismatch,
'newer' => $newer,
'post' => isset($post) ? $post['status'] : 'too old',
'response_encoding' => isset($post['content-encoding']) ? $post['content-encoding'] : null,
'client_certificate' => $result['client_certificate'],
)
),
'type' => 'json'
)
);
return $this->RestResponse->viewData([
'status' => 1,
'local_version' => implode('.', $local_version),
'version' => implode('.', $version),
'mismatch' => $mismatch,
'newer' => $newer,
'post' => isset($post) ? $post['status'] : 'too old',
'response_encoding' => isset($post['content-encoding']) ? $post['content-encoding'] : null,
'request_encoding' => isset($result['info']['request_encoding']) ? $result['info']['request_encoding'] : null,
'client_certificate' => $result['client_certificate'],
], 'json');
} else {
$result['status'] = 3;
}
@ -1800,17 +1800,15 @@ class ServersController extends AppController
public function getVersion()
{
if (!$this->userRole['perm_auth']) {
throw new MethodNotAllowedException('This action requires API access.');
}
$versionArray = $this->Server->checkMISPVersion();
$this->set('response', array(
$response = [
'version' => $versionArray['major'] . '.' . $versionArray['minor'] . '.' . $versionArray['hotfix'],
'perm_sync' => $this->userRole['perm_sync'],
'perm_sighting' => $this->userRole['perm_sighting'],
'perm_galaxy_editor' => $this->userRole['perm_galaxy_editor'],
));
$this->set('_serialize', 'response');
'request_encoding' => $this->CompressedRequestHandler->supportedEncodings(),
];
return $this->RestResponse->viewData($response, $this->response->type());
}
public function getPyMISPVersion()
@ -2059,7 +2057,8 @@ class ServersController extends AppController
App::uses('HttpSocket', 'Network/Http');
$HttpSocket = new HttpSocket($params);
$view_data = array();
$temp_headers = explode("\n", $request['header']);
$temp_headers = empty($request['header']) ? [] : explode("\n", $request['header']);
$request['header'] = array(
'Authorization' => $this->Auth->user('authkey'),
'Accept' => 'application/json',
@ -2443,6 +2442,27 @@ misp.direct_call(relative_path, body)
}
}
public function cspReport()
{
if (!$this->request->is('post')) {
throw new MethodNotAllowedException('This action expects a POST request.');
}
$report = $this->Server->jsonDecode($this->request->input());
if (!isset($report['csp-report'])) {
throw new RuntimeException("Invalid report");
}
$message = 'CSP reported violation';
$remoteIp = $this->_remoteIp();
if ($remoteIp) {
$message .= ' from IP ' . $remoteIp;
}
$this->log("$message: " . json_encode($report['csp-report'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
return new CakeResponse(['statusCodes' => 204]);
}
public function viewDeprecatedFunctionUse()
{
$data = $this->Deprecation->getDeprecatedAccessList($this->Server);

View File

@ -828,6 +828,52 @@ class ShadowAttributesController extends AppController
$this->set('_serialize', array('ShadowAttribute'));
}
public function viewPicture($id, $thumbnail=false)
{
$conditions['ShadowAttribute.id'] = $id;
$conditions['ShadowAttribute.type'] = 'attachment';
$options = array(
'conditions' => $conditions,
'includeAllTags' => false,
'includeAttributeUuid' => true,
'flatten' => true,
'deleted' => [0, 1]
);
$sa = $this->ShadowAttribute->find('first', array(
'recursive' => -1,
'contain' => ['Event', 'Attribute'], // required because of conditions
'fields' => array(
'ShadowAttribute.id', 'ShadowAttribute.old_id', 'ShadowAttribute.event_id', 'ShadowAttribute.type', 'ShadowAttribute.category', 'ShadowAttribute.uuid', 'ShadowAttribute.to_ids', 'ShadowAttribute.value', 'ShadowAttribute.comment', 'ShadowAttribute.org_id', 'ShadowAttribute.first_seen', 'ShadowAttribute.last_seen',
),
'conditions' => $conditions,
));
if (empty($sa)) {
throw new NotFoundException(__('Invalid proposal.'));
}
if (!$this->ShadowAttribute->Attribute->isImage($sa['ShadowAttribute'])) {
throw new NotFoundException("ShadowAttribute is not an image.");
}
if ($this->_isRest()) {
if ($this->ShadowAttribute->typeIsAttachment($sa['ShadowAttribute']['type'])) {
$encodedFile = $this->ShadowAttribute->base64EncodeAttachment($sa['ShadowAttribute']);
$sa['ShadowAttribute']['data'] = $encodedFile;
}
}
if ($this->_isRest()) {
return $this->RestResponse->viewData($sa['ShadowAttribute']['data'], $this->response->type());
} else {
$width = isset($this->request->params['named']['width']) ? $this->request->params['named']['width'] : 200;
$height = isset($this->request->params['named']['height']) ? $this->request->params['named']['height'] : 200;
$imageData = $this->ShadowAttribute->getPictureData($sa, $thumbnail, $width, $height);
$extension = pathinfo($sa['ShadowAttribute']['value'], PATHINFO_EXTENSION);
return new CakeResponse(array('body' => $imageData, 'type' => strtolower($extension)));
}
}
public function index($eventId = false)
{
$conditions = array();

View File

@ -608,7 +608,7 @@ class SharingGroupsController extends AppController
}
}
}
if (false === $addServer) {
if (false === $removeServer) {
return $this->RestResponse->saveFailResponse('SharingGroup', $this->action, false, 'Server is not in the sharing group.', $this->response->type());
}
$result = $this->SharingGroup->SharingGroupServer->delete($removeServer);

View File

@ -223,7 +223,7 @@ class SightingsController extends AppController
}
}
// takes a sighting ID
// takes a sighting ID or UUID
public function delete($id)
{
if (!$this->userRole['perm_modify_org']) {
@ -232,22 +232,22 @@ class SightingsController extends AppController
if (!$this->request->is('post')) {
throw new MethodNotAllowedException('This action can only be accessed via a post request.');
}
$id = $this->Toolbox->findIdByUuid($this->Sighting, $id);
$conditions = array('Sighting.id' => $id);
$sighting = $this->Sighting->find('first', array('conditions' => $conditions, 'recursive' => -1));
$sighting = $this->Sighting->find('first', array(
'conditions' => Validation::uuid($id) ? ['Sighting.uuid' => $id] : ['Sighting.id' => $id],
'recursive' => -1,
'fields' => ['id', 'org_id'],
));
if (empty($sighting)) {
throw new NotFoundException('Invalid sighting.');
}
if (!$this->_isSiteAdmin()) {
if ($sighting['Sighting']['org_id'] != $this->Auth->user('org_id')) {
throw new NotFoundException('Invalid sighting.');
}
if (!$this->_isSiteAdmin() && $sighting['Sighting']['org_id'] != $this->Auth->user('org_id')) {
throw new NotFoundException('Invalid sighting.');
}
$result = $this->Sighting->delete($sighting['Sighting']['id']);
if (!$result) {
return $this->RestResponse->saveFailResponse('Sighting', 'delete', $id, 'Could not delete the Sighting.');
} else {
return $this->RestResponse->saveSuccessResponse('Sighting', 'delete', $id, false, 'Sighting successfuly deleted.');
return $this->RestResponse->saveSuccessResponse('Sighting', 'delete', $id, false, 'Sighting successfully deleted.');
}
}

View File

@ -41,7 +41,6 @@ class TagsController extends AppController
'request' => $this->request,
'named_params' => $this->params['named'],
'paramArray' => ['favouritesOnly', 'filter', 'searchall', 'name', 'search', 'exclude_statistics'],
'ordered_url_params' => @compact($paramArray)
);
$exception = false;
$passedArgsArray = $this->_harvestParameters($filterData, $exception);
@ -140,9 +139,6 @@ class TagsController extends AppController
public function add()
{
if (!$this->_isSiteAdmin() && !$this->userRole['perm_tag_editor']) {
throw new NotFoundException('You don\'t have permission to do that.');
}
if ($this->request->is('post')) {
if (!isset($this->request->data['Tag'])) {
$this->request->data = array('Tag' => $this->request->data);
@ -193,31 +189,22 @@ class TagsController extends AppController
} elseif ($this->_isRest()) {
return $this->RestResponse->describe('Tag', 'add', false, $this->response->type());
}
$this->loadModel('Organisation');
$temp = $this->Organisation->find('all', array(
$orgs = $this->Tag->Organisation->find('list', array(
'conditions' => array('local' => 1),
'fields' => array('id', 'name'),
'recursive' => -1
'order' => 'name',
));
$orgs = array(0 => 'Unrestricted');
if (!empty($temp)) {
foreach ($temp as $org) {
$orgs[$org['Organisation']['id']] = $org['Organisation']['name'];
}
}
$orgs = [0 => 'Unrestricted'] + $orgs;
$this->set('orgs', $orgs);
$users = array(0 => 'Unrestricted');
if ($this->_isSiteAdmin()) {
$temp = $this->Organisation->User->find('all', array(
$users = $this->Tag->User->find('list', array(
'conditions' => array('disabled' => 0),
'fields' => array('id', 'email'),
'recursive' => -1
'order' => 'email',
));
if (!empty($temp)) {
foreach ($temp as $user) {
$users[$user['User']['id']] = $user['User']['email'];
}
}
$users = [0 => 'Unrestricted'] + $users;
$this->set('users', $users);
}
}
@ -248,9 +235,6 @@ class TagsController extends AppController
throw new NotFoundException('Invalid tag');
}
}
if (!$this->_isSiteAdmin()) {
throw new NotFoundException('You don\'t have permission to do that.');
}
if ($this->request->is('post') || $this->request->is('put')) {
if (!isset($this->request->data['Tag'])) {
$this->request->data = array('Tag' => $this->request->data);
@ -281,41 +265,28 @@ class TagsController extends AppController
} elseif ($this->_isRest()) {
return $this->RestResponse->describe('Tag', 'edit', false, $this->response->type());
}
$this->loadModel('Organisation');
$temp = $this->Organisation->find('all', array(
$orgs = $this->Tag->Organisation->find('list', array(
'conditions' => array('local' => 1),
'fields' => array('id', 'name'),
'recursive' => -1
'order' => 'name',
));
$orgs = array(0 => 'Unrestricted');
if (!empty($temp)) {
foreach ($temp as $org) {
$orgs[$org['Organisation']['id']] = $org['Organisation']['name'];
}
}
$orgs = [0 => 'Unrestricted'] + $orgs;
$this->set('orgs', $orgs);
$users = array(0 => 'Unrestricted');
if ($this->_isSiteAdmin()) {
$temp = $this->Organisation->User->find('all', array(
'conditions' => array('disabled' => 0),
'fields' => array('id', 'email'),
'recursive' => -1
));
if (!empty($temp)) {
foreach ($temp as $user) {
$users[$user['User']['id']] = $user['User']['email'];
}
}
$this->set('users', $users);
}
$users = $this->Tag->User->find('list', array(
'conditions' => array('disabled' => 0),
'fields' => array('id', 'email'),
'order' => 'email',
));
$users = [0 => 'Unrestricted'] + $users;
$this->set('users', $users);
$this->request->data = $this->Tag->read(null, $id);
}
public function delete($id)
{
if (!$this->_isSiteAdmin()) {
throw new NotFoundException('You don\'t have permission to do that.');
}
if (!$this->request->is('post')) {
throw new MethodNotAllowedException();
}
@ -344,79 +315,33 @@ class TagsController extends AppController
public function view($id)
{
if ($this->_isRest()) {
$contain = array('EventTag' => array('fields' => 'event_id'));
$contain['AttributeTag'] = array('fields' => 'attribute_id');
$tag = $this->Tag->find('first', array(
'conditions' => array('id' => $id),
'recursive' => -1,
'contain' => $contain
));
if (empty($tag)) {
throw new MethodNotAllowedException('Invalid Tag');
}
if (empty($tag['EventTag'])) {
$tag['Tag']['count'] = 0;
} else {
$eventIDs = array();
foreach ($tag['EventTag'] as $eventTag) {
$eventIDs[] = $eventTag['event_id'];
}
$conditions = array('Event.id' => $eventIDs);
if (!$this->_isSiteAdmin()) {
$conditions = array_merge(
$conditions,
array('OR' => array(
array('AND' => array(
array('Event.distribution >' => 0),
array('Event.published =' => 1)
)),
array('Event.orgc_id' => $this->Auth->user('org_id'))
))
);
}
$events = $this->Tag->EventTag->Event->find('all', array(
'fields' => array('Event.id', 'Event.distribution', 'Event.orgc_id'),
'conditions' => $conditions
));
$tag['Tag']['count'] = count($events);
}
unset($tag['EventTag']);
if (empty($tag['AttributeTag'])) {
$tag['Tag']['attribute_count'] = 0;
} else {
$attributeIDs = array();
foreach ($tag['AttributeTag'] as $attributeTag) {
$attributeIDs[] = $attributeTag['attribute_id'];
}
$conditions = array('Attribute.id' => $attributeIDs);
if (!$this->_isSiteAdmin()) {
$conditions = array_merge(
$conditions,
array('OR' => array(
array('AND' => array(
array('Attribute.deleted =' => 0),
array('Attribute.distribution >' => 0),
array('Event.distribution >' => 0),
array('Event.published =' => 1)
)),
array('Event.orgc_id' => $this->Auth->user('org_id'))
))
);
}
$attributes = $this->Tag->AttributeTag->Attribute->find('all', array(
'fields' => array('Attribute.id', 'Attribute.deleted', 'Attribute.distribution', 'Event.id', 'Event.distribution', 'Event.orgc_id'),
'contain' => array('Event' => array('fields' => array('id', 'distribution', 'orgc_id'))),
'conditions' => $conditions
));
$tag['Tag']['attribute_count'] = count($attributes);
}
unset($tag['AttributeTag']);
$this->set('Tag', $tag['Tag']);
$this->set('_serialize', 'Tag');
} else {
if (!$this->_isRest()) {
throw new MethodNotAllowedException('This action is only for REST users.');
}
$tag = $this->Tag->find('first', array(
'conditions' => array('id' => $id),
'recursive' => -1,
'contain' => ['AttributeTag' => ['fields' => 'attribute_id']],
));
if (empty($tag)) {
throw new MethodNotAllowedException('Invalid Tag');
}
$tag['Tag']['count'] = $this->Tag->EventTag->countForTag($tag['Tag']['id'], $this->Auth->user());
if (empty($tag['AttributeTag'])) {
$tag['Tag']['attribute_count'] = 0;
} else {
$attributeIDs = array_column($tag['AttributeTag'], 'attribute_id');
$tag['Tag']['attribute_count'] = count($this->Tag->AttributeTag->Attribute->fetchAttributes($this->Auth->user(), [
'conditions' => ['Attribute.id' => $attributeIDs],
'list' => true,
]));
}
unset($tag['AttributeTag']);
return $this->RestResponse->viewData($tag['Tag'], $this->response->type());
}
public function showEventTag($id)
@ -439,7 +364,7 @@ class TagsController extends AppController
$event = $this->Tag->EventTag->Event->massageTags($this->Auth->user(), $event, 'Event', false, true);
$this->set('tags', $event['EventTag']);
$this->set('required_taxonomies', $this->Tag->EventTag->Event->getRequiredTaxonomies());
$this->set('missingTaxonomies', $this->Tag->EventTag->Event->missingTaxonomies($event));
$tagConflicts = $this->Taxonomy->checkIfTagInconsistencies($event['EventTag']);
$this->set('tagConflicts', $tagConflicts);
$this->set('event', $event);

View File

@ -9,17 +9,17 @@ class TaxonomiesController extends AppController
public $components = array('Session', 'RequestHandler');
public $paginate = array(
'limit' => 60,
'maxLimit' => 9999, // LATER we will bump here on a problem once we have more than 9999 events <- no we won't, this is the max a user van view/page.
'contain' => array(
'TaxonomyPredicate' => array(
'fields' => array('TaxonomyPredicate.id'),
'TaxonomyEntry' => array('fields' => array('TaxonomyEntry.id'))
)
),
'order' => array(
'Taxonomy.id' => 'DESC'
),
'limit' => 60,
'maxLimit' => 9999, // LATER we will bump here on a problem once we have more than 9999 events <- no we won't, this is the max a user van view/page.
'contain' => array(
'TaxonomyPredicate' => array(
'fields' => array('TaxonomyPredicate.id', 'TaxonomyPredicate.value'),
'TaxonomyEntry' => array('fields' => array('TaxonomyEntry.id', 'TaxonomyEntry.value'))
)
),
'order' => array(
'Taxonomy.id' => 'DESC'
),
);
public function index()
@ -46,25 +46,15 @@ class TaxonomiesController extends AppController
} else {
$taxonomies = $this->paginate();
}
$this->loadModel('Tag');
foreach ($taxonomies as $key => $taxonomy) {
$total = 0;
foreach ($taxonomy['TaxonomyPredicate'] as $predicate) {
$total += empty($predicate['TaxonomyEntry']) ? 1 : count($predicate['TaxonomyEntry']);
}
$taxonomies[$key]['total_count'] = $total;
$taxonomies[$key]['current_count'] = $this->Tag->find('count', array(
'conditions' => array('lower(Tag.name) LIKE ' => strtolower($taxonomy['Taxonomy']['namespace']) . ':%', 'hide_tag' => 0),
'recursive' => -1,
));
unset($taxonomies[$key]['TaxonomyPredicate']);
}
$taxonomies = $this->__tagCount($taxonomies);
if ($this->_isRest()) {
return $this->RestResponse->viewData($taxonomies, $this->response->type());
} else {
$this->set('taxonomies', $taxonomies);
$this->set('passedArgsArray', $this->passedArgs);
}
$this->set('taxonomies', $taxonomies);
$this->set('passedArgsArray', $this->passedArgs);
}
public function view($id)
@ -111,14 +101,15 @@ class TaxonomiesController extends AppController
$params = $customPagination->applyRulesOnArray($taxonomy['entries'], $params, 'taxonomies');
if ($this->_isRest()) {
return $this->RestResponse->viewData($taxonomy, $this->response->type());
} else {
$this->set('entries', $taxonomy['entries']);
$this->set('urlparams', $urlparams);
$this->set('passedArgs', json_encode($passedArgs));
$this->set('passedArgsArray', $passedArgs);
$this->set('taxonomy', $taxonomy['Taxonomy']);
$this->set('id', $id);
}
$this->set('entries', $taxonomy['entries']);
$this->set('urlparams', $urlparams);
$this->set('passedArgs', json_encode($passedArgs));
$this->set('passedArgsArray', $passedArgs);
$this->set('taxonomy', $taxonomy['Taxonomy']);
$this->set('id', $id);
$this->set('title_for_layout', __('%s Taxonomy Library', h(strtoupper($taxonomy['Taxonomy']['namespace']))));
}
public function enable($id)
@ -469,6 +460,53 @@ class TaxonomiesController extends AppController
$this->render('ajax/toggle_required');
}
/**
* Attach tag counts.
* @param array $taxonomies
* @return array
*/
private function __tagCount(array $taxonomies)
{
$tags = [];
foreach ($taxonomies as $taxonomyPos => $taxonomy) {
$total = 0;
foreach ($taxonomy['TaxonomyPredicate'] as $predicate) {
if (isset($predicate['TaxonomyEntry']) && !empty($predicate['TaxonomyEntry'])) {
foreach ($predicate['TaxonomyEntry'] as $entry) {
$tag = mb_strtolower($taxonomy['Taxonomy']['namespace'] . ':' . $predicate['value'] . '="' . $entry['value'] . '"');
$tags[$tag] = $taxonomyPos;
$total++;
}
} else {
$tag = mb_strtolower($taxonomy['Taxonomy']['namespace'] . ':' . $predicate['value']);
$tags[$tag] = $taxonomyPos;
$total++;
}
}
$taxonomies[$taxonomyPos]['total_count'] = $total;
$taxonomies[$taxonomyPos]['current_count'] = 0;
unset($taxonomies[$taxonomyPos]['TaxonomyPredicate']);
}
$this->loadModel('Tag');
$existingTags = $this->Tag->find('column', [
'fields' => ['Tag.name'],
'conditions' => [
'lower(Tag.name)' => array_keys($tags),
'hide_tag' => 0
],
]);
foreach ($existingTags as $existingTag) {
$existingTag = mb_strtolower($existingTag);
if (isset($tags[$existingTag])) {
$taxonomies[$tags[$existingTag]]['current_count']++;
}
}
return $taxonomies;
}
private function __search($value)
{
$value = mb_strtolower(trim($value));

View File

@ -922,23 +922,13 @@ class UsersController extends AppController
continue;
}
if ($field != 'password') {
array_push($fields, $field);
}
}
$fieldsOldValues = array();
foreach ($fields as $field) {
if ($field == 'enable_password') {
continue;
}
if ($field != 'confirm_password') {
$fieldsOldValues[$field] = $this->User->field($field);
} else {
$fieldsOldValues[$field] = $this->User->field('password');
$fields[] = $field;
}
}
if (
isset($this->request->data['User']['enable_password']) && $this->request->data['User']['enable_password'] != '0' &&
isset($this->request->data['User']['password']) && "" != $this->request->data['User']['password']
(!empty($this->request->data['User']['enable_password']) || $this->_isRest()) &&
!empty($this->request->data['User']['password']) &&
$this->__canChangePassword()
) {
$fields[] = 'password';
if ($this->_isRest() && !isset($this->request->data['User']['confirm_password'])) {
@ -958,6 +948,12 @@ class UsersController extends AppController
}
}
$fields[] = 'date_modified'; // time will be inserted in `beforeSave` action
$fieldsOldValues = $this->User->find('first', [
'recursive' => -1,
'conditions' => ['id' => $id],
])['User'];
if ($this->User->save($this->request->data, true, $fields)) {
// newValues to array
$fieldsNewValues = array();
@ -967,7 +963,7 @@ class UsersController extends AppController
}
if ($field !== 'confirm_password') {
$newValue = $this->data['User'][$field];
if (gettype($newValue) == 'array') {
if (is_array($newValue)) {
$newValueStr = '';
$cP = 0;
foreach ($newValue as $newValuePart) {
@ -989,6 +985,9 @@ class UsersController extends AppController
// compare
$fieldsResult = array();
foreach ($fields as $field) {
if ($field === 'date_modified') {
continue;
}
if (isset($fieldsOldValues[$field]) && $fieldsOldValues[$field] != $fieldsNewValues[$field]) {
if ($field != 'confirm_password' && $field != 'enable_password') {
$fieldsResult[$field] = array($fieldsOldValues[$field], $fieldsNewValues[$field]);
@ -1138,7 +1137,7 @@ class UsersController extends AppController
}
if ($this->request->is('post') && Configure::read('Security.email_otp_enabled')) {
$user = $this->Auth->identify($this->request, $this->response);
if ($user) {
if ($user && !$user['disabled']) {
$this->Session->write('email_otp_user', $user);
return $this->redirect('email_otp');
}
@ -1295,24 +1294,22 @@ class UsersController extends AppController
public function resetauthkey($id = null, $alert = false)
{
if (!$this->_isAdmin() && Configure::read('MISP.disableUserSelfManagement')) {
throw new MethodNotAllowedException('User self-management has been disabled on this instance.');
}
if (!$this->request->is('post') && !$this->request->is('put')) {
throw new MethodNotAllowedException(__('This functionality is only accessible via POST requests.'));
}
if ($id == 'me') {
if ($id === 'me') {
$id = $this->Auth->user('id');
// Reset just current auth key
$keyId = isset($this->Auth->user()['authkey_id']) ? $this->Auth->user()['authkey_id'] : null;
} else {
$keyId = null;
}
if (!$this->userRole['perm_auth']) {
throw new MethodNotAllowedException(__('Invalid action.'));
}
$newkey = $this->User->resetauthkey($this->Auth->user(), $id, $alert);
$newkey = $this->User->resetauthkey($this->Auth->user(), $id, $alert, $keyId);
if ($newkey === false) {
throw new MethodNotAllowedException(__('Invalid user.'));
}
if (!$this->_isRest()) {
$this->Flash->success(__('New authkey generated.', true));
$this->Flash->success(__('New authkey generated.'));
$this->redirect($this->referer());
} else {
return $this->RestResponse->saveSuccessResponse('User', 'resetauthkey', $id, $this->response->type(), 'Authkey updated: ' . $newkey);
@ -1693,7 +1690,7 @@ class UsersController extends AppController
if ($this->request->is('post') && isset($this->request->data['User']['otp'])) {
$stored_otp = $redis->get('misp:otp:' . $user_id);
if (!empty($stored_otp) && $this->request->data['User']['otp'] == $stored_otp) {
if (!empty($stored_otp) && trim($this->request->data['User']['otp']) == $stored_otp) {
// we invalidate the previously generated OTP
$redis->del('misp:otp:' . $user_id);
// We login the user with CakePHP

View File

@ -9,20 +9,20 @@ class WarninglistsController extends AppController
public $components = array('Session', 'RequestHandler');
public $paginate = array(
'limit' => 60,
'maxLimit' => 9999, // LATER we will bump here on a problem once we have more than 9999 events <- no we won't, this is the max a user can view/page.
'contain' => array(
'WarninglistType'
),
'order' => array(
'Warninglist.id' => 'DESC'
),
'limit' => 60,
'maxLimit' => 9999, // LATER we will bump here on a problem once we have more than 9999 events <- no we won't, this is the max a user can view/page.
'contain' => array(
'WarninglistType'
),
'order' => array(
'Warninglist.id' => 'DESC'
),
'recursive' => -1,
);
public function index()
{
$filters = $this->IndexFilter->harvestParameters(['value']);
$this->paginate['recursive'] = -1;
$filters = $this->IndexFilter->harvestParameters(['value', 'enabled']);
if (!empty($filters['value'])) {
$this->paginate['conditions'] = [
'OR' => [
@ -32,20 +32,20 @@ class WarninglistsController extends AppController
]
];
}
if (isset($filters['enabled'])) {
$this->paginate['conditions'][] = ['Warninglist.enabled' => $filters['enabled']];
}
$warninglists = $this->paginate();
foreach ($warninglists as &$warninglist) {
$warninglist['Warninglist']['valid_attributes'] = array();
foreach ($warninglist['WarninglistType'] as $type) {
$warninglist['Warninglist']['valid_attributes'][] = $type['type'];
}
$warninglist['Warninglist']['valid_attributes'] = implode(', ', $warninglist['Warninglist']['valid_attributes']);
$validAttributes = array_column($warninglist['WarninglistType'], 'type');
$warninglist['Warninglist']['valid_attributes'] = implode(', ', $validAttributes);
unset($warninglist['WarninglistType']);
}
if ($this->_isRest()) {
$this->set('Warninglists', $warninglists);
$this->set('_serialize', array('Warninglists'));
return $this->RestResponse->viewData(['Warninglists' => $warninglists], $this->response->type());
} else {
$this->set('warninglists', $warninglists);
$this->set('passedArgsArray', $filters);
}
}

View File

@ -55,7 +55,7 @@ class CsseCovidTrendsWidget
'date' => (empty($options['timeframe']) ? 10 : $options['timeframe']) . 'd'
);
$eventIds = $this->Event->filterEventIds($user, $params);
$eventIds = array_reverse(array_values($eventIds));
$eventIds = array_reverse($eventIds);
$data = array();
if (empty($options['type'])) {
$options['type'] = 'confirmed';

View File

@ -0,0 +1,88 @@
<?php
class EventStreamWidget
{
public $title = 'Event Stream';
public $render = 'Index';
public $width = 4;
public $height = 2;
public $params = [
'tags' => 'A list of tagnames to filter on. Comma separated list, prepend each tag with an exclamation mark to negate it.',
'orgs' => 'A list of organisation names to filter on. Comma separated list, prepend each tag with an exclamation mark to negate it.',
'published' => 'Boolean flag to filter on published events only',
'limit' => 'How many events should be listed? Defaults to 5',
'fields' => 'A list of fields that should be displayed. Valid fields: id, orgc, info, tags, threat_level, analysis, date. Default field selection ["id", "orgc", "info"]'
];
public $description = 'Monitor incoming events based on your own filters.';
public $cacheLifetime = false;
public $autoRefreshDelay = 5;
private $__default_fields = ['id', 'orgc', 'info'];
public function handler($user, $options = array())
{
$this->Event = ClassRegistry::init('Event');
$params = [
'metadata' => 1,
'limit' => 5,
'page' => 1,
'order' => 'Event.id DESC'
];
$field_options = [
'id' => [
'name' => '#',
'url' => Configure::read('MISP.baseurl') . '/events/view',
'element' => 'links',
'data_path' => 'Event.id',
'url_params_data_paths' => 'Event.id'
],
'orgc' => [
'name' => 'Org',
'data_path' => 'Orgc',
'element' => 'org'
],
'info' => [
'name' => 'Info',
'data_path' => 'Event.info',
],
'tags' => [
'name' => 'Tags',
'data_path' => 'EventTag',
'element' => 'tags',
'scope' => 'feeds'
],
'threat_level' => [
'name' => 'Threat Level',
'data_path' => 'ThreatLevel.name'
],
'analysis' => [
'name' => 'Analysis',
'data_path' => 'Event.analysis',
'element' => 'array_lookup_field',
'arrayData' => [__('Initial'), __('Ongoing'), __('Complete')]
],
'date' => [
'name' => 'Date',
'data_path' => 'Event.date'
],
];
$fields = [];
if (empty($options['fields'])) {
$options['fields'] = $this->__default_fields;
}
foreach ($options['fields'] as $field) {
if (!empty($field_options[$field])) {
$fields[] = $field_options[$field];
}
}
foreach (['published', 'limit', 'tags', 'orgs'] as $field) {
if (!empty($options[$field])) {
$params[$field] = $options[$field];
}
}
$data = $this->Event->fetchEvent($user, $params);
return [
'data' => $data,
'fields' => $fields
];
}
}

View File

@ -7,41 +7,43 @@ class MispSystemResourceWidget
public $width = 3;
public $height = 3;
public $params = array(
'treshold' => 'Treshold for disk space'
'threshold' => 'Threshold for disk space'
);
public $description = 'Basic widget showing some system server statistics.';
public $cacheLifetime = false;
public $autoRefreshDelay = 30;
public $placeholder =
'{
"treshold": "85"
"threshold": "85"
}';
public function handler($user, $options = array())
public function handler(array $user, $options = array())
{
// Keep BC with typo value
$threshold = isset($options['threshold']) ? $options['threshold'] : (isset($options['treshold']) ? $options['treshold'] : 85);
$drive = round((1 - disk_free_space(getcwd())/disk_total_space(getcwd()))*100,2);
$cwd = getcwd();
$drive = round((1 - disk_free_space($cwd)/disk_total_space($cwd))*100,2);
$driveFree = $drive . "%";
$driveFreeClass = "";
if ($drive > intval($options['treshold'])) {
$driveFree = $drive . "% - [Above Treshhold]";
if ($drive > intval($threshold)) {
$driveFree = $drive . "% - [Above Threshold]";
$driveFreeClass = "red";
}
$sysload = sys_getloadavg();
preg_match('#MemFree:[\s\t]+([\d]+)\s+kB#', file_get_contents('/proc/meminfo'), $matches);
$meminfo = file_get_contents('/proc/meminfo');
preg_match('#MemFree:[\s\t]+([\d]+)\s+kB#', $meminfo, $matches);
$memoryFree = $matches[1];
preg_match('#MemTotal:[\s\t]+([\d]+)\s+kB#', file_get_contents('/proc/meminfo'), $matches);
preg_match('#MemTotal:[\s\t]+([\d]+)\s+kB#', $meminfo, $matches);
$memoryTotal = $matches[1];
$data = array(
array( 'title' => __('User'), 'value' => $user['email']),
array( 'title' => __('System'), 'value' => php_uname()),
array( 'title' => __('Disk usage'), 'value' => h($driveFree), 'class' => $driveFreeClass),
array( 'title' => __('Load'), 'value' => h($sysload[0] . " - " . $sysload[1] . " - " . $sysload[2])),
array( 'title' => __('Memory'), 'value' => h(round($memoryFree/1024,2) . "M free (" . round((1 - $memoryFree/$memoryTotal)*100,2) . "% used)")),
);
array( 'title' => __('User'), 'value' => $user['email']),
array( 'title' => __('System'), 'value' => php_uname()),
array( 'title' => __('Disk usage'), 'value' => h($driveFree), 'class' => $driveFreeClass),
array( 'title' => __('Load'), 'value' => h(implode(" - ", sys_getloadavg()))),
array( 'title' => __('Memory'), 'value' => h(round($memoryFree / 1024,2) . " MB free (" . round((1 - $memoryFree/$memoryTotal)*100,2) . " % used)")),
);
return $data;
}

View File

@ -20,36 +20,41 @@ class TrendingTagsWidget
"include": ["misp-galaxy:", "my-internal-taxonomy"]
}';
public $description = 'Widget showing the trending tags over the past x seconds, along with the possibility to include/exclude tags.';
public $cacheLifetime = 600;
public function handler($user, $options = array())
{
$this->Event = ClassRegistry::init('Event');
$params = array(
'metadata' => 1,
'timestamp' => time() - (empty($options['time_window']) ? 8640000 : $options['time_window'])
);
/** @var Event $eventModel */
$eventModel = ClassRegistry::init('Event');
$threshold = empty($options['threshold']) ? 10 : $options['threshold'];
$eventIds = $this->Event->filterEventIds($user, $params);
$params['eventid'] = $eventIds;
$events = array();
$params = [
'timestamp' => time() - (empty($options['time_window']) ? 8640000 : $options['time_window']),
];
$eventIds = $eventModel->filterEventIds($user, $params);
$tags = [];
$tagColours = [];
if (!empty($eventIds)) {
$events = $this->Event->fetchEvent($user, $params);
}
$tags = array();
$tagColours = array();
foreach ($events as $event) {
foreach ($event['EventTag'] as $et) {
if ($this->checkTag($options, $et['Tag']['name'])) {
if (empty($tags[$et['Tag']['name']])) {
$tags[$et['Tag']['name']] = 1;
$tagColours[$et['Tag']['name']] = $et['Tag']['colour'];
} else {
$tags[$et['Tag']['name']] += 1;
}
$eventTags = $eventModel->EventTag->find('all', [
'conditions' => ['EventTag.event_id' => $eventIds],
'contain' => ['Tag' => ['fields' => ['name', 'colour']]],
'recursive' => -1,
'fields' => ['id'],
]);
foreach ($eventTags as $eventTag) {
$tagName = $eventTag['Tag']['name'];
if (isset($tags[$tagName])) {
$tags[$tagName]++;
} else if ($this->checkTag($options, $tagName)) {
$tags[$tagName] = 1;
$tagColours[$tagName] = $eventTag['Tag']['colour'];
}
}
arsort($tags);
}
arsort($tags);
$data['data'] = array_slice($tags, 0, $threshold);
$data['colours'] = $tagColours;
return $data;

View File

@ -6,6 +6,7 @@ class CsvExport
public $default_fields = array('uuid', 'event_id', 'category', 'type', 'value', 'comment', 'to_ids', 'timestamp', 'object_relation', 'attribute_tag');
public $default_obj_fields = array('object_uuid', 'object_name', 'object_meta-category');
public $requested_fields = array();
public $decaying_fields = array('decay_score_score', 'decay_score_decayed');
public $non_restrictive_export = true;
public function handler($data, $options = array())
@ -22,6 +23,9 @@ class CsvExport
public function modify_params($user, $params)
{
if (!empty($params['includeDecayScore'])) {
$this->enable_decaying();
}
if (empty($params['contain'])) {
$params['contain'] = array();
}
@ -36,6 +40,11 @@ class CsvExport
return $params;
}
public function enable_decaying()
{
$this->default_fields = array_merge($this->default_fields, $this->decaying_fields);
}
private function __attributesHandler($attribute, $options)
{
$attribute = $this->__addMetadataToAttributeAtomic($attribute);
@ -44,6 +53,17 @@ class CsvExport
$attribute['object_name'] = $attribute['Object']['name'];
$attribute['object_meta-category'] = $attribute['Object']['meta-category'];
}
if (!empty($attribute['decay_score'])) {
$all_scores = Hash::extract($attribute, 'decay_score.{n}.score');
$all_decayed = Hash::extract($attribute, 'decay_score.{n}.decayed');
$avg_score = array_sum($all_scores)/count($all_scores);
$avg_decayed = count(array_intersect([true], $all_decayed)) > 0;
$attribute['decay_score_score'] = $avg_score;
$attribute['decay_score_decayed'] = $avg_decayed;
} else {
$attribute['decay_score_score'] = 0;
$attribute['decay_score_decayed'] = false;
}
return $this->__addLine($attribute, $options);
}

145
app/Lib/Tools/CidrTool.php Normal file
View File

@ -0,0 +1,145 @@
<?php
class CidrTool
{
/** @var array */
private $ipv4 = [];
/**
* Minimum netmask for IPv4 in list. 33 because maximum netmask is 32..
* @var int
*/
private $minimumIpv4Mask = 33;
/** @var array */
private $ipv6 = [];
public function __construct(array $list)
{
$this->filterInputList($list);
}
/**
* @param string $value IPv4 or IPv6 address or range
* @return false|string
*/
public function contains($value)
{
$valueMask = null;
if (strpos($value, '/') !== false) {
list($value, $valueMask) = explode('/', $value);
}
$match = false;
if (filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
// This code converts IP address to all possible CIDRs that can contains given IP address
// and then check if given hash table contains that CIDR.
$ip = ip2long($value);
// Start from 1, because doesn't make sense to check 0.0.0.0/0 match
for ($bits = $this->minimumIpv4Mask; $bits <= 32; $bits++) {
$mask = -1 << (32 - $bits);
$needle = long2ip($ip & $mask) . "/$bits";
if (isset($this->ipv4[$needle])) {
$match = $needle;
break;
}
}
} elseif (filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
$value = unpack('n*', inet_pton($value));
foreach ($this->ipv6 as $netmask => $lv) {
foreach ($lv as $l) {
if ($this->ipv6InCidr($value, $l, $netmask)) {
$match = inet_ntop($l) . "/$netmask";
break;
}
}
}
}
if ($match && $valueMask) {
$matchMask = explode('/', $match)[1];
if ($valueMask < $matchMask) {
return false;
}
}
return $match;
}
/**
* @param string $cidr
* @return bool
*/
public static function validate($cidr)
{
$parts = explode('/', $cidr, 2);
$ipBytes = inet_pton($parts[0]);
if ($ipBytes === false) {
return false;
}
$maximumNetmask = strlen($ipBytes) === 4 ? 32 : 128;
if (isset($parts[1]) && ($parts[1] > $maximumNetmask || $parts[1] < 0)) {
return false; // Netmask part of CIDR is invalid
}
return true;
}
/**
* Using solution from https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpFoundation/IpUtils.php
*
* @param array $ip
* @param string $cidr
* @param int $netmask
* @return bool
*/
private function ipv6InCidr($ip, $cidr, $netmask)
{
$bytesAddr = unpack('n*', $cidr);
for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) {
$left = $netmask - 16 * ($i - 1);
$left = ($left <= 16) ? $left : 16;
$mask = ~(0xffff >> $left) & 0xffff;
if (($bytesAddr[$i] & $mask) != ($ip[$i] & $mask)) {
return false;
}
}
return true;
}
/**
* Filter out invalid IPv4 or IPv4 CIDR and append maximum netmask if no netmask is given.
* @param array $list
*/
private function filterInputList(array $list)
{
foreach ($list as $v) {
$parts = explode('/', $v, 2);
$ipBytes = inet_pton($parts[0]);
if ($ipBytes === false) {
continue; // IP address part of CIDR is invalid
}
$maximumNetmask = strlen($ipBytes) === 4 ? 32 : 128;
if (isset($parts[1]) && ($parts[1] > $maximumNetmask || $parts[1] < 0)) {
// Netmask part of CIDR is invalid
continue;
}
$mask = isset($parts[1]) ? $parts[1] : $maximumNetmask;
if ($maximumNetmask === 32) {
if ($mask < $this->minimumIpv4Mask) {
$this->minimumIpv4Mask = (int)$mask;
}
if (!isset($parts[1])) {
$v = "$v/$maximumNetmask"; // If CIDR doesnt contains '/', we will consider CIDR as /32
}
$this->ipv4[$v] = true;
} else {
$this->ipv6[$mask][] = $ipBytes;
}
}
}
}

View File

@ -1,15 +1,14 @@
<?php
class ClusterRelationsGraphTool
{
private $GalaxyCluster = false;
private $user = false;
private $GalaxyCluster;
private $user;
private $lookup = array();
public function construct($user, $GalaxyCluster)
public function __construct(array $user, GalaxyCluster $GalaxyCluster)
{
$this->GalaxyCluster = $GalaxyCluster;
$this->user = $user;
return true;
}
/**
@ -75,7 +74,7 @@
'conditions' => array(
'referenced_galaxy_cluster_uuid' => $cluster['GalaxyCluster']['uuid']
),
'contain' => array('Org', 'Orgc', 'SharingGroup'),
'contain' => array('SharingGroup'),
));
if (!empty($referencingRelations)) {
foreach ($referencingRelations as $relation) {

View File

@ -66,7 +66,11 @@ class ColourPaletteTool
// pass the element's id from the list along to get a colour for a single item
public function generatePaletteFromString($string, $items, $onlySpecific = false)
{
$hue = $this->__stringToNumber($string);
if (Validation::uuid($string)) {
$hue = $this->__uuidToNumber($string);
} else {
$hue = $this->__stringToNumber($string);
}
$saturation = 1;
$steps = 80 / $items;
$results = array();
@ -91,4 +95,12 @@ class ColourPaletteTool
}
return $number % 100 / 100;
}
private function __uuidToNumber($string)
{
$part = explode('-', $string)[4];
$number = hexdec($part);
$max = hexdec('ffffffffffff');
return round($number / $max, 2);
}
}

View File

@ -10,15 +10,10 @@ class ComplexTypeTool
'types' => array('link', 'url')
),
array(
'from' => '/(\[\.\]|\[dot\]|\(dot\)|\\\\\.)/',
'from' => '/(\[\.\]|\[dot\]|\(dot\))/',
'to' => '.',
'types' => array('link', 'url', 'ip-dst', 'ip-src', 'domain|ip', 'domain', 'hostname')
),
array(
'from' => '/\.+/',
'to' => '.',
'types' => array('ip-dst', 'ip-src', 'domain|ip', 'domain', 'hostname')
),
array(
'from' => '/\[hxxp:\/\/\]/',
'to' => 'http://',
@ -41,7 +36,7 @@ class ComplexTypeTool
public static function refangValue($value, $type)
{
foreach (self::$__refangRegexTable as $regex) {
if (in_array($type, $regex['types'])) {
if (in_array($type, $regex['types'], true)) {
$value = preg_replace($regex['from'], $regex['to'], $value);
}
}
@ -131,16 +126,6 @@ class ComplexTypeTool
return array('type' => 'other', 'value' => $input);
}
private function __returnOddElements($array)
{
foreach ($array as $k => $v) {
if ($k % 2 != 1) {
unset($array[$k]);
}
}
return array_values($array);
}
/**
* Parse a CSV file with the given settings
* All lines starting with # are stripped
@ -203,23 +188,21 @@ class ComplexTypeTool
public function checkFreeText($input, $settings = array())
{
$charactersToTrim = '\'",() ' . "\t\n\r\0\x0B"; // custom + default PHP trim
$charactersToTrim = '\'".,() ' . "\t\n\r\0\x0B"; // custom + default PHP trim
$input = str_replace("\xc2\xa0", ' ', $input); // non breaking space to normal space
$input = preg_replace('/\p{C}+/u', ' ', $input);
$iocArray = preg_split("/\r\n|\n|\r|\s|\s+|,|\<|\>|;/", $input);
$quotedText = explode('"', $input);
foreach ($quotedText as $k => $temp) {
$temp = trim($temp);
if (empty($temp)) {
unset($quotedText[$k]);
} else {
$quotedText[$k] = $temp;
}
}
$iocArray = array_merge($iocArray, $this->__returnOddElements($quotedText));
$resultArray = array();
preg_match_all('/\"([^\"]*)\"/', $input, $matches);
foreach ($matches[1] as $match) {
if ($match !== '') {
$iocArray[] = $match;
}
}
$resultArray = [];
foreach ($iocArray as $ioc) {
$ioc = str_replace("\xc2\xa0", '', $ioc); // remove non breaking space
$ioc = trim($ioc, $charactersToTrim);
$ioc = preg_replace('/\p{C}+/u', '', $ioc);
if (empty($ioc)) {
continue;
}
@ -258,6 +241,16 @@ class ComplexTypeTool
*/
private function __resolveType($raw_input)
{
// Check if value is clean IP without doing expensive operations.
if (filter_var($raw_input, FILTER_VALIDATE_IP)) {
return [
'types' => ['ip-dst', 'ip-src', 'ip-src/ip-dst'],
'to_ids' => true,
'default_type' => 'ip-dst',
'value' => $raw_input,
];
}
$input = array('raw' => $raw_input);
// Check hashes before refang and port extracting, it is not necessary for hashes. This speedups parsing

View File

@ -4,13 +4,14 @@
private $__lookupTables = array();
private $__related_events = array();
private $__related_attributes = array();
private $__eventModel = false;
/** @var Event */
private $__eventModel;
private $__taxonomyModel = false;
private $__galaxyClusterModel = false;
private $__user = false;
private $__json = array();
public function construct($eventModel, $taxonomyModel, $galaxyClusterModel, $user, $json)
public function construct(Event $eventModel, $taxonomyModel, $galaxyClusterModel, $user, $json)
{
$this->__eventModel = $eventModel;
$this->__taxonomyModel = $taxonomyModel;
@ -99,11 +100,11 @@
{
foreach ($objects as $k => $object) {
$include = $full;
if (!$include) {
if (!$include && isset($object['Attribute'])) {
foreach ($object['Attribute'] as $attribute) {
if (isset($this->__related_attributes[$attribute['id']])) {
$include = true;
continue;
break;
}
}
}

View File

@ -116,6 +116,7 @@ class DistributionGraphTool
'noShadowAttributes' => true,
'noEventReports' => true,
'noSightings' => true,
'excludeGalaxy' => true,
'includeEventCorrelations' => false,
'extended' => $this->__extended_view,
));

View File

@ -40,6 +40,9 @@ class ElasticSearchClient
// Format timestamp
$time = strftime("%Y-%m-%d %H:%M:%S", strtotime($document["Log"]["created"]));
$document["Log"]["created"] = $time;
if (empty($document["Log"]["ip"])) {
$document["Log"]["ip"] = null;
}
$params = array(
'index' => $index,
'type' => $document_type,

View File

@ -21,12 +21,16 @@
'fields' => array('Tag.id', 'Tag.name'),
'sort' => array('lower(Tag.name) asc'),
));
$this->__extendedEventUUIDMapping = array();
$this->__extended_view = $extended_view;
$this->__lookupTables = array(
'analysisLevels' => $this->__eventModel->analysisLevels,
'distributionLevels' => $this->__eventModel->Attribute->distributionLevels
);
$this->__authorized_JSON_key = array('event_id', 'distribution', 'category', 'type', 'value', 'comment', 'uuid', 'to_ids', 'timestamp', 'id');
App::uses('ColourPaletteTool', 'Tools');
$this->__paletteTool = new ColourPaletteTool();
return true;
}
@ -77,6 +81,11 @@
if (!($check1 && $check2)) {
unset($event['Object'][$i]);
}
foreach($obj['ObjectReference'] as $j => $rel) {
if ($rel['deleted']) {
unset($event['Object'][$i]['ObjectReference'][$j]);
}
}
}
foreach ($event['Attribute'] as $i => $attr) {
$check1 = $this->__satisfy_val_filtering($attr, false);
@ -261,8 +270,10 @@
'node_type' => 'attribute',
);
array_push($this->__json['items'], $toPush);
$this->__extendedEventUUIDMapping[$toPush['event_id']] = '';
}
$templatesCount = [];
foreach ($object as $obj) {
$toPush = array(
'id' => sprintf('o-%s', $obj['id']),
@ -283,8 +294,13 @@
$this->__json['existing_object_relation'][$attr['object_relation']] = 0; // set-alike
}
}
if (empty($templatesCount[$obj['template_uuid']])) {
$templatesCount[$obj['template_uuid']] = 0;
}
$templatesCount[$obj['template_uuid']]++;
array_push($this->__json['items'], $toPush);
$this->__extendedEventUUIDMapping[$toPush['event_id']] = '';
foreach ($obj['ObjectReference'] as $rel) {
$toPush = array(
@ -299,6 +315,12 @@
array_push($this->__json['relations'], $toPush);
}
}
$this->__json['items'] = $this->addObjectColors($this->__json['items'], $templatesCount);
if ($this->__extended_view) {
$this->fetchEventUUIDFromId();
$this->__json['extended_event_uuid_mapping'] = $this->__extendedEventUUIDMapping;
}
return $this->__json;
}
@ -519,7 +541,7 @@
public function get_reference_data($uuid)
{
$objectReference = $this->__refModel->ObjectReference->find('all', array(
'conditions' => array('ObjectReference.uuid' => $uuid),
'conditions' => array('ObjectReference.uuid' => $uuid, 'ObjectReference.deleted' => false),
'recursive' => -1,
//'fields' => array('ObjectReference.id', 'relationship_type', 'comment', 'referenced_uuid')
));
@ -542,4 +564,27 @@
}
return $templates;
}
public function fetchEventUUIDFromId()
{
$eventUUIDs = $this->__eventModel->find('list', [
'conditions' => ['id' => array_keys($this->__extendedEventUUIDMapping)],
'fields' => ['uuid']
]);
$this->__extendedEventUUIDMapping = $eventUUIDs;
}
private function addObjectColors($items, $templatesCount)
{
$colours = [];
foreach ($templatesCount as $templateUUID => $count) {
$colours[$templateUUID] = $this->__paletteTool->generatePaletteFromString($templateUUID, $count);
}
foreach ($items as $i => $item) {
if ($item['node_type'] == 'object') {
$items[$i]['color'] = array_shift($colours[$item['template_uuid']]);
}
}
return $items;
}
}

View File

@ -32,6 +32,10 @@ class HttpSocketResponseExtended extends HttpSocketResponse
{
parent::parseResponse($message);
if ($this->body === '') {
return; // skip decoding body if is empty
}
$contentEncoding = $this->getHeader('Content-Encoding');
if ($contentEncoding === 'gzip' && function_exists('gzdecode')) {
$this->body = gzdecode($this->body);
@ -107,7 +111,7 @@ class HttpSocketExtended extends HttpSocket
}
// Convert connection timeout to SocketException
if (!empty($this->lastError)) {
throw new SocketException($this->lastError['msg']);
throw new SocketException($this->lastError['str']);
}
return $response;
}

View File

@ -105,18 +105,19 @@ class JSONConverterTool
}
/**
* Event to JSON stream convertor.
* Event to JSON convertor, but that is intended for machine to machine communication
* @param array $event
* @return Generator<string>
*/
public function streamConvert(array $event)
{
$event = $this->convert($event, false, true);
// Fast and inaccurate way how to check if event is too big for to convert in one call. This can be changed in future.
$isBigEvent = (isset($event['Event']['Attribute']) ? count($event['Event']['Attribute']) : 0) +
(isset($event['Event']['Object']) ? count($event['Event']['Object']) : 0) > 100;
if (!$isBigEvent) {
yield json_encode($event, JSON_PRETTY_PRINT);
yield json_encode($event, JSON_UNESCAPED_UNICODE);
return;
}
@ -127,11 +128,11 @@ class JSONConverterTool
yield ($firstKey === $key ? '' : ',') . json_encode($key) . ":[";
$firstInnerKey = key($value);
foreach ($value as $i => $attribute) {
yield ($firstInnerKey === $i ? '' : ',') . json_encode($attribute);
yield ($firstInnerKey === $i ? '' : ',') . json_encode($attribute, JSON_UNESCAPED_UNICODE);
}
yield "]";
} else {
yield ($firstKey === $key ? '' : ',') . json_encode($key) . ":" . json_encode($value);
yield ($firstKey === $key ? '' : ',') . json_encode($key) . ":" . json_encode($value, JSON_UNESCAPED_UNICODE);
}
}
if (isset($event['errors'])) {

View File

@ -273,6 +273,7 @@ class PubSubTool
'redis_password' => '',
'redis_database' => '1',
'redis_namespace' => 'mispq',
'host' => '127.0.0.1',
'port' => '50000',
'username' => null,
'password' => null,

View File

@ -0,0 +1,522 @@
<?php
App::uses('SyncTool', 'Tools');
class SecurityAudit
{
const STRONG_PASSWORD_LENGTH = 17;
/**
* @param Server $server
* @return array
*/
public function run(Server $server)
{
$output = [];
foreach (['config.php', 'database.php', 'email.php'] as $configFile) {
$perms = fileperms(CONFIG . $configFile);
if ($perms & 0x0004) {
$output['File permissions'][] = ['error', __('%s config file is readable for any user.', $configFile)];
}
}
$redisPassword = Configure::read('MISP.redis_password');
if (empty($redisPassword)) {
$output['Redis'][] = ['error', __('Redis password not set.')];
} else if (strlen($redisPassword) < 32) { // for Redis, password should be stronger
$output['Redis'][] = [
'warning',
__('Redis password is too short, should be at least 32 chars long.'),
'https://redis.io/topics/security#authentication-feature',
];
}
$databasePassword = ConnectionManager::getDataSource('default')->config['password'];
if (empty($databasePassword)) {
$output['Database'][] = ['error', __('Database password not set.')];
} else if (strlen($databasePassword) < self::STRONG_PASSWORD_LENGTH) {
$output['Database'][] = ['warning', __('Database password is too short, should be at least %s chars long.', self::STRONG_PASSWORD_LENGTH)];
}
$passwordPolicyLength = Configure::read('Security.password_policy_length') ?: $server->serverSettings['Security']['password_policy_length']['value'];
if ($passwordPolicyLength < 8) {
$output['Password'][] = ['error', __('Minimum password length is set to %s, it is highly advised to increase it.', $passwordPolicyLength)];
} elseif ($passwordPolicyLength < 12) {
$output['Password'][] = ['warning', __('Minimum password length is set to %s, consider raising to at least 12 characters.', $passwordPolicyLength)];
}
if (empty(Configure::read('Security.require_password_confirmation'))) {
$output['Password'][] = [
'warning',
__('Password confirmation is not enabled. %s', $server->serverSettings['Security']['require_password_confirmation']['description']),
];
}
if (!empty(Configure::read('Security.auth')) && !Configure::read('Security.auth_enforced')) {
$output['Login'][] = [
'hint',
__('External authentication is enabled, but local accounts will still work. You can disable the ability to log in via local accounts by setting `Security.auth_enforced` to `true`.'),
];
}
if (empty(Configure::read('Security.disable_browser_cache'))) {
$output['Browser'][] = [
'warning',
__('Browser cache is enabled. An attacker could obtain sensitive data from the user cache. You can disable the cache by setting `Security.disable_browser_cache` to `true`.'),
];
}
if (empty(Configure::read('Security.check_sec_fetch_site_header'))) {
$output['Browser'][] = [
'warning',
__('The MISP server is not checking `Sec-Fetch` HTTP headers. This is a protection mechanism against CSRF used by modern browsers. You can enable this check by setting `Security.check_sec_fetch_site_header` to `true`.'),
];
}
if (empty(Configure::read('Security.csp_enforce'))) {
$output['Browser'][] = [
'warning',
__('Content security policies (CSP) are not enforced. Consider enabling them by setting `Security.csp_enforce` to `true`.'),
'https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP',
];
}
if (!env('HTTPS') && strpos(Configure::read('MISP.baseurl'), 'https://') === 0) {
$output['Browser'][] = [
'error',
__('MISP base URL is set to https://, but MISP thinks that the connection is insecure. This usually happens when a server is running behind a reverse proxy. By setting `Security.force_https` to `true`, session cookies will be set as Secure and CSP headers will upgrade insecure requests.'),
];
}
$sessionConfig = Configure::read('Session');
if (isset($sessionConfig['ini']['session.cookie_secure']) && !$sessionConfig['ini']['session.cookie_secure']) {
$output['Browser'][] = ['error', __('Setting session cookies as not secure is never a good idea.')];
}
if (empty(Configure::read('Security.advanced_authkeys'))) {
$output['Auth Key'][] = ['warning', __('Consider enabling Advanced Auth Keys, they provide increased security by only storing the API key hashes.')];
}
if (Configure::read('Security.allow_unsafe_apikey_named_param')) {
$output['Auth Key'][] = ['error', __('It is possible to pass API keys via the URL, meaning that the keys can be logged by proxies.')];
}
if (empty(Configure::read('Security.do_not_log_authkeys'))) {
$output['Auth Key'][] = ['warning', __('Auth Key logging is not disabled. Auth Keys in cleartext can be visible in the Audit log.')];
}
$salt = Configure::read('Security.salt');
if (empty($salt)) {
$output['Security salt'][] = ['error', __('Salt is not set.')];
} else if (strlen($salt) < 32) {
$output['Security salt'][] = ['warning', __('Salt is too short, should contain at least 32 characters.')];
} else if ($salt === "Rooraenietu8Eeyo<Qu2eeNfterd-dd+") {
$output['Security salt'][] = ['error', __('Salt is set to the default value.')];
}
if (empty(Configure::read('MISP.log_client_ip'))) {
$output['Logging'][] = ['warning', __('Logging client IP in audit log is disabled. Logging IP address can help to solve potential security breaches.')];
}
if (empty(Configure::read('MISP.log_user_ips'))) {
$output['Logging'][] = ['warning', __('Logging client IP in Redis is disabled. Logging IP addresses can help investigate potential security breaches.')];
}
if (Configure::read('MISP.log_user_ips') && Configure::read('Security.advanced_authkeys') && empty(Configure::read('MISP.log_user_ips_authkeys'))) {
$output['Logging'][] = [
'hint',
__('You can enable the logging of advanced authkeys by setting `MISP.log_user_ips_authkeys` to `true`.'),
];
}
if (empty(Configure::read('Security.username_in_response_header'))) {
$output['Logging'][] = [
'hint',
__('Passing user information to response headers is disabled. This can be useful for logging user info at the reverse proxy level. You can enable it by setting `Security.username_in_response_header` to `true`.'),
];
}
if (empty(Configure::read('MISP.attachment_scan_module'))) {
$output['Attachment scanning'][] = ['hint', __('No module for scanning attachments for viruses is currently defined.')];
}
if (Configure::read('debug')) {
$output['Debug'][] = ['error', __('Debug mode is enabled for all users.')];
}
if (Configure::read('Proxy.host')) {
$proxyPassword = Configure::read('Proxy.password');
if (empty($proxyPassword)) {
$output['Proxy'][] = ['error', __('Proxy password is empty.')];
} else if (strlen($proxyPassword) < self::STRONG_PASSWORD_LENGTH) {
$output['Proxy'][] = ['warning', __('Proxy password is too short, should be at least %s chars long.', self::STRONG_PASSWORD_LENGTH)];
}
}
if (Configure::read('Security.rest_client_enable_arbitrary_urls')) {
$output['REST client'][] = [
'hint',
__('Users can use the REST client to query any remote URL. This is generally not a good idea if your instance is public.')
];
}
if (Configure::read('Plugins.ZeroMQ_enable')) {
$zeroMqPassword = Configure::read('Plugins.ZeroMQ_password');
if (empty($zeroMqPassword)) {
$output['ZeroMQ'][] = ['error', __('ZeroMQ password is not set.')];
} else if (strlen($zeroMqPassword) < self::STRONG_PASSWORD_LENGTH) {
$output['ZeroMQ'][] = ['warning', __('ZeroMQ password is too short, should be at least %s chars long.', self::STRONG_PASSWORD_LENGTH)];
}
$redisPassword = Configure::read('Plugins.ZeroMQ_redis_password');
if (empty($redisPassword)) {
$output['ZeroMQ'][] = ['error', __('Redis password is not set.')];
} else if (strlen($redisPassword) < 32) { // for Redis, password should be stronger
$output['ZeroMQ'][] = [
'warning',
__('Redis password is too short, should be at least 32 chars long.'),
'https://redis.io/topics/security#authentication-feature',
];
}
}
$this->email($output);
/*
* These settings are dangerous and break both the transparency and potential introduce sync issues
if (!Configure::read('Security.hide_organisation_index_from_users')) {
$output['MISP'][] = [
'hint',
__('Any user can see list of all organisations. You can disable that by setting `Security.hide_organisation_index_from_users` to `true`. %s', $server->serverSettings['Security']['hide_organisation_index_from_users']['description']),
];
}
if (!Configure::read('Security.hide_organisations_in_sharing_groups')) {
$output['MISP'][] = [
'hint',
__('Any user can see list of all organisations in sharing group that user can see. You can disable that by setting `Security.hide_organisations_in_sharing_groups` to `true`. %s', $server->serverSettings['Security']['hide_organisations_in_sharing_groups']['description']),
];
}
*/
$this->feeds($output);
$this->remoteServers($output);
try {
$cakeVersion = $this->getCakeVersion();
if (version_compare($cakeVersion, '2.10.21', '<')) {
$output['Dependencies'][] = ['warning', __('CakePHP version %s is outdated.', $cakeVersion)];
}
} catch (RuntimeException $e) {}
if (version_compare(PHP_VERSION, '7.3.0', '<')) {
$output['PHP'][] = [
'warning',
__('PHP version %s is not supported anymore. It can be still supported by your distribution. ', PHP_VERSION),
'https://www.php.net/supported-versions.php'
];
} else if (version_compare(PHP_VERSION, '7.4.0', '<')) {
$output['PHP'][] = [
'hint',
__('PHP version 7.3 will not be supported after 6 Dec 2021. Even beyond that date, it can be still supported by your distribution.'),
'https://www.php.net/supported-versions.php'
];
}
if (ini_get('session.use_strict_mode') != 1) {
$output['PHP'][] = [
'warning',
__('Session strict mode is disabled.'),
'https://www.php.net/manual/en/session.configuration.php#ini.session.use-strict-mode',
];
}
if (empty(ini_get('session.cookie_httponly'))) {
$output['PHP'][] = ['error', __('Session cookie is not set as HTTP only. Session cookie can be accessed from JavaScript.')];
}
if (!in_array(strtolower(ini_get('session.cookie_samesite')), ['strict', 'lax'])) {
$output['PHP'][] = [
'error',
__('Session cookie SameSite parameter is not defined or set to None.'),
'https://developer.mozilla.org/en-us/docs/Web/HTTP/Headers/Set-Cookie/SameSite',
];
}
$sidLength = ini_get('session.sid_length');
if ($sidLength !== false && $sidLength < 32) {
$output['PHP'][] = [
'warning',
__('Session ID length is set to %s, at least 32 is recommended.', $sidLength),
'https://www.php.net/manual/en/session.configuration.php#ini.session.sid-length',
];
}
$sidBits = ini_get('session.sid_bits_per_character');
if ($sidBits !== false && $sidBits <= 4) {
$output['PHP'][] = [
'warning',
__('Session ID bit per character is set to %s, at least 5 is recommended.', $sidBits),
'https://www.php.net/manual/en/session.configuration.php#ini.session.sid-bits-per-character',
];
}
$this->system($server, $output);
return $output;
}
private function feeds(array &$output)
{
/** @var Feed $feed */
$feed = ClassRegistry::init('Feed');
$enabledFeeds = $feed->find('list', [
'conditions' => [
'input_source' => 'network',
'OR' => [
'enabled' => true,
'caching_enabled' => true,
]
],
'fields' => ['name', 'url'],
]);
foreach ($enabledFeeds as $feedName => $feedUrl) {
if (substr($feedUrl, 0, strlen('http://')) === 'http://') {
$output['Feeds'][] = ['warning', __('Feed %s uses insecure (HTTP) connection.', $feedName)];
}
}
}
private function remoteServers(array &$output)
{
/** @var Server $server */
$server = ClassRegistry::init('Server');
$enabledServers = $server->find('all', [
'conditions' => ['OR' => [
'push' => true,
'pull' => true,
'push_sightings' => true,
'caching_enabled' => true,
]],
'fields' => ['id', 'name', 'url', 'self_signed', 'cert_file', 'client_cert_file'],
]);
foreach ($enabledServers as $enabledServer) {
if (substr($enabledServer['Server']['url'], 0, strlen('http://')) === 'http://') {
$output['Remote servers'][] = ['warning', __('Server %s uses insecure (HTTP) connection.', $enabledServer['Server']['name'])];
} else if ($enabledServer['Server']['self_signed']) {
$output['Remote servers'][] = ['warning', __('Server %s uses self signed certificate. This is considered insecure.', $enabledServer['Server']['name'])];
}
try {
$parsed = SyncTool::getServerClientCertificateInfo($enabledServer);
if (isset($parsed['public_key_size_ok']) && !$parsed['public_key_size_ok']) {
$algo = $parsed['public_key_type'] . " " . $parsed['public_key_size'];
$output['Remote servers'][] = ['warning', __('Server %s uses weak client certificate (%s).', $enabledServer['Server']['name'], $algo)];
}
} catch (Exception $e) {}
try {
$parsed = SyncTool::getServerCaCertificateInfo($enabledServer);
if (isset($parsed['public_key_size_ok']) && !$parsed['public_key_size_ok']) {
$algo = $parsed['public_key_type'] . " " . $parsed['public_key_size'];
$output['Remote servers'][] = ['warning', __('Server %s uses weak CA certificate (%s).', $enabledServer['Server']['name'], $algo)];
}
} catch (Exception $e) {}
}
}
private function email(array &$output)
{
$canSignPgp = Configure::read('GnuPG.sign');
$canSignSmime = Configure::read('SMIME.enabled') &&
!empty(Configure::read('SMIME.cert_public_sign')) &&
!empty(Configure::read('SMIME.key_sign'));
if (!$canSignPgp && !$canSignSmime) {
$output['Email'][] = [
'warning',
__('Email signing (PGP or S/MIME) is not enabled.')
];
}
if ($canSignPgp) {
$gpgKeyPassword = Configure::read('GnuPG.password');
if (empty($gpgKeyPassword)) {
$output['Email'][] = ['error', __('PGP private key password is empty.')];
} else if (strlen($gpgKeyPassword) < self::STRONG_PASSWORD_LENGTH) {
$output['Email'][] = ['warning', __('PGP private key password is too short, should be at least %s chars long.', self::STRONG_PASSWORD_LENGTH)];
}
}
if (!Configure::read('GnuPG.bodyonlyencrypted')) {
$output['Email'][] = [
'hint',
__('Full email body with all event information will be sent, even without encryption.')
];
}
if ($canSignPgp && !Configure::read('GnuPG.obscure_subject')) {
$output['Email'][] = [
'hint',
__('Even for encrypted emails, the email subject will be sent unencrypted. You can change that behaviour by setting `GnuPG.obscure_subject` to `true`.'),
];
}
App::uses('CakeEmail', 'Network/Email');
$email = new CakeEmail();
$emailConfig = $email->config();
if ($emailConfig['transport'] === 'Smtp' && $emailConfig['port'] == 25 && !$emailConfig['tls']) {
$output['Email'][] = [
'warning',
__('STARTTLS is not enabled.'),
'https://en.wikipedia.org/wiki/Opportunistic_TLS',
];
}
}
private function system(Server $server, array &$output)
{
$kernelBuildTime = $this->getKernelBuild();
if ($kernelBuildTime) {
$diff = (new DateTime())->diff($kernelBuildTime);
$diffDays = $diff->format('a');
if ($diffDays > 300) {
$output['System'][] = [
'warning',
__('Kernel build time was s days ago. This usually means that the system kernel is not updated.', $diffDays),
];
}
}
// uptime
try {
$since = $this->execute(['uptime', '-s']);
$since = new DateTime($since);
$diff = (new DateTime())->diff($since);
$diffDays = $diff->format('a');
if ($diffDays > 100) {
$output['System'][] = [
'warning',
__('Uptime of this server is %s days. This usually means that the system kernel is outdated.', $diffDays),
];
}
} catch (Exception $e) {
}
// Python version
try {
$pythonVersion = $this->execute([$server->getPythonVersion(), '-V']);
$parts = explode(' ', $pythonVersion);
if ($parts[0] !== 'Python') {
throw new Exception("Invalid python version response: $pythonVersion");
}
if (version_compare($parts[1], '3.6', '<')) {
$output['System'][] = [
'warning',
__('You are using Python %s. This version is not supported anymore, but it can be still supported by your distribution.', $parts[1]),
'https://endoflife.date/python',
];
} else if (version_compare($parts[1], '3.7', '<')) {
$output['System'][] = [
'hint',
__('You are using Python %s. This version will not be supported beyond 23 Dec 2021, but it can be that it is still supported by your distribution.', $parts[1]),
'https://endoflife.date/python',
];
}
} catch (Exception $e) {
}
$ubuntuVersion = $this->getUbuntuVersion();
if ($ubuntuVersion) {
if (in_array($ubuntuVersion, ['14.04', '19.10'])) {
$output['System'][] = [
'warning',
__('You are using Ubuntu %s. This version doesn\'t receive security support anymore.', $ubuntuVersion),
'https://endoflife.date/ubuntu',
];
} else if (in_array($ubuntuVersion, ['16.04'])) {
$output['System'][] = [
'hint',
__('You are using Ubuntu %s. This version will be not supported after 02 Apr 2021.', $ubuntuVersion),
'https://endoflife.date/ubuntu',
];
}
}
}
private function getKernelBuild()
{
if (!php_uname('s') !== 'Linux') {
return false;
}
$version = php_uname('v');
if (substr($version, 0, 7) !== '#1 SMP ') {
return false;
}
$buildTime = substr($version, 7);
return new DateTime($buildTime);
}
private function getUbuntuVersion()
{
if (!php_uname('s') !== 'Linux') {
return false;
}
if (!is_readable('/etc/os-release')) {
return false;
}
$content = file_get_contents('/etc/os-release');
if ($content === false) {
return false;
}
$parsed = parse_ini_string($content);
if ($parsed === false) {
return false;
}
if (!isset($parsed['NAME'])) {
return false;
}
if ($parsed['NAME'] !== 'Ubuntu') {
return false;
}
if (!isset($parsed['VERSION_ID'])) {
return false;
}
return $parsed['VERSION_ID'];
}
/**
* @return string
*/
private function getCakeVersion()
{
$filePath = APP . 'Lib/cakephp/lib/Cake/VERSION.txt';
$version = file_get_contents($filePath);
if (!$version) {
throw new RuntimeException("Could not open CakePHP version file '$filePath'.");
}
foreach (explode("\n", $version) as $line) {
if ($line[0] === '/') {
continue;
}
return trim($line);
}
throw new RuntimeException("CakePHP version not found in file '$filePath'.");
}
private function execute(array $command)
{
$descriptorspec = [
1 => ["pipe", "w"], // stdout
2 => ["pipe", "w"], // stderr
];
$command = implode(' ', $command);
$process = proc_open($command, $descriptorspec, $pipes);
if (!$process) {
throw new Exception("Command '$command' could not be started.");
}
$stdout = stream_get_contents($pipes[1]);
if ($stdout === false) {
throw new Exception("Could not get STDOUT of command.");
}
fclose($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[2]);
$returnCode = proc_close($process);
if ($returnCode !== 0) {
throw new Exception("Command '$command' return error code $returnCode. STDERR: '$stderr', STDOUT: '$stdout'");
}
return $stdout;
}
}

View File

@ -3,6 +3,36 @@ App::uses('CakeEmail', 'Network/Email');
class SendEmailException extends Exception {}
class CakeEmailBody
{
/** @var string|null */
public $html;
/** @var string|null */
public $text;
public function __construct($text = null, $html = null)
{
$this->html = $html;
$this->text = $text;
}
/**
* @return string
*/
public function format()
{
if (!empty($this->html) && !empty($this->text)) {
return 'both';
}
if (!empty($this->html)) {
return 'html';
}
return 'text';
}
}
/**
* Class CakeEmailExtended
*
@ -14,7 +44,7 @@ class SendEmailException extends Exception {}
class CakeEmailExtended extends CakeEmail
{
/**
* @var MimeMultipart|MessagePart
* @var MimeMultipart|MessagePart|CakeEmailBody
*/
private $body;
@ -30,7 +60,7 @@ class CakeEmailExtended extends CakeEmail
$headers['Content-Type'] = $this->body->getContentType();
} else if ($this->body instanceof MessagePart) {
$headers = array_merge($headers, $this->body->getHeaders());
} else {
} else if ($this->_emailFormat !== 'both') { // generate correct content-type header for 'text' or 'html' format
$headers['Content-Type'] = 'multipart/mixed; boundary="' . $this->boundary() . '"';
}
@ -71,18 +101,40 @@ class CakeEmailExtended extends CakeEmail
return $this->body->render();
} else if ($this->body instanceof MessagePart) {
return $this->body->render(false);
} else if ($this->body instanceof CakeEmailBody) {
return $this->_render([]); // @see _renderTemplates
}
return $this->_render($this->_wrap($this->body));
throw new InvalidArgumentException("Expected that body is instance of MimeMultipart, MessagePart or CakeEmailBody, " . gettype($this->body) . " given.");
}
// This is hack how to force CakeEmail to always generate multipart message.
protected function _renderTemplates($content)
{
if (!$this->body instanceof CakeEmailBody) {
throw new InvalidArgumentException("Expected instance of CakeEmailBody, " . gettype($this->body) . " given.");
}
$this->_boundary = md5(uniqid());
$output = parent::_renderTemplates($content);
$output[''] = '';
return $output;
$rendered = [];
if (!empty($this->body->text)) {
$rendered['text'] = $this->body->text;
}
if (!empty($this->body->html)) {
$rendered['html'] = $this->body->html;
}
foreach ($rendered as $type => $content) {
$content = str_replace(array("\r\n", "\r"), "\n", $content);
$content = $this->_encodeString($content, $this->charset);
$content = $this->_wrap($content);
$content = implode("\n", $content);
$rendered[$type] = rtrim($content, "\n");
}
// This is hack how to force CakeEmail to always generate multipart message.
$rendered[''] = '';
return $rendered;
}
protected function _render($content)
@ -101,7 +153,7 @@ class CakeEmailExtended extends CakeEmail
if ($content !== null) {
throw new InvalidArgumentException("Content must be null for CakeEmailExtended.");
}
return parent::send($this->body);
return parent::send();
}
public function __toString()
@ -252,11 +304,12 @@ class MessagePart
class SendEmail
{
/**
* @var CryptGpgExtended
*/
/** @var CryptGpgExtended */
private $gpg;
/** @var string|null */
private $transport;
/**
* @param CryptGpgExtended|null $gpg
*/
@ -271,6 +324,14 @@ class SendEmail
}
}
/**
* @param string $transport
*/
public function setTransport($transport)
{
$this->transport = $transport;
}
/**
* @param array $params
* @return array|bool
@ -285,7 +346,8 @@ class SendEmail
}
}
$params['body'] = str_replace('\n', PHP_EOL, $params['body']); // TODO: Why this?
$body = str_replace('\n', PHP_EOL, $params['body']); // TODO: Why this?
$body = new CakeEmailBody($body);
$attachments = array();
if (!empty($params['requestor_gpgkey'])) {
@ -306,10 +368,14 @@ class SendEmail
$email->returnPath(Configure::read('MISP.email'));
$email->to($params['to']);
$email->subject($params['subject']);
$email->emailFormat('text');
$email->body($params['body']);
$email->emailFormat($body->format());
$email->body($body);
$email->attachments($attachments);
if ($this->transport) {
$email->transport($this->transport);
}
$mock = false;
if (!empty(Configure::read('MISP.disable_emailing')) || !empty($params['mock'])) {
$email->transport('Debug');
@ -352,16 +418,20 @@ class SendEmail
/**
* @param array $user
* @param string $subject
* @param string $body
* @param string|null $bodyWithoutEncryption
* @param SendEmailTemplate|string $body
* @param string|false $bodyWithoutEncryption
* @param array $replyToUser
* @return bool True if e-mail is encrypted, false if not.
* @return array
* @throws Crypt_GPG_BadPassphraseException
* @throws Crypt_GPG_Exception
* @throws SendEmailException
*/
public function sendToUser(array $user, $subject, $body, $bodyWithoutEncryption = null, array $replyToUser = array())
public function sendToUser(array $user, $subject, $body, $bodyWithoutEncryption = false, array $replyToUser = array())
{
if ($body instanceof SendEmailTemplate && $bodyWithoutEncryption !== false) {
throw new InvalidArgumentException("When body is instance of SendEmailTemplate, \$bodyWithoutEncryption must be false.");
}
if (Configure::read('MISP.disable_emailing')) {
throw new SendEmailException('Emailing is currently disabled on this instance.');
}
@ -378,14 +448,36 @@ class SendEmail
throw new SendEmailException('Encrypted messages are enforced and the message could not be encrypted for this user as no valid encryption key was found.');
}
// If 'bodyonlyencrypted' is enabled and the user has no encryption key, use the alternate body (if it exists)
if (Configure::read('GnuPG.bodyonlyencrypted') && !$canEncryptSmime && !$canEncryptGpg && $bodyWithoutEncryption) {
$body = $bodyWithoutEncryption;
// If 'GnuPG.bodyonlyencrypted' is enabled and the user has no encryption key, use the alternate body
$hideDetails = Configure::read('GnuPG.bodyonlyencrypted') && !$canEncryptSmime && !$canEncryptGpg;
if ($body instanceof SendEmailTemplate) {
$body->set('canEncryptSmime', $canEncryptSmime);
$body->set('canEncryptGpg', $canEncryptGpg);
$bodyContent = $body->render($hideDetails);
$subject = $body->subject() ?: $subject; // Get generated subject from template
} else {
if ($hideDetails && $bodyWithoutEncryption) {
$body = $bodyWithoutEncryption;
}
$bodyContent = new CakeEmailBody($body);
}
$body = str_replace('\n', PHP_EOL, $body); // TODO: Why this?
$email = $this->create($user, $subject, $bodyContent, [], $replyToUser);
$email = $this->create($user, $subject, $body, array(), $replyToUser);
if ($this->transport) {
$email->transport($this->transport);
}
// Generate `In-Reply-To` and `References` headers to group emails
if ($body instanceof SendEmailTemplate && $body->referenceId()) {
$reference = sha1($body->referenceId() . '|' . Configure::read('MISP.uuid'));
$reference = "<$reference@{$email->domain()}>";
$email->addHeaders([
'In-Reply-To' => $reference,
'References' => $reference,
]);
}
$signed = false;
if (Configure::read('GnuPG.sign')) {
@ -454,8 +546,11 @@ class SendEmail
}
try {
$email->send();
return $encrypted;
return [
'contents' => $email->send(),
'encrypted' => $encrypted,
'subject' => $subject,
];
} catch (Exception $e) {
throw new SendEmailException('The message could not be sent.', 0, $e);
}
@ -499,15 +594,23 @@ class SendEmail
/**
* @param array $user User model
* @param string $subject
* @param string $body
* @param CakeEmailBody $body
* @param array $attachments
* @param array $replyToUser User model
* @return CakeEmailExtended
*/
private function create(array $user, $subject, $body, array $attachments = array(), array $replyToUser = array())
private function create(array $user, $subject, CakeEmailBody $body, array $attachments = array(), array $replyToUser = array())
{
$email = new CakeEmailExtended();
$fromEmail = Configure::read('MISP.email');
// Set correct domain when sending email from CLI
$fromEmailParts = explode('@', $fromEmail, 2);
if (isset($fromEmailParts[1])) {
$email->domain($fromEmailParts[1]);
}
// We must generate message ID by own, because CakeEmail returns different message ID for every call of
// getHeaders() method.
$email->messageId($this->generateMessageId($email));
@ -530,11 +633,11 @@ class SendEmail
$email->replyTo(Configure::read('MISP.email_reply_to'));
}
$email->from(Configure::read('MISP.email'));
$email->returnPath(Configure::read('MISP.email')); // TODO?
$email->from($fromEmail, Configure::read('MISP.email_from_name'));
$email->returnPath($fromEmail); // TODO?
$email->to($user['User']['email']);
$email->subject($subject);
$email->emailFormat('text');
$email->emailFormat($body->format());
$email->body($body);
foreach ($attachments as $key => $value) {
@ -558,14 +661,15 @@ class SendEmail
$messagePart = new MessagePart();
$messagePart->addHeader('Content-Type', array(
'multipart/mixed',
$email->emailFormat() === 'both' ? 'multipart/alternative' : 'multipart/mixed',
'boundary="' . $email->boundary() . '"',
'protected-headers="v1"',
));
// Protect User-Facing Headers according to https://tools.ietf.org/id/draft-autocrypt-lamps-protected-headers-01.html
// Protect User-Facing Headers and Structural Headers according to
// https://tools.ietf.org/id/draft-autocrypt-lamps-protected-headers-02.html
$originalHeaders = $email->getHeaders(array('subject', 'from', 'to'));
$protectedHeaders = array('From', 'To', 'Date', 'Message-ID', 'Subject', 'Reply-To');
$protectedHeaders = ['From', 'To', 'Date', 'Message-ID', 'Subject', 'Reply-To', 'In-Reply-To', 'References'];
foreach ($protectedHeaders as $header) {
if (isset($originalHeaders[$header])) {
$messagePart->addHeader($header, $originalHeaders[$header]);
@ -654,7 +758,7 @@ class SendEmail
$messagePart = new MessagePart();
$messagePart->addHeader('Content-Type', array(
'multipart/mixed',
$email->emailFormat() === 'both' ? 'multipart/alternative' : 'multipart/mixed',
'boundary="' . $email->boundary() . '"',
));
$messagePart->setPayload($renderedEmail);
@ -729,14 +833,14 @@ class SendEmail
}
list($inputFile, $outputFile) = $this->createInputOutputFiles($body);
$result = openssl_pkcs7_sign($inputFile->pwd(), $outputFile->pwd(), $certPublicSign, $keySign, array(), 0);
$result = openssl_pkcs7_sign($inputFile->pwd(), $outputFile->pwd(), $certPublicSign, $keySign, array(), PKCS7_DETACHED);
$inputFile->delete();
if ($result) {
$data = $outputFile->read();
$outputFile->delete();
$parts = explode("\n\n", $data);
return $parts[1] . "\n";
return $parts[4] . "\n";
} else {
$outputFile->delete();

View File

@ -0,0 +1,88 @@
<?php
class SendEmailTemplate
{
/** @var array */
private $viewVars = [];
/** @var string */
private $viewName;
/** @var string|null */
private $referenceId;
/** @var string|null */
private $subject;
public function __construct($viewName)
{
$this->viewName = $viewName;
}
/**
* This value will be used for grouping emails in mail client.
* @param string|null $referenceId
* @return string
*/
public function referenceId($referenceId = null)
{
if ($referenceId === null) {
return $this->referenceId;
}
$this->referenceId = $referenceId;
}
/**
* Get subject from template. Must be called after render method.
* @param string|null $subject
* @return string
*/
public function subject($subject = null)
{
if ($subject === null) {
return $this->subject;
}
$this->subject = $subject;
}
/**
* Set template variable.
* @param string $key
* @param mixed $value
*/
public function set($key, $value)
{
$this->viewVars[$key] = $value;
}
/**
* @param bool $hideDetails True when GnuPG.bodyonlyencrypted is enabled and e-mail cannot be send in encrypted form
* @return CakeEmailBody
* @throws CakeException
*/
public function render($hideDetails = false)
{
$View = new View();
$View->autoLayout = false;
$View->helpers = ['TextColour'];
$View->loadHelpers();
$View->set($this->viewVars);
$View->set('hideDetails', $hideDetails);
$View->viewPath = $View->layoutPath = 'Emails' . DS . 'html';
try {
$html = $View->render($this->viewName);
} catch (MissingViewException $e) {
$html = null; // HTMl template is optional
}
$View->viewPath = $View->layoutPath = 'Emails' . DS . 'text';
$View->hasRendered = false;
$text = $View->render($this->viewName);
// Template can change default subject.
$this->subject = $View->get('subject');
return new CakeEmailBody($text, $html);
}
}

View File

@ -92,6 +92,35 @@ class SyncTool
return self::getClientCertificateInfo($certificateContent);
}
/**
* @param array $server
* @return array|void
* @throws Exception
*/
public static function getServerCaCertificateInfo(array $server)
{
if (!$server['Server']['cert_file']) {
return;
}
$caCertificate = new File(APP . "files" . DS . "certs" . DS . $server['Server']['id'] . '.pem');
if (!$caCertificate->exists()) {
throw new Exception("Certificate file '{$caCertificate->pwd()}' doesn't exists.");
}
$certificateContent = $caCertificate->read();
if ($certificateContent === false) {
throw new Exception("Could not read '{$caCertificate->pwd()}' file with certificate.");
}
$certificate = openssl_x509_read($certificateContent);
if (!$certificate) {
throw new Exception("Couldn't read certificate: " . openssl_error_string());
}
return self::parseCertificate($certificate);
}
/**
* @param string $certificateContent PEM encoded certificate and private key.
* @return array
@ -101,11 +130,11 @@ class SyncTool
{
$certificate = openssl_x509_read($certificateContent);
if (!$certificate) {
throw new Exception("Could't parse certificate: " . openssl_error_string());
throw new Exception("Couldn't read certificate: " . openssl_error_string());
}
$privateKey = openssl_pkey_get_private($certificateContent);
if (!$privateKey) {
throw new Exception("Could't get private key from certificate: " . openssl_error_string());
throw new Exception("Couldn't get private key from certificate: " . openssl_error_string());
}
$verify = openssl_x509_check_private_key($certificate, $privateKey);
if (!$verify) {
@ -123,7 +152,7 @@ class SyncTool
{
$parsed = openssl_x509_parse($certificate);
if (!$parsed) {
throw new Exception("Could't get parse X.509 certificate: " . openssl_error_string());
throw new Exception("Couldn't get parse X.509 certificate: " . openssl_error_string());
}
$currentTime = new DateTime();
$output = [

View File

@ -14,7 +14,7 @@ class TmpFileTool
public function __construct($maxInMemory = null)
{
if ($maxInMemory === null) {
$maxInMemory = 2 * 1024 * 1024;
$maxInMemory = 5 * 1024 * 1024;
}
$this->tmpfile = fopen("php://temp/maxmemory:$maxInMemory", "w+");
if ($this->tmpfile === false) {

View File

@ -198,13 +198,18 @@ class XMLConverterTool
$field = str_replace($this->__toEscape, $this->__escapeWith, $field);
}
/**
* @param string $input
* @param false $mispVersion
* @return Generator
*/
public function frameCollection($input, $mispVersion = false)
{
$result = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL . '<response>' . PHP_EOL;
$result .= $input;
yield '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL . '<response>' . PHP_EOL;
yield $input . PHP_EOL;
if ($mispVersion) {
$result .= '<xml_version>' . $mispVersion . '</xml_version>';
yield '<xml_version>' . $mispVersion . '</xml_version>';
}
return $result . '</response>' . PHP_EOL;
yield '</response>' . PHP_EOL;
}
}

@ -1 +1 @@
Subproject commit d0c51b37422d0d2c99be74045d4439a674259308
Subproject commit cf14e6546ec44e3369e3531add11fdb946656280

View File

@ -13,20 +13,27 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
#: View/Errors/error400.ctp:20
#: View/Errors/error500.ctp:19
#: View/Errors/missing_connection.ctp:4
#: View/Errors/error400.ctp:7
#: View/Errors/error500.ctp:4
#: View/Errors/missing_connection.ctp:5
#: View/Errors/missing_datasource_config.ctp:4
#: View/Errors/pdo_error.ctp:4
msgid "Error"
msgstr ""
#: View/Errors/error400.ctp:22
#: View/Errors/error400.ctp:11
msgid "The requested address %s was not found on this server."
msgstr ""
#: View/Errors/error500.ctp:20
#: View/Errors/missing_connection.ctp:5
#: View/Errors/error400.ctp:14
msgid "You don't have permission to access %s."
msgstr ""
#: View/Errors/error500.ctp:5
msgid "An Internal Error Has Occurred. Please try your action again. If the problem persists, please contact administrator."
msgstr ""
#: View/Errors/missing_connection.ctp:6
#: View/Errors/missing_datasource_config.ctp:5
#: View/Errors/pdo_error.ctp:5
msgid "An Internal Error Has Occurred."

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

13185
app/Locale/default.pot Executable file → Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: misp\n"
"PO-Revision-Date: 2020-04-24 02:08\n"
"PO-Revision-Date: 2021-03-26 04:21\n"
"Last-Translator: NAME <EMAIL@ADDRESS>\n"
"Language-Team: French\n"
"MIME-Version: 1.0\n"
@ -9,8 +9,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Crowdin-Project: misp\n"
"X-Crowdin-Project-ID: 306440\n"
"X-Crowdin-Language: fr\n"
"X-Crowdin-File: default.pot\n"
"X-Crowdin-File-ID: 1\n"
"Language: fr_FR\n"
#: Console/Command/AdminShell.php:72

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -43,4 +43,21 @@ class AdminSetting extends AppModel
return false;
}
}
public function updatesDone($blocking = false)
{
if ($blocking) {
$continue = false;
while ($continue == false) {
$db_version = $this->find('first', array('conditions' => array('setting' => 'db_version')));
$continue = empty($this->findUpgrades($db_version['AdminSetting']['value']));
}
return true;
} else {
$db_version = $this->find('first', array('conditions' => array('setting' => 'db_version')));
return empty($this->findUpgrades($db_version['AdminSetting']['value']));
}
}
}

View File

@ -41,7 +41,8 @@ class AppModel extends Model
public $inserted_ids = array();
private $__redisConnection = null;
/** @var null|Redis */
private static $__redisConnection = null;
private $__profiler = array();
@ -88,7 +89,7 @@ class AppModel extends Model
45 => false, 46 => false, 47 => false, 48 => false, 49 => false, 50 => false,
51 => false, 52 => false, 53 => false, 54 => false, 55 => false, 56 => false,
57 => false, 58 => false, 59 => false, 60 => false, 61 => false, 62 => false,
63 => true, 64 => false, 65 => false
63 => true, 64 => false, 65 => false, 66 => false, 67 => false,
);
public $advanced_updates_description = array(
@ -1567,6 +1568,13 @@ class AppModel extends Model
INDEX `value` (`value`(255))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
break;
case 66:
$sqlArray[] = "ALTER TABLE `galaxy_clusters` MODIFY COLUMN `tag_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '';";
$indexArray[] = ['event_reports', 'event_id'];
break;
case 67:
$sqlArray[] = "ALTER TABLE `auth_keys` ADD `allowed_ips` text DEFAULT NULL;";
break;
case 'fixNonEmptySharingGroupID':
$sqlArray[] = 'UPDATE `events` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';
$sqlArray[] = 'UPDATE `attributes` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';
@ -1820,18 +1828,19 @@ class AppModel extends Model
}
}
private function __addIndex($table, $field, $length = false)
private function __addIndex($table, $field, $length = null, $unique = false)
{
$dataSourceConfig = ConnectionManager::getDataSource('default')->config;
$dataSource = $dataSourceConfig['datasource'];
$this->Log = ClassRegistry::init('Log');
$index = $unique ? 'UNIQUE INDEX' : 'INDEX';
if ($dataSource == 'Database/Postgres') {
$addIndex = "CREATE INDEX idx_" . $table . "_" . $field . " ON " . $table . " (" . $field . ");";
$addIndex = "CREATE $index idx_" . $table . "_" . $field . " ON " . $table . " (" . $field . ");";
} else {
if (!$length) {
$addIndex = "ALTER TABLE `" . $table . "` ADD INDEX `" . $field . "` (`" . $field . "`);";
$addIndex = "ALTER TABLE `" . $table . "` ADD $index `" . $field . "` (`" . $field . "`);";
} else {
$addIndex = "ALTER TABLE `" . $table . "` ADD INDEX `" . $field . "` (`" . $field . "`(" . $length . "));";
$addIndex = "ALTER TABLE `" . $table . "` ADD $index `" . $field . "` (`" . $field . "`(" . $length . "));";
}
}
$result = true;
@ -1840,7 +1849,7 @@ class AppModel extends Model
try {
$this->query($addIndex);
} catch (Exception $e) {
$duplicate = (strpos($e->getMessage(), '1061') !== false);
$duplicate = strpos($e->getMessage(), '1061') !== false;
$errorMessage = $e->getMessage();
$result = false;
}
@ -2003,7 +2012,7 @@ class AppModel extends Model
}
}
$db_version = $db_version[0];
$updates = $this->__findUpgrades($db_version['AdminSetting']['value']);
$updates = $this->findUpgrades($db_version['AdminSetting']['value']);
if ($processId) {
$job = $this->Job->find('first', array(
'conditions' => array('Job.id' => $processId)
@ -2348,7 +2357,7 @@ class AppModel extends Model
}
}
private function __findUpgrades($db_version)
public function findUpgrades($db_version)
{
$updates = array();
if (strpos($db_version, '.')) {
@ -2477,8 +2486,8 @@ class AppModel extends Model
*/
public function setupRedisWithException()
{
if ($this->__redisConnection) {
return $this->__redisConnection;
if (self::$__redisConnection) {
return self::$__redisConnection;
}
if (!class_exists('Redis')) {
@ -2503,7 +2512,7 @@ class AppModel extends Model
throw new Exception("Could not select Redis database $database: {$redis->getLastError()}");
}
$this->__redisConnection = $redis;
self::$__redisConnection = $redis;
return $redis;
}
@ -2718,26 +2727,26 @@ class AppModel extends Model
/**
* @param array $server
* @param string $model
* @return array[]
* @throws JsonException
*/
protected function setupSyncRequest(array $server, $model = 'Server')
{
$version = implode('.', $this->checkMISPVersion());
$commit = $this->checkMIPSCommit();
$request = array(
'header' => array(
'Authorization' => $server[$model]['authkey'],
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'MISP-version' => $version,
'User-Agent' => 'MISP ' . $version . (empty($commit) ? '' : ' - #' . $commit),
)
);
$commit = $this->checkMIPSCommit();
if ($commit) {
$request['header']['commit'] = $commit;
}
$request['header']['User-Agent'] = 'MISP ' . $version . (empty($commit) ? '' : ' - #' . $commit);
return $request;
}
@ -3027,6 +3036,34 @@ class AppModel extends Model
}
}
/**
* Optimised version of CakePHP _findList method when just one or two fields are set from same model
* @param string $state
* @param array $query
* @param array $results
* @return array
*/
protected function _findList($state, $query, $results = [])
{
if ($state === 'before') {
return parent::_findList($state, $query, $results);
}
if (empty($results)) {
return [];
}
if ($query['list']['groupPath'] === null) {
$keyPath = explode('.', $query['list']['keyPath']);
$valuePath = explode('.', $query['list']['valuePath']);
if ($keyPath[1] === $valuePath[1]) { // same model
return array_column(array_column($results, $keyPath[1]), $valuePath[2], $keyPath[2]);
}
}
return parent::_findList($state, $query, $results);
}
/**
* Find method that allows to fetch just one column from database.
* @param $state

View File

@ -12,6 +12,7 @@ App::uses('ComplexTypeTool', 'Tools');
/**
* @property Event $Event
* @property AttributeTag $AttributeTag
* @property Sighting $Sighting
* @property-read array $typeDefinitions
* @property-read array $categoryDefinitions
*/
@ -62,6 +63,7 @@ class Attribute extends AppModel
public $shortDist = array(0 => 'Organisation', 1 => 'Community', 2 => 'Connected', 3 => 'All', 4 => ' Sharing Group', 5 => 'Inherit');
private $exclusions = null;
public function __construct($id = false, $table = null, $ds = null)
{
@ -202,17 +204,17 @@ class Attribute extends AppModel
'stringNotEmpty' => array(
'rule' => array('stringNotEmpty')
),
'validComposite' => array(
'rule' => array('validComposite'),
'message' => 'Composite type found but the value not in the composite (value1|value2) format.'
),
'userdefined' => array(
'rule' => array('validateAttributeValue'),
'message' => 'Value not in the right type/format. Please double check the value or select type "other".'
),
'uniqueValue' => array(
'rule' => array('valueIsUnique'),
'message' => 'A similar attribute already exists for this event.'
),
'validComposite' => array(
'rule' => array('validComposite'),
'message' => 'Composite type found but the value not in the composite (value1|value2) format.'
'rule' => array('valueIsUnique'),
'message' => 'A similar attribute already exists for this event.'
),
'maxTextLength' => array(
'rule' => array('maxTextLength')
@ -602,7 +604,7 @@ class Attribute extends AppModel
public function validComposite($fields)
{
$compositeTypes = $this->getCompositeTypes();
if (in_array($this->data['Attribute']['type'], $compositeTypes)) {
if (in_array($this->data['Attribute']['type'], $compositeTypes, true)) {
if (substr_count($fields['value'], '|') !== 1) {
return false;
}
@ -1031,6 +1033,7 @@ class Attribute extends AppModel
case 'first-name':
case 'middle-name':
case 'last-name':
case 'full-name':
$returnValue = true;
break;
case 'link':
@ -1095,6 +1098,8 @@ class Attribute extends AppModel
case 'github-repository':
case 'github-organisation':
case 'twitter-id':
case 'dkim':
case 'dkim-signature':
case 'favicon-mmh3':
case 'chrome-extension-id':
case 'mobile-application-id':
@ -1400,6 +1405,7 @@ class Attribute extends AppModel
// prepare the conditions
$conditions = array(
'Attribute.event_id !=' => $attribute['event_id'],
'Attribute.deleted !=' => 1,
);
// prevent issues with empty fields
@ -1575,7 +1581,7 @@ class Attribute extends AppModel
* @return string
* @throws Exception
*/
private function resizeImage($data, $maxWidth, $maxHeight)
public function resizeImage($data, $maxWidth, $maxHeight)
{
$image = imagecreatefromstring($data);
if ($image === false) {
@ -1764,15 +1770,12 @@ class Attribute extends AppModel
if (!empty($a['value2'])) {
$value .= '|' . $a['value2'];
}
if (empty($this->exclusions)) {
if ($this->exclusions === null) {
try {
$redis = $this->setupRedisWithException();
} catch (Exception $e) {
$redisFail = true;
}
if (empty($redisFail)) {
$this->Correlation = ClassRegistry::init('Correlation');
$this->exclusions = $redis->sMembers('misp:correlation_exclusions');
} catch (Exception $e) {
$this->exclusions = [];
}
}
foreach ($this->exclusions as $exclusion) {
@ -1809,11 +1812,11 @@ class Attribute extends AppModel
if (!empty($a['disable_correlation']) || Configure::read('MISP.completely_disable_correlation')) {
return true;
}
if ($this->__preventExcludedCorrelations($a)) {
// Don't do any correlation if the type is a non correlating type
if (in_array($a['type'], $this->nonCorrelatingTypes, true)) {
return true;
}
// Don't do any correlation if the type is a non correlating type
if (in_array($a['type'], $this->nonCorrelatingTypes)) {
if ($this->__preventExcludedCorrelations($a)) {
return true;
}
if (!$event) {
@ -2542,7 +2545,6 @@ class Attribute extends AppModel
'recursive' => -1, // int
'fields' => array('Attribute.id', 'Attribute.event_id', 'Attribute.type', 'Attribute.category', 'Attribute.comment', 'Attribute.to_ids', 'Attribute.value', 'Attribute.value' . $valueField),
'contain' => array('Event' => array('fields' => array('Event.id', 'Event.threat_level_id', 'Event.orgc_id', 'Event.uuid'))),
'group' => array('Attribute.type', 'Attribute.value' . $valueField), // fields to GROUP BY
'enforceWarninglist' => $enforceWarninglist,
'flatten' => 1
)
@ -2944,9 +2946,7 @@ class Attribute extends AppModel
if (!empty($attribute)) {
if (!empty($attribute['AttributeTag'])) {
foreach ($attribute['AttributeTag'] as $at) {
if ($at['Tag']['exportable']) {
$attribute['Attribute']['Tag'][] = $at['Tag'];
}
$attribute['Attribute']['Tag'][] = $at['Tag'];
}
}
unset($attribute['AttributeTag']);
@ -3092,7 +3092,7 @@ class Attribute extends AppModel
$params['conditions']['AND'][] = $options['conditions'];
}
if (empty($options['flatten'])) {
$params['conditions']['AND'][] = array('(Attribute.object_id + 0)' => 0);
$params['conditions']['AND'][] = array('Attribute.object_id' => 0);
}
if (isset($options['order'])) {
$params['order'] = $options['order'];
@ -3127,7 +3127,7 @@ class Attribute extends AppModel
$options['includeEventTags'] = true;
}
if (!$user['Role']['perm_sync'] || !isset($options['deleted']) || !$options['deleted']) {
$params['conditions']['AND']['(Attribute.deleted + 0)'] = 0;
$params['conditions']['AND']['Attribute.deleted'] = 0;
} else {
if ($options['deleted'] === "only") {
$options['deleted'] = 1;
@ -3979,7 +3979,7 @@ class Attribute extends AppModel
}
}
if (!empty($attribute['Sighting'])) {
$this->Sighting->captureSighting($attribute['Sighting'], $this->id, $eventId, $user);
$this->Sighting->captureSightings($attribute['Sighting'], $this->id, $eventId, $user);
}
}
if (!empty($this->validationErrors)) {
@ -4102,6 +4102,10 @@ class Attribute extends AppModel
));
return $this->validationErrors;
} else {
if (isset($attribute['Sighting']) && !empty($attribute['Sighting'])) {
$this->Sighting = ClassRegistry::init('Sighting');
$this->Sighting->captureSightings($attribute['Sighting'], $this->id, $eventId, $user);
}
if ($user['Role']['perm_tagger']) {
/*
We should uncomment the line below in the future once we have tag soft-delete
@ -4249,7 +4253,7 @@ class Attribute extends AppModel
'tags' => array('function' => 'set_filter_tags', 'pop' => true),
'uuid' => array('function' => 'set_filter_uuid'),
'deleted' => array('function' => 'set_filter_deleted'),
'timestamp' => array('function' => 'set_filter_timestamp'),
'timestamp' => array('function' => 'set_filter_timestamp', 'pop' => true),
'attribute_timestamp' => array('function' => 'set_filter_timestamp'),
'first_seen' => array('function' => 'set_filter_seen'),
'last_seen' => array('function' => 'set_filter_seen'),
@ -4629,7 +4633,7 @@ class Attribute extends AppModel
),
'Network activity' => array(
'desc' => __('Information about network traffic generated by the malware'),
'types' => array('ip-src', 'ip-dst', 'ip-dst|port', 'ip-src|port', 'port', 'hostname', 'domain', 'domain|ip', 'mac-address', 'mac-eui-64', 'email', 'email-dst', 'email-src', 'eppn', 'url', 'uri', 'user-agent', 'http-method', 'AS', 'snort', 'pattern-in-file', 'filename-pattern','stix2-pattern', 'pattern-in-traffic', 'attachment', 'comment', 'text', 'x509-fingerprint-md5', 'x509-fingerprint-sha1', 'x509-fingerprint-sha256', 'ja3-fingerprint-md5', 'jarm-fingerprint', 'hassh-md5', 'hasshserver-md5', 'other', 'hex', 'cookie', 'hostname|port', 'bro', 'zeek', 'anonymised', 'community-id', 'email-subject', 'favicon-mmh3')
'types' => array('ip-src', 'ip-dst', 'ip-dst|port', 'ip-src|port', 'port', 'hostname', 'domain', 'domain|ip', 'mac-address', 'mac-eui-64', 'email', 'email-dst', 'email-src', 'eppn', 'url', 'uri', 'user-agent', 'http-method', 'AS', 'snort', 'pattern-in-file', 'filename-pattern','stix2-pattern', 'pattern-in-traffic', 'attachment', 'comment', 'text', 'x509-fingerprint-md5', 'x509-fingerprint-sha1', 'x509-fingerprint-sha256', 'ja3-fingerprint-md5', 'jarm-fingerprint', 'hassh-md5', 'hasshserver-md5', 'other', 'hex', 'cookie', 'hostname|port', 'bro', 'zeek', 'anonymised', 'community-id', 'email-subject', 'favicon-mmh3', 'dkim', 'dkim-signature')
),
'Payload type' => array(
'desc' => __('Information about the final payload(s)'),
@ -4661,7 +4665,7 @@ class Attribute extends AppModel
),
'Person' => array(
'desc' => __('A human being - natural person'),
'types' => array('first-name', 'middle-name', 'last-name', 'date-of-birth', 'place-of-birth', 'gender', 'passport-number', 'passport-country', 'passport-expiration', 'redress-number', 'nationality', 'visa-number', 'issue-date-of-the-visa', 'primary-residence', 'country-of-residence', 'special-service-request', 'frequent-flyer-number', 'travel-details', 'payment-details', 'place-port-of-original-embarkation', 'place-port-of-clearance', 'place-port-of-onward-foreign-destination', 'passenger-name-record-locator-number', 'comment', 'text', 'other', 'phone-number', 'identity-card-number', 'anonymised', 'email', 'pgp-public-key', 'pgp-private-key')
'types' => array('first-name', 'middle-name', 'last-name', 'full-name', 'date-of-birth', 'place-of-birth', 'gender', 'passport-number', 'passport-country', 'passport-expiration', 'redress-number', 'nationality', 'visa-number', 'issue-date-of-the-visa', 'primary-residence', 'country-of-residence', 'special-service-request', 'frequent-flyer-number', 'travel-details', 'payment-details', 'place-port-of-original-embarkation', 'place-port-of-clearance', 'place-port-of-onward-foreign-destination', 'passenger-name-record-locator-number', 'comment', 'text', 'other', 'phone-number', 'identity-card-number', 'anonymised', 'email', 'pgp-public-key', 'pgp-private-key')
),
'Other' => array(
'desc' => __('Attributes that are not part of any other category or are meant to be used as a component in MISP objects in the future'),
@ -4838,9 +4842,12 @@ class Attribute extends AppModel
'github-organisation' => array('desc' => __('A github organisation'), 'default_category' => 'Social network', 'to_ids' => 0),
'jabber-id' => array('desc' => __('Jabber ID'), 'default_category' => 'Social network', 'to_ids' => 0),
'twitter-id' => array('desc' => __('Twitter ID'), 'default_category' => 'Social network', 'to_ids' => 0),
'dkim' => array('desc' => __('DKIM public key'), 'default_category' => 'Network activity', 'to_ids' => 0),
'dkim-signature'=> array('desc' => __('DKIM signature'), 'default_category' => 'Network activity', 'to_ids' => 0),
'first-name' => array('desc' => __('First name of a natural person'), 'default_category' => 'Person', 'to_ids' => 0),
'middle-name' => array('desc' => __('Middle name of a natural person'), 'default_category' => 'Person', 'to_ids' => 0),
'last-name' => array('desc' => __('Last name of a natural person'), 'default_category' => 'Person', 'to_ids' => 0),
'full-name' => array('desc' => __('Full name of a natural person'), 'default_category' => 'Person', 'to_ids' => 0),
'date-of-birth' => array('desc' => __('Date of birth of a natural person (in YYYY-MM-DD format)'), 'default_category' => 'Person', 'to_ids' => 0),
'place-of-birth' => array('desc' => __('Place of birth of a natural person'), 'default_category' => 'Person', 'to_ids' => 0),
'gender' => array('desc' => __('The gender of a natural person (Male, Female, Other, Prefer not to say)'), 'default_category' => 'Person', 'to_ids' => 0),
@ -4859,7 +4866,7 @@ class Attribute extends AppModel
//'remarks' => array('desc' => '', 'default_category' => 'Person', 'to_ids' => 0),
'travel-details' => array('desc' => __('Travel details'), 'default_category' => 'Person', 'to_ids' => 0),
'payment-details' => array('desc' => __('Payment details'), 'default_category' => 'Person', 'to_ids' => 0),
'place-port-of-original-embarkation' => array('desc' => __('The orignal port of embarkation'), 'default_category' => 'Person', 'to_ids' => 0),
'place-port-of-original-embarkation' => array('desc' => __('The original port of embarkation'), 'default_category' => 'Person', 'to_ids' => 0),
'place-port-of-clearance' => array('desc' => __('The port of clearance'), 'default_category' => 'Person', 'to_ids' => 0),
'place-port-of-onward-foreign-destination' => array('desc' => __('A Port where the passenger is transiting to'), 'default_category' => 'Person', 'to_ids' => 0),
'passenger-name-record-locator-number' => array('desc' => __('The Passenger Name Record Locator is a key under which the reservation for a trip is stored in the system. The PNR contains, among other data, the name, flight segments and address of the passenger. It is defined by a combination of five or six letters and numbers.'), 'default_category' => 'Person', 'to_ids' => 0),

View File

@ -3,6 +3,7 @@ App::uses('AppModel', 'Model');
/**
* @property Tag $Tag
* @property Attribute $Attribute
*/
class AttributeTag extends AppModel
{
@ -87,7 +88,7 @@ class AttributeTag extends AppModel
{
$this->delete($id);
}
/**
* handleAttributeTags
*
@ -117,27 +118,43 @@ class AttributeTag extends AppModel
}
}
public function handleAttributeTag($attribute_id, $event_id, $tag)
public function handleAttributeTag($attribute_id, $event_id, array $tag)
{
if (empty($tag['deleted'])) {
$this->attachTagToAttribute($attribute_id, $event_id, $tag['id']);
$local = isset($tag['local']) ? $tag['local'] : false;
$this->attachTagToAttribute($attribute_id, $event_id, $tag['id'], $local);
} else {
$this->detachTagFromAttribute($attribute_id, $event_id, $tag['id']);
}
}
public function attachTagToAttribute($attribute_id, $event_id, $tag_id)
/**
* @param int $attribute_id
* @param int $event_id
* @param int $tag_id
* @param bool $local
* @return bool
* @throws Exception
*/
public function attachTagToAttribute($attribute_id, $event_id, $tag_id, $local = false)
{
$existingAssociation = $this->find('first', array(
'recursive' => -1,
'fields' => ['id'],
'conditions' => array(
'tag_id' => $tag_id,
'attribute_id' => $attribute_id
)
));
if (empty($existingAssociation)) {
$data = [
'attribute_id' => $attribute_id,
'event_id' => $event_id,
'tag_id' => $tag_id,
'local' => $local ? 1 : 0,
];
$this->create();
if (!$this->save(array('attribute_id' => $attribute_id, 'event_id' => $event_id, 'tag_id' => $tag_id))) {
if (!$this->save($data)) {
return false;
}
}
@ -148,6 +165,7 @@ class AttributeTag extends AppModel
{
$existingAssociation = $this->find('first', array(
'recursive' => -1,
'fields' => ['id'],
'conditions' => array(
'tag_id' => $tag_id,
'event_id' => $event_id,
@ -230,9 +248,12 @@ class AttributeTag extends AppModel
}
} else {
$allowed_tag_lookup_table = array_flip($allowedTags);
$attributes = $this->Attribute->fetchAttributes($user, array('conditions' => array(
'Attribute.event_id' => $eventId
)));
$attributes = $this->Attribute->fetchAttributes($user, array(
'conditions' => array(
'Attribute.event_id' => $eventId
),
'flatten' => 1
));
$scores = array('scores' => array(), 'maxScore' => 0);
foreach ($attributes as $attribute) {
foreach ($attribute['AttributeTag'] as $tag) {

View File

@ -1,6 +1,7 @@
<?php
App::uses('AppModel', 'Model');
App::uses('RandomTool', 'Tools');
App::uses('CidrTool', 'Tools');
/**
* @property User $User
@ -26,7 +27,6 @@ class AuthKey extends AppModel
// massage the data before we send it off for validation before saving anything
public function beforeValidate($options = array())
{
//parent::beforeValidate();
if (empty($this->data['AuthKey']['id'])) {
if (empty($this->data['AuthKey']['uuid'])) {
$this->data['AuthKey']['uuid'] = CakeText::uuid();
@ -42,22 +42,66 @@ class AuthKey extends AppModel
$this->data['AuthKey']['authkey_end'] = substr($authkey, -4);
$this->data['AuthKey']['authkey_raw'] = $authkey;
$this->authkey_raw = $authkey;
}
$validity = Configure::read('Security.advanced_authkeys_validity');
if (empty($this->data['AuthKey']['expiration'])) {
$this->data['AuthKey']['expiration'] = $validity ? strtotime("+$validity days") : 0;
if (!empty($this->data['AuthKey']['allowed_ips'])) {
if (is_string($this->data['AuthKey']['allowed_ips'])) {
$this->data['AuthKey']['allowed_ips'] = trim($this->data['AuthKey']['allowed_ips']);
if (empty($this->data['AuthKey']['allowed_ips'])) {
$this->data['AuthKey']['allowed_ips'] = [];
} else {
$this->data['AuthKey']['allowed_ips'] = explode("\n", $this->data['AuthKey']['allowed_ips']);
$this->data['AuthKey']['allowed_ips'] = array_map('trim', $this->data['AuthKey']['allowed_ips']);
}
}
if (!is_array($this->data['AuthKey']['allowed_ips'])) {
$this->invalidate('allowed_ips', 'Allowed IPs must be array');
}
foreach ($this->data['AuthKey']['allowed_ips'] as $cidr) {
if (!CidrTool::validate($cidr)) {
$this->invalidate('allowed_ips', "$cidr is not valid IP range");
}
}
}
$creationTime = isset($this->data['AuthKey']['created']) ? $this->data['AuthKey']['created'] : time();
$validity = Configure::read('Security.advanced_authkeys_validity');
if (empty($this->data['AuthKey']['expiration'])) {
$this->data['AuthKey']['expiration'] = $validity ? strtotime("+$validity days", $creationTime) : 0;
} else {
$expiration = is_numeric($this->data['AuthKey']['expiration']) ?
(int)$this->data['AuthKey']['expiration'] :
strtotime($this->data['AuthKey']['expiration']);
if ($expiration === false) {
$this->invalidate('expiration', __('Expiration must be in YYYY-MM-DD format.'));
}
if ($validity && $expiration > strtotime("+$validity days", $creationTime)) {
$this->invalidate('expiration', __('Maximal key validity is %s days.', $validity));
}
$this->data['AuthKey']['expiration'] = $expiration;
}
return true;
}
public function afterFind($results, $primary = false)
{
foreach ($results as $key => $val) {
if (isset($val['AuthKey']['allowed_ips'])) {
$results[$key]['AuthKey']['allowed_ips'] = $this->jsonDecode($val['AuthKey']['allowed_ips']);
}
}
return $results;
}
public function beforeSave($options = array())
{
if (isset($this->data['AuthKey']['allowed_ips'])) {
if (empty($this->data['AuthKey']['allowed_ips'])) {
$this->data['AuthKey']['allowed_ips'] = null;
} else {
$expiration = is_numeric($this->data['AuthKey']['expiration']) ?
(int)$this->data['AuthKey']['expiration'] :
strtotime($this->data['AuthKey']['expiration']);
if ($expiration === false) {
$this->invalidate('expiration', __('Expiration must be in YYYY-MM-DD format.'));
}
if ($validity && $expiration > strtotime("+$validity days")) {
$this->invalidate('expiration', __('Maximal key validity is %s days.', $validity));
}
$this->data['AuthKey']['expiration'] = $expiration;
$this->data['AuthKey']['allowed_ips'] = json_encode($this->data['AuthKey']['allowed_ips']);
}
}
return true;
@ -71,9 +115,9 @@ class AuthKey extends AppModel
{
$start = substr($authkey, 0, 4);
$end = substr($authkey, -4);
$existing_authkeys = $this->find('all', [
$possibleAuthkeys = $this->find('all', [
'recursive' => -1,
'fields' => ['id', 'authkey', 'user_id', 'expiration'],
'fields' => ['id', 'authkey', 'user_id', 'expiration', 'allowed_ips'],
'conditions' => [
'OR' => [
'expiration >' => time(),
@ -84,12 +128,13 @@ class AuthKey extends AppModel
]
]);
$passwordHasher = $this->getHasher();
foreach ($existing_authkeys as $existing_authkey) {
if ($passwordHasher->check($authkey, $existing_authkey['AuthKey']['authkey'])) {
$user = $this->User->getAuthUser($existing_authkey['AuthKey']['user_id']);
foreach ($possibleAuthkeys as $possibleAuthkey) {
if ($passwordHasher->check($authkey, $possibleAuthkey['AuthKey']['authkey'])) {
$user = $this->User->getAuthUser($possibleAuthkey['AuthKey']['user_id']);
if ($user) {
$user['authkey_id'] = $existing_authkey['AuthKey']['id'];
$user['authkey_expiration'] = $existing_authkey['AuthKey']['expiration'];
$user['authkey_id'] = $possibleAuthkey['AuthKey']['id'];
$user['authkey_expiration'] = $possibleAuthkey['AuthKey']['expiration'];
$user['allowed_ips'] = $possibleAuthkey['AuthKey']['allowed_ips'];
}
return $user;
}
@ -97,26 +142,67 @@ class AuthKey extends AppModel
return false;
}
public function resetauthkey($id)
/**
* @param int $userId
* @param int|null $keyId
* @return false|string
* @throws Exception
*/
public function resetAuthKey($userId, $keyId = null)
{
$existing_authkeys = $this->find('all', [
'recursive' => -1,
'conditions' => [
'user_id' => $id
]
]);
foreach ($existing_authkeys as $key) {
$key['AuthKey']['expiration'] = time();
$this->save($key);
$time = time();
if ($keyId) {
$currentAuthkey = $this->find('first', [
'recursive' => -1,
'conditions' => [
'id' => $keyId,
'user_id' => $userId,
],
]);
if (empty($currentAuthkey)) {
throw new RuntimeException("Key with ID $keyId for user with ID $userId not found.");
}
$currentAuthkey['AuthKey']['expiration'] = $time;
if (!$this->save($currentAuthkey)) {
throw new RuntimeException("Key with ID $keyId could not be saved.");
}
$comment = __("Created by resetting auth key %s\n%s", $keyId, $currentAuthkey['AuthKey']['comment']);
$allowedIps = isset($currentAuthkey['AuthKey']['allowed_ips']) ? $currentAuthkey['AuthKey']['allowed_ips'] : [];
return $this->createnewkey($userId, $comment, $allowedIps);
} else {
$existingAuthkeys = $this->find('all', [
'recursive' => -1,
'conditions' => [
'OR' => [
'expiration >' => $time,
'expiration' => 0
],
'user_id' => $userId
]
]);
foreach ($existingAuthkeys as $key) {
$key['AuthKey']['expiration'] = $time;
$this->save($key);
}
return $this->createnewkey($userId);
}
return $this->createnewkey($id);
}
public function createnewkey($id)
/**
* @param int $userId
* @param string $comment
* @param array $allowedIps
* @return false|string
* @throws Exception
*/
public function createnewkey($userId, $comment = '', array $allowedIps = [])
{
$newKey = [
'authkey' => (new RandomTool())->random_str(true, 40),
'user_id' => $id
'user_id' => $userId,
'comment' => $comment,
'allowed_ips' => empty($allowedIps) ? null : $allowedIps,
];
$this->create();
if ($this->save($newKey)) {
@ -175,6 +261,18 @@ class AuthKey extends AppModel
return $output;
}
/**
* When key is modified, update `date_modified` for user that was assigned to that key, so session data
* will be realoaded.
* @see AppController::_refreshAuth
*/
public function afterSave($created, $options = array())
{
parent::afterSave($created, $options);
$userId = $this->data['AuthKey']['user_id'];
$this->User->updateAll(['date_modified' => time()], ['User.id' => $userId]);
}
/**
* When key is deleted, update after `date_modified` for user that was assigned to that key, so session data
* will be realoaded and canceled.

View File

@ -34,9 +34,8 @@ class CorrelationExclusion extends AppModel
return false;
}
$redis->del($this->key);
$exclusions = $this->find('list', [
'recursive' => -1,
'fields' => ['id', 'value']
$exclusions = $this->find('column', [
'fields' => ['value']
]);
$redis->sAddArray($this->key, $exclusions);
}
@ -85,6 +84,7 @@ class CorrelationExclusion extends AppModel
$this->Job = ClassRegistry::init('Job');
$this->Job->id = $jobId;
}
$total = count($exclusions);
foreach ($exclusions as $exclusion_chunk) {
$i = 0;
foreach ($exclusion_chunk as $exclusion) {

File diff suppressed because it is too large Load Diff

View File

@ -53,4 +53,18 @@ class EventGraph extends AppModel
}
return true;
}
public function getPictureData($eventGraph)
{
$b64 = str_replace('data:image/png;base64,', '', $eventGraph['EventGraph']['preview_img']);
$imageDecoded = base64_decode($b64);
$source = imagecreatefromstring($imageDecoded);
imagesavealpha($source, true);
ob_start();
imagepng($source, null, 9);
$imageData = ob_get_contents();
ob_end_clean();
imagedestroy($source);
return $imageData;
}
}

View File

@ -1,65 +1,143 @@
<?php
App::uses('AppModel', 'Model');
// Table `event_locks` is not used anymore
class EventLock extends AppModel
{
public $useTable = 'event_locks';
// In seconds
const DEFAULT_TTL = 900,
PREFIX = 'misp:event_lock';
public $recursive = -1;
public $actsAs = array(
'Containable',
);
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
)
);
public $validate = array(
);
public function beforeValidate($options = array())
/**
* @param array $user
* @param int $eventId
* @return bool True if insert was successful.
*/
public function insertLock(array $user, $eventId)
{
parent::beforeValidate();
return true;
return $this->insertLockToRedis($eventId, "user:{$user['id']}", [
'type' => 'user',
'timestamp' => time(),
'User' => [
'id' => $user['id'],
'org_id' => $user['org_id'],
'email' => $user['email'],
]
]);
}
public function insertLock($user, $eventId)
/**
* @param int $eventId
* @param int $jobId
* @return bool True if insert was successful.
*/
public function insertLockBackgroundJob($eventId, $jobId)
{
$date = new DateTime();
$lock = array(
'timestamp' => $date->getTimestamp(),
return $this->insertLockToRedis($eventId, "job:$jobId", [
'type' => 'job',
'timestamp' => time(),
'job_id' => $jobId,
]);
}
/**
* @param int $eventId
* @return int|null Lock ID
*/
public function insertLockApi($eventId, array $user)
{
$apiLockId = mt_rand();
if ($this->insertLockToRedis($eventId, "api:{$user['id']}:$apiLockId", [
'type' => 'api',
'user_id' => $user['id'],
'event_id' => $eventId
);
$this->deleteAll(array('user_id' => $user['id']));
$this->create();
return $this->save($lock);
'timestamp' => time(),
])) {
return $apiLockId;
}
return null;
}
public function checkLock($user, $eventId)
/**
* @param int $eventId
* @param int $apiLockId
* @return bool
*/
public function deleteApiLock($eventId, $apiLockId, array $user)
{
$this->cleanupLock($user, $eventId);
$locks = $this->find('all', array(
'recursive' => -1,
'contain' => array('User.email', 'User.org_id', 'User.id'),
'conditions' => array(
'event_id' => $eventId
)
));
return $locks;
try {
$redis = $this->setupRedisWithException();
} catch (Exception $e) {
return false;
}
$deleted = $redis->hdel(self::PREFIX . $eventId, "api:{$user['id']}:$apiLockId");
return $deleted > 0;
}
// If a lock has been active for 15 minutes, delete it
public function cleanupLock()
/**
* @param int $eventId
* @param int $jobId
* @return bool
*/
public function deleteBackgroundJobLock($eventId, $jobId)
{
$date = new DateTime();
$timestamp = $date->getTimestamp();
$timestamp -= 900;
$this->deleteAll(array('timestamp <' => $timestamp));
return true;
try {
$redis = $this->setupRedisWithException();
} catch (Exception $e) {
return false;
}
$deleted = $redis->hDel(self::PREFIX . $eventId, "job:$jobId");
return $deleted > 0;
}
/**
* @param array $user
* @param int $eventId
* @return array[]
* @throws JsonException
*/
public function checkLock(array $user, $eventId)
{
try {
$redis = $this->setupRedisWithException();
} catch (Exception $e) {
return [];
}
$keys = $redis->hGetAll(self::PREFIX . $eventId);
if (empty($keys)) {
return [];
}
$output = [];
$now = time();
foreach ($keys as $value) {
$value = $this->jsonDecode($value);
if ($value['timestamp'] + self::DEFAULT_TTL > $now) {
$output[] = $value;
}
}
return $output;
}
/**
* @param int $eventId
* @param string $lockId
* @param array $data
* @return bool
*/
private function insertLockToRedis($eventId, $lockId, array $data)
{
try {
$redis = $this->setupRedisWithException();
} catch (Exception $e) {
return false;
}
$pipeline = $redis->pipeline();
$pipeline->hSet(self::PREFIX . $eventId, $lockId, json_encode($data));
$pipeline->expire(self::PREFIX . $eventId, self::DEFAULT_TTL); // prolong TTL
return $pipeline->exec()[0] !== false;
}
}

View File

@ -103,6 +103,9 @@ class EventReport extends AppModel
$report = ['EventReport' => $report];
}
$report['EventReport']['event_id'] = $eventId;
if (!empty($report['EventReport']['id'])) {
unset($report['EventReport']['id']);
}
$report = $this->captureSG($user, $report);
$this->create();
$errors = $this->saveAndReturnErrors($report, ['fieldList' => $this->captureFields]);
@ -114,7 +117,7 @@ class EventReport extends AppModel
}
return $errors;
}
/**
* addReport Add a report
*
@ -131,7 +134,7 @@ class EventReport extends AppModel
}
return $errors;
}
/**
* editReport Edit a report
*
@ -208,7 +211,7 @@ class EventReport extends AppModel
}
return $errors;
}
/**
* restoreReport ACL-aware method to restore a report.
*
@ -231,11 +234,11 @@ class EventReport extends AppModel
{
$this->Event = ClassRegistry::init('Event');
if (isset($report['EventReport']['distribution']) && $report['EventReport']['distribution'] == 4) {
$report['EventReport'] = $this->Event->__captureSGForElement($report['EventReport'], $user);
$report['EventReport'] = $this->Event->captureSGForElement($report['EventReport'], $user);
}
return $report;
}
/**
* buildACLConditions Generate ACL conditions for viewing the report
*
@ -299,7 +302,7 @@ class EventReport extends AppModel
}
return array();
}
/**
* fetchReports ACL-aware method. Basically find with ACL
*
@ -391,7 +394,7 @@ class EventReport extends AppModel
}
return true;
}
public function reArrangeReport(array $report)
{
$rearrangeObjects = array('Event', 'SharingGroup');
@ -460,15 +463,17 @@ class EventReport extends AppModel
$objects = [];
$templateConditions = [];
foreach ($event['Object'] as $k => $object) {
foreach ($object['Attribute'] as &$objectAttribute) {
unset($objectAttribute['ShadowAttribute']);
$objectAttribute['object_uuid'] = $object['uuid'];
$attributes[$objectAttribute['uuid']] = $objectAttribute;
if (isset($object['Attribute'])) {
foreach ($object['Attribute'] as &$objectAttribute) {
unset($objectAttribute['ShadowAttribute']);
$objectAttribute['object_uuid'] = $object['uuid'];
$attributes[$objectAttribute['uuid']] = $objectAttribute;
foreach ($objectAttribute['AttributeTag'] as $at) {
$allTagNames[$at['Tag']['name']] = $at['Tag'];
foreach ($objectAttribute['AttributeTag'] as $at) {
$allTagNames[$at['Tag']['name']] = $at['Tag'];
}
$this->Event->Attribute->removeGalaxyClusterTags($objectAttribute);
}
$this->Event->Attribute->removeGalaxyClusterTags($objectAttribute);
}
$objects[$object['uuid']] = $object;
@ -548,7 +553,7 @@ class EventReport extends AppModel
}
return $errors;
}
public function applySuggestionsInText($contentWithSuggestions, array $attribute, $value)
{
$textToBeReplaced = "@[suggestion]($value)";
@ -592,7 +597,7 @@ class EventReport extends AppModel
'attribute' => $savedAttribute
];
}
/**
* transformFreeTextIntoReplacement
*
@ -704,7 +709,7 @@ class EventReport extends AppModel
'replacementResult' => $replacementResult,
];
}
/**
* extractWithReplacements Extract context information from report with special care for ATT&CK
*
@ -907,7 +912,7 @@ class EventReport extends AppModel
if ($tagId === -1) {
$tagId = $this->EventTag->Tag->captureTag(['name' => $tagName], $user);
}
$this->EventTag->attachTagToEvent($eventId, $tagId);
$this->EventTag->attachTagToEvent($eventId, ['id' => $tagId]);
}
}

View File

@ -85,25 +85,25 @@ class EventTag extends AppModel
public function handleEventTag($event_id, $tag, &$nothingToChange = false)
{
if (empty($tag['deleted'])) {
$result = $this->attachTagToEvent($event_id, $tag['id'], $nothingToChange);
$result = $this->attachTagToEvent($event_id, $tag, $nothingToChange);
} else {
$result = $this->detachTagFromEvent($event_id, $tag['id'], $nothingToChange);
}
return $result;
}
public function attachTagToEvent($event_id, $tag_id, &$nothingToChange = false)
public function attachTagToEvent($event_id, $tag, &$nothingToChange = false)
{
$existingAssociation = $this->find('first', array(
'recursive' => -1,
'conditions' => array(
'tag_id' => $tag_id,
'tag_id' => $tag['id'],
'event_id' => $event_id
)
));
if (empty($existingAssociation)) {
$this->create();
if (!$this->save(array('event_id' => $event_id, 'tag_id' => $tag_id))) {
if (!$this->save(array('event_id' => $event_id, 'tag_id' => $tag['id'], 'local' => !empty($tag['local'])))) {
return false;
}
} else {

View File

@ -32,7 +32,10 @@ class Feed extends AppModel
'rule' => array('urlOrExistingFilepath')
),
'provider' => 'valueNotEmpty',
'name' => 'valueNotEmpty',
'name' => [
'rule' => 'valueNotEmpty',
'required' => true,
],
'event_id' => array(
'rule' => array('numeric'),
'message' => 'Please enter a numeric event ID or leave this field blank.',
@ -207,7 +210,7 @@ class Feed extends AppModel
* @return array
* @throws Exception
*/
public function getManifest($feed, HttpSocket $HttpSocket = null)
public function getManifest(array $feed, HttpSocket $HttpSocket = null)
{
$events = $this->downloadManifest($feed, $HttpSocket);
$events = $this->__filterEventsIndex($events, $feed);
@ -658,13 +661,19 @@ class Feed extends AppModel
return true;
}
private function __filterEventsIndex($events, $feed)
private function __filterEventsIndex(array $events, array $feed)
{
$filterRules = $this->__prepareFilterRules($feed);
if (!$filterRules) {
$filterRules = array();
}
foreach ($events as $k => $event) {
if (isset($event['orgc']) && !isset($event['Orgc'])) { // fix key case
$event['Orgc'] = $event['orgc'];
unset($event['orgc']);
$events[$k] = $event;
}
if (isset($filterRules['orgs']['OR']) && !empty($filterRules['orgs']['OR']) && !in_array($event['Orgc']['name'], $filterRules['orgs']['OR'])) {
unset($events[$k]);
continue;
@ -767,7 +776,13 @@ class Feed extends AppModel
return $result;
}
private function __prepareEvent($event, $feed, $filterRules)
/**
* @param array $event
* @param array $feed
* @param array $filterRules
* @return array|string
*/
private function __prepareEvent($event, array $feed, $filterRules)
{
if (isset($event['response'])) {
$event = $event['response'];
@ -776,7 +791,11 @@ class Feed extends AppModel
$event = $event[0];
}
if (!isset($event['Event']['uuid'])) {
throw new Exception("Event uuid field missing.");
throw new InvalidArgumentException("Event UUID field missing.");
}
if (isset($event['Event']['orgc']) && !isset($event['Event']['Orgc'])) { // fix key case
$event['Event']['Orgc'] = $event['Event']['orgc'];
unset($event['Event']['orgc']);
}
$event['Event']['distribution'] = $feed['Feed']['distribution'];
$event['Event']['sharing_group_id'] = $feed['Feed']['sharing_group_id'];
@ -994,7 +1013,7 @@ class Feed extends AppModel
* @return bool
* @throws Exception
*/
public function saveFreetextFeedData($feed, $data, $user, $jobId = false)
public function saveFreetextFeedData(array $feed, array $data, array $user, $jobId = false)
{
$this->Event = ClassRegistry::init('Event');
@ -1037,25 +1056,14 @@ class Feed extends AppModel
}
}
if ($feed['Feed']['fixed_event']) {
$existsAttributes = $this->Event->Attribute->find('all', array(
$existsAttributesValueToId = $this->Event->Attribute->find('list', array(
'conditions' => array(
'Attribute.deleted' => 0,
'Attribute.event_id' => $event['Event']['id']
),
'recursive' => -1,
'fields' => array('id', 'value1', 'value2')
'fields' => array('value', 'id')
));
$existsAttributesValueToId = array();
foreach ($existsAttributes as $t) {
if (!empty($t['Attribute']['value2'])) {
$value = $t['Attribute']['value1'] . '|' . $t['Attribute']['value2'];
} else {
$value = $t['Attribute']['value1'];
}
// Since event values are unique, it is OK to put value into key
$existsAttributesValueToId[$value] = $t['Attribute']['id'];
}
unset($existsAttributes);
// Create event diff. After this cycle, `$data` will contains just attributes that do not exists in current
// event and in `$existsAttributesValueToId` will contains just attributes that do not exists in current feed.
@ -1095,7 +1103,6 @@ class Feed extends AppModel
return true;
}
$data = array_values($data);
$uniqueValues = array();
foreach ($data as $key => $value) {
if (isset($uniqueValues[$value['value']])) {
@ -1125,7 +1132,7 @@ class Feed extends AppModel
$this->Event->publishRouter($event['Event']['id'], null, $user);
}
if ($feed['Feed']['tag_id']) {
$this->Event->EventTag->attachTagToEvent($event['Event']['id'], $feed['Feed']['tag_id']);
$this->Event->EventTag->attachTagToEvent($event['Event']['id'], ['id' => $feed['Feed']['tag_id']]);
}
return true;
}
@ -1235,7 +1242,7 @@ class Feed extends AppModel
$md5Values = array_map('md5', array_column($values, 'value'));
$redis->del('misp:feed_cache:' . $feedId);
foreach (array_chunk($md5Values, 1000) as $k => $chunk) {
foreach (array_chunk($md5Values, 5000) as $k => $chunk) {
$pipe = $redis->multi(Redis::PIPELINE);
if (method_exists($redis, 'sAddArray')) {
$redis->sAddArray('misp:feed_cache:' . $feedId, $chunk);
@ -1247,7 +1254,7 @@ class Feed extends AppModel
}
}
$pipe->exec();
$this->jobProgress($jobId, __('Feed %s: %s/%s values cached.', $feedId, $k * 1000, count($md5Values)));
$this->jobProgress($jobId, __('Feed %s: %s/%s values cached.', $feedId, $k * 5000, count($md5Values)));
}
$redis->set('misp:feed_cache_timestamp:' . $feedId, time());
return true;
@ -1469,7 +1476,7 @@ class Feed extends AppModel
}
}
$this->create();
if (!$this->save($feed, true, array('name', 'provider', 'url', 'rules', 'source_format', 'fixed_event', 'delta_merge', 'override_ids', 'publish', 'settings', 'tag_id', 'default', 'lookup_visible'))) {
if (!$this->save($feed, true, array('name', 'provider', 'url', 'rules', 'source_format', 'fixed_event', 'delta_merge', 'override_ids', 'publish', 'settings', 'tag_id', 'default', 'lookup_visible', 'headers'))) {
$results['fails']++;
} else {
$results['successes']++;

View File

@ -377,12 +377,23 @@ class Galaxy extends AppModel
$result = $this->Tag->$connectorModel->save($toSave);
if ($result) {
if ($target_type !== 'tag_collection') {
$date = new DateTime();
if ($target_type === 'event') {
$event = $target;
} else if ($target_type === 'attribute') {
$target['Attribute']['timestamp'] = $date->getTimestamp();
$this->Tag->AttributeTag->Attribute->save($target);
if (!empty($target['Attribute']['object_id'])) {
$container_object = $this->Tag->AttributeTag->Attribute->Object->find('first', [
'recursive' => -1,
'conditions' => ['id' => $target['Attribute']['object_id']]
]);
$container_object['Object']['timestamp'] = $date->getTimestamp();
$this->Tag->AttributeTag->Attribute->Object->save($container_object);
}
}
$this->Tag->EventTag->Event->insertLock($user, $event['Event']['id']);
$event['Event']['published'] = 0;
$date = new DateTime();
$event['Event']['timestamp'] = $date->getTimestamp();
$this->Tag->EventTag->Event->save($event);
}

View File

@ -2,6 +2,10 @@
App::uses('AppModel', 'Model');
App::uses('TmpFileTool', 'Tools');
/**
* @property Tag $Tag
* @property GalaxyClusterRelation $GalaxyClusterRelation
*/
class GalaxyCluster extends AppModel
{
public $useTable = 'galaxy_clusters';
@ -24,15 +28,10 @@ class GalaxyCluster extends AppModel
'rule' => array('stringNotEmpty')
)
),
'description' => array(
'stringNotEmpty' => array(
'rule' => array('stringNotEmpty')
)
),
'uuid' => array(
'uuid' => array(
'rule' => array('custom', '/^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/'),
'message' => 'Please provide a valid UUID'
'rule' => 'uuid',
'message' => 'Please provide a valid RFC 4122 UUID'
),
'unique' => array(
'rule' => 'isUnique',
@ -101,7 +100,7 @@ class GalaxyCluster extends AppModel
if (!isset($this->data['GalaxyCluster']['description'])) {
$this->data['GalaxyCluster']['description'] = '';
}
if ($this->data['GalaxyCluster']['distribution'] != 4) {
if (isset($this->data['GalaxyCluster']['distribution']) && $this->data['GalaxyCluster']['distribution'] != 4) {
$this->data['GalaxyCluster']['sharing_group_id'] = null;
}
if (!isset($this->data['GalaxyCluster']['published'])) {
@ -415,7 +414,7 @@ class GalaxyCluster extends AppModel
}
$saveSuccess = $this->save($cluster, array('fieldList' => $fieldList));
if ($saveSuccess) {
if (!empty($cluster['GalaxyCluster']['GalaxyElement'])) {
if (isset($cluster['GalaxyCluster']['GalaxyElement'])) {
$elementsToSave = array();
foreach ($cluster['GalaxyCluster']['GalaxyElement'] as $element) { // transform cluster into Galaxy meta format
$elementsToSave[$element['key']][] = $element['value'];
@ -460,14 +459,14 @@ class GalaxyCluster extends AppModel
$job = ClassRegistry::init('Job');
$job->create();
$data = array(
'worker' => $this->Event->__getPrioWorkerIfPossible(),
'job_type' => 'publish_galaxy_clusters',
'job_input' => 'Cluster ID: ' . $clusterId,
'status' => 0,
'retries' => 0,
'org_id' => $user['org_id'],
'org' => $user['Organisation']['name'],
'message' => $message
'worker' => 'prio',
'job_type' => 'publish_galaxy_clusters',
'job_input' => 'Cluster ID: ' . $clusterId,
'status' => 0,
'retries' => 0,
'org_id' => $user['org_id'],
'org' => $user['Organisation']['name'],
'message' => $message
);
$job->save($data);
$jobId = $job->id;
@ -565,6 +564,15 @@ class GalaxyCluster extends AppModel
), array('fieldList' => array('published', 'deleted', 'version')));
}
public function touchTimestamp($id)
{
$version = (new DateTime())->getTimestamp();
return $this->save(array(
'id' => $id,
'version' => $version,
), array('fieldList' => array('version')));
}
/**
* uploadClusterToServersRouter Upload the cluster to all remote servers
*
@ -786,13 +794,15 @@ class GalaxyCluster extends AppModel
$this->GalaxyElement->captureElements($user, $cluster['GalaxyCluster']['GalaxyElement'], $savedCluster['GalaxyCluster']['id']);
}
if (!empty($cluster['GalaxyCluster']['GalaxyClusterRelation'])) {
$this->GalaxyClusterRelation->deleteAll(array('GalaxyClusterRelation.galaxy_cluster_id' => $savedCluster['GalaxyCluster']['id']));
$saveResult = $this->GalaxyClusterRelation->captureRelations($user, $savedCluster, $cluster['GalaxyCluster']['GalaxyClusterRelation'], $fromPull=$fromPull);
if ($saveResult['failed'] > 0) {
$results['errors'][] = __('Issues while capturing relations have been logged.');
}
}
if ($savedCluster['GalaxyCluster']['published']) {
$this->publishRouter($user, $savedCluster['GalaxyCluster']['id'], $passAlong=$server['Server']['id']);
$passAlong = isset($server['Server']['id']) ? $server['Server']['id'] : null;
$this->publishRouter($user, $savedCluster['GalaxyCluster']['id'], $passAlong);
}
} else {
$results['failed']++;
@ -808,7 +818,7 @@ class GalaxyCluster extends AppModel
{
$this->Event = ClassRegistry::init('Event');
if (isset($element[$model]['distribution']) && $element[$model]['distribution'] == 4) {
$element[$model] = $this->Event->__captureSGForElement($element[$model], $user);
$element[$model] = $this->Event->captureSGForElement($element[$model], $user);
}
// first we want to see how the creator organisation is encoded
// The options here are either by passing an organisation object along or simply passing a string along
@ -822,11 +832,30 @@ class GalaxyCluster extends AppModel
return $element;
}
public function attachExtendByInfo($user, $cluster)
/**
* @param array $user
* @param array $clusters
* @return void
*/
public function attachExtendByInfo(array $user, array &$clusters)
{
$extensions = $this->fetchGalaxyClusters($user, array('conditions' => array('extends_uuid' => $cluster['GalaxyCluster']['uuid'])));
$cluster['GalaxyCluster']['extended_by'] = $extensions;
return $cluster;
if (empty($clusters)) {
return;
}
$clusterUuids = array_column(array_column($clusters, 'GalaxyCluster'), 'uuid');
$extensions = $this->fetchGalaxyClusters($user, [
'conditions' => ['extends_uuid' => $clusterUuids],
]);
foreach ($clusters as &$cluster) {
$extendedBy = [];
foreach ($extensions as $extension) {
if ($cluster['GalaxyCluster']['uuid'] === $extension['GalaxyCluster']['extends_uuid']) {
$extendedBy[] = $extension;
}
}
$cluster['GalaxyCluster']['extended_by'] = $extendedBy;
}
}
public function attachExtendFromInfo($user, $cluster)
@ -870,25 +899,23 @@ class GalaxyCluster extends AppModel
}
/**
* @param string $name
* @param string|int $name Cluster name or ID
* @param array $user
* @return array|mixed
*/
public function getCluster($name, $user)
{
$isGalaxyTag = strpos($name, 'misp-galaxy:') === 0;
if (!$isGalaxyTag) {
return null;
}
if (isset($this->__clusterCache[$name])) {
return $this->__clusterCache[$name];
}
$conditions = array();
if (is_numeric($name)) {
$conditions[] = array('GalaxyCluster.id' => $name);
$conditions = array('GalaxyCluster.id' => $name);
} else {
$conditions[] = array('LOWER(GalaxyCluster.tag_name)' => strtolower($name));
$isGalaxyTag = strpos($name, 'misp-galaxy:') === 0;
if (!$isGalaxyTag) {
return null;
}
$conditions = array('GalaxyCluster.tag_name' => $name);
}
$cluster = $this->fetchGalaxyClusters($user, array(
'conditions' => $conditions,
@ -911,21 +938,33 @@ class GalaxyCluster extends AppModel
* @param bool $fetchFullCluster
* @return array
*/
public function getClusters(array $namesOrIds, array $user, $postProcess = true)
public function getClusters(array $namesOrIds, array $user, $postProcess = true, $fetchFullCluster = true)
{
$conditions = array();
if (count(array_filter($namesOrIds, 'is_numeric')) === count($namesOrIds)) { // all elements are numeric
$conditions[] = array('GalaxyCluster.id' => $namesOrIds);
$conditions = array('GalaxyCluster.id' => $namesOrIds);
} else {
$conditions[] = array('LOWER(GalaxyCluster.tag_name)' => array_map('strtolower', $namesOrIds));
$conditions = array('GalaxyCluster.tag_name' => $namesOrIds);
}
$clusters = $this->fetchGalaxyClusters($user, array(
'conditions' => $conditions,
), true);
$options = ['conditions' => $conditions];
if (!$fetchFullCluster) {
$options['contain'] = ['Galaxy', 'GalaxyElement'];
}
$clusters = $this->fetchGalaxyClusters($user, $options, $fetchFullCluster);
if (!empty($clusters) && $postProcess) {
$tagNames = array_map('strtolower', array_column(array_column($clusters, 'GalaxyCluster'), 'tag_name'));
$tagIds = $this->Tag->find('list', [
'conditions' => ['LOWER(Tag.name)' => $tagNames],
'recursive' => -1,
'fields' => array('Tag.name', 'Tag.id'),
]);
$tagIds = array_change_key_case($tagIds);
foreach ($clusters as $k => $cluster) {
$clusters[$k] = $this->postprocess($cluster);
$tagName = strtolower($cluster['GalaxyCluster']['tag_name']);
$clusters[$k] = $this->postprocess($cluster, isset($tagIds[$tagName]) ? $tagIds[$tagName] : null);
}
}
@ -958,7 +997,6 @@ class GalaxyCluster extends AppModel
return $conditions;
}
/**
* fetchGalaxyClusters Very flexible, it's basically a replacement for find, with the addition that it restricts access based on user
*
@ -979,7 +1017,7 @@ class GalaxyCluster extends AppModel
'GalaxyElement',
'GalaxyClusterRelation' => array(
'conditions' => $this->GalaxyClusterRelation->buildConditions($user, false),
'GalaxyClusterRelationTag' => array('Tag'),
'GalaxyClusterRelationTag',
'SharingGroup',
),
'Orgc',
@ -1022,6 +1060,44 @@ class GalaxyCluster extends AppModel
if (empty($clusters)) {
return $clusters;
}
if ($full) {
$clusterIds = array_column(array_column($clusters, 'GalaxyCluster'), 'id');
$targetingClusterRelations = $this->TargetingClusterRelation->fetchRelations($user, array(
'contain' => array(
'GalaxyClusterRelationTag',
'SharingGroup',
),
'conditions' => array(
'TargetingClusterRelation.referenced_galaxy_cluster_id' => $clusterIds,
)
));
$tagsToFetch = Hash::extract($clusters, "{n}.GalaxyClusterRelation.{n}.GalaxyClusterRelationTag.{n}.tag_id");
$tagsToFetch = array_merge($tagsToFetch, Hash::extract($targetingClusterRelations, "GalaxyClusterRelationTag.{n}.tag_id"));
$tags = $this->GalaxyClusterRelation->GalaxyClusterRelationTag->Tag->find('all', [
'conditions' => ['id' => array_unique($tagsToFetch)],
'recursive' => -1,
]);
$tags = array_column(array_column($tags, 'Tag'), null, 'id');
foreach ($targetingClusterRelations as $k => $targetingClusterRelation) {
if (!empty($targetingClusterRelation['GalaxyClusterRelationTag'])) {
foreach ($targetingClusterRelation['GalaxyClusterRelationTag'] as $relationTag) {
if (isset($tags[$relationTag['tag_id']])) {
$targetingClusterRelation['TargetingClusterRelation']['Tag'][] = $tags[$relationTag['tag_id']];
}
}
}
unset($targetingClusterRelation['GalaxyClusterRelationTag']);
if (!empty($targetingClusterRelation['SharingGroup']['id'])) {
$targetingClusterRelation['TargetingClusterRelation']['SharingGroup'] = $targetingClusterRelation['SharingGroup'];
}
$targetingClusterRelations[$k] = $targetingClusterRelation['TargetingClusterRelation'];
}
}
$this->Event = ClassRegistry::init('Event');
$sharingGroupData = $this->Event->__cacheSharingGroupData($user, false);
foreach ($clusters as $i => $cluster) {
@ -1033,32 +1109,22 @@ class GalaxyCluster extends AppModel
if (!empty($relation['sharing_group_id']) && isset($sharingGroupData[$relation['sharing_group_id']])) {
$clusters[$i]['GalaxyClusterRelation'][$j]['SharingGroup'] = $sharingGroupData[$relation['sharing_group_id']]['SharingGroup'];
}
foreach ($relation['GalaxyClusterRelationTag'] as $relationTag) {
if (isset($tags[$relationTag['tag_id']])) {
$clusters[$i]['GalaxyClusterRelation'][$j]['Tag'][] = $tags[$relationTag['tag_id']];
}
}
unset($clusters[$i]['GalaxyClusterRelation'][$j]['GalaxyClusterRelationTag']);
}
}
if ($full && isset($cluster['GalaxyCluster']['id'])) {
$targetingClusterRelations = $this->TargetingClusterRelation->fetchRelations($user, array(
'contain' => array(
'GalaxyClusterRelationTag' => array('Tag'),
'SharingGroup',
),
'conditions' => array(
'TargetingClusterRelation.referenced_galaxy_cluster_id' => $cluster['GalaxyCluster']['id']
)
));
foreach ($targetingClusterRelations as $k => $targetingClusterRelation) {
if (!empty($targetingClusterRelation['GalaxyClusterRelationTag'])) {
$targetingClusterRelation['TargetingClusterRelation']['Tag'] = Hash::extract($targetingClusterRelation['GalaxyClusterRelationTag'], '{n}.Tag');
if ($full) {
foreach ($targetingClusterRelations as $targetingClusterRelation) {
if ($targetingClusterRelation['referenced_galaxy_cluster_id'] == $cluster['GalaxyCluster']['id']) {
$clusters[$i]['TargetingClusterRelation'][] = $targetingClusterRelation;
}
if (!empty($targetingClusterRelation['SharingGroup']['id'])) {
$targetingClusterRelation['TargetingClusterRelation']['SharingGroup'] = $targetingClusterRelation['SharingGroup'];
}
$targetingClusterRelations[$k] = $targetingClusterRelation['TargetingClusterRelation'];
}
$clusters[$i]['TargetingClusterRelation'] = $targetingClusterRelations;
}
$clusters[$i] = $this->arrangeData($clusters[$i]);
$clusters[$i] = $this->GalaxyClusterRelation->massageRelationTag($clusters[$i]);
$clusters[$i] = $this->TargetingClusterRelation->massageRelationTag($clusters[$i]);
}
return $clusters;
}
@ -1395,17 +1461,16 @@ class GalaxyCluster extends AppModel
/**
* @param array $user
* @param array $events
* @param bool $replace
* @param bool $fetchFullCluster
* @param bool $replace Remove galaxy cluster tags
* @return array
*/
public function attachClustersToEventIndex(array $user, array $events, $replace = false, $fetchFullCluster = true)
public function attachClustersToEventIndex(array $user, array $events, $replace = false)
{
$clusterTagNames = [];
foreach ($events as $event) {
foreach ($event['EventTag'] as $eventTag) {
if ($eventTag['Tag']['is_galaxy']) {
$clusterTagNames[strtolower($eventTag['Tag']['name'])] = true;
$clusterTagNames[$eventTag['Tag']['id']] = $eventTag['Tag']['name'];
}
}
}
@ -1415,12 +1480,10 @@ class GalaxyCluster extends AppModel
}
$options = [
'conditions' => ['LOWER(GalaxyCluster.tag_name)' => array_keys($clusterTagNames)],
'conditions' => ['GalaxyCluster.tag_name' => $clusterTagNames],
'contain' => ['Galaxy', 'GalaxyElement'],
];
if (!$fetchFullCluster) {
$options['contain'] = ['Galaxy'];
}
$clusters = $this->fetchGalaxyClusters($user, $options, $fetchFullCluster);
$clusters = $this->fetchGalaxyClusters($user, $options);
$clustersByTagName = [];
foreach ($clusters as $cluster) {
@ -1435,7 +1498,6 @@ class GalaxyCluster extends AppModel
$tagName = strtolower($eventTag['Tag']['name']);
if (isset($clustersByTagName[$tagName])) {
$cluster = $this->postprocess($clustersByTagName[$tagName], $eventTag['Tag']['id']);
$cluster['GalaxyCluster']['tag_id'] = $eventTag['Tag']['id'];
$cluster['GalaxyCluster']['local'] = $eventTag['local'];
$events[$k]['GalaxyCluster'][] = $cluster['GalaxyCluster'];
if ($replace) {

View File

@ -85,7 +85,6 @@ class GalaxyClusterRelation extends AppModel
if (!$user['Role']['perm_site_admin']) {
$alias = $this->alias;
$sgids = $this->Event->cacheSgids($user, true);
$gcids = $this->SourceCluster->cacheGalaxyClusterIDs($user);
$gcOwnerIds = $this->SourceCluster->cacheGalaxyClusterOwnerIDs($user);
$conditionsRelations['AND']['OR'] = [
"${alias}.galaxy_cluster_id" => $gcOwnerIds,
@ -145,20 +144,18 @@ class GalaxyClusterRelation extends AppModel
public function getExistingRelationships()
{
$this->ObjectRelationship = ClassRegistry::init('ObjectRelationship');
$existingRelationships = $this->find('list', array(
$existingRelationships = $this->find('column', array(
'recursive' => -1,
'fields' => array('referenced_galaxy_cluster_type', 'referenced_galaxy_cluster_type'),
'group' => array('referenced_galaxy_cluster_type')
), false, false);
$existingRelationships = array_values($existingRelationships);
$objectRelationships = $this->ObjectRelationship->find('all', array(
'recursive' => -1,
'fields' => array('name')
'fields' => array('referenced_galaxy_cluster_type'),
'unique' => true,
));
$objectRelationships = Hash::extract($objectRelationships, '{n}.ObjectRelationship.name');
$relationShips = array_unique(array_merge($existingRelationships, $objectRelationships));
return $relationShips;
$this->ObjectRelationship = ClassRegistry::init('ObjectRelationship');
$objectRelationships = $this->ObjectRelationship->find('column', array(
'recursive' => -1,
'fields' => array('name'),
'unique' => true,
));
return array_unique(array_merge($existingRelationships, $objectRelationships));
}
public function deleteRelations($conditions)
@ -166,21 +163,6 @@ class GalaxyClusterRelation extends AppModel
$this->deleteAll($conditions, false, false);
}
public function massageRelationTag($cluster)
{
if (!empty($cluster['GalaxyCluster'][$this->alias])) {
foreach ($cluster['GalaxyCluster'][$this->alias] as $k => $relation) {
if (!empty($relation['GalaxyClusterRelationTag'])) {
foreach ($relation['GalaxyClusterRelationTag'] as $relationTag) {
$cluster['GalaxyCluster'][$this->alias][$k]['Tag'][] = $relationTag['Tag'];
}
}
unset($cluster['GalaxyCluster'][$this->alias][$k]['GalaxyClusterRelationTag']);
}
}
return $cluster;
}
/**
* saveRelations
*
@ -493,7 +475,7 @@ class GalaxyClusterRelation extends AppModel
$this->Event = ClassRegistry::init('Event');
if (isset($relation['GalaxyClusterRelation']['distribution']) && $relation['GalaxyClusterRelation']['distribution'] == 4) {
$relation['GalaxyClusterRelation'] = $this->Event->__captureSGForElement($relation['GalaxyClusterRelation'], $user);
$relation['GalaxyClusterRelation'] = $this->Event->captureSGForElement($relation['GalaxyClusterRelation'], $user);
}
$saveSuccess = $this->save($relation);

View File

@ -128,4 +128,14 @@ class GalaxyElement extends AppModel
}
return $elements;
}
public function getExpandedJSONFromElements($elements)
{
$keyedValue = [];
foreach ($elements as $i => $element) {
$keyedValue[$element['GalaxyElement']['key']][] = $element['GalaxyElement']['value'];
}
$expanded = Hash::expand($keyedValue);
return $expanded;
}
}

Some files were not shown because too many files have changed in this diff Show More