VaKeR CYBER ARMY
Logo of a company Server : Apache
System : Linux host44.registrar-servers.com 4.18.0-513.18.1.lve.2.el8.x86_64 #1 SMP Sat Mar 30 15:36:11 UTC 2024 x86_64
User : vapecompany ( 2719)
PHP Version : 7.4.33
Disable Function : NONE
Directory :  /proc/self/root/opt/imunify360/venv/lib/python3.11/site-packages/imav/plugins/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : //proc/self/root/opt/imunify360/venv/lib/python3.11/site-packages/imav/plugins/wordpress.py
"""
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.


This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
See the GNU General Public License for more details.


You should have received a copy of the GNU General Public License
 along with this program.  If not, see <https://www.gnu.org/licenses/>.

Copyright © 2019 Cloud Linux Software Inc.

This software is also available under ImunifyAV commercial license,
see <https://www.imunify360.com/legal/eula>
"""
import logging
import pwd
import asyncio

from defence360agent.contracts.hook_events import HookEvent
from defence360agent.contracts.config import (
    choose_value_from_config,
    SystemConfig,
)
from defence360agent.contracts.messages import MessageType
from defence360agent.contracts.config import ANTIVIRUS_MODE
from defence360agent.contracts.plugins import (
    MessageSink,
    MessageSource,
    expect,
)
from defence360agent.subsys.panels.hosting_panel import HostingPanel
from defence360agent.subsys.persistent_state import load_state, save_state
from defence360agent.utils import Scope

if not ANTIVIRUS_MODE:
    # imav.wordpress.plugin imports clcommon which require pymysql in Plesk
    # environment. Which is available only im360 environment. (venv-firewall)
    # Before moving it to venv package we need to make sure that it is not
    # increasing memory usage.
    from imav.wordpress import plugin
else:
    plugin = None
from imav.contracts.config import Wordpress
from imav.malwarelib.model import MalwareHit
from imav.model.wordpress import WPSite

logger = logging.getLogger(__name__)


class ImunifySecurityPlugin(MessageSink, MessageSource):
    SCOPE = Scope.IM360

    def __init__(self):
        self._loop = None
        self._sink = None
        self.installation_completed = load_state("ImunifySecurityPlugin").get(
            "installed"
        )
        self.last_config_value = (
            load_state("ImunifySecurityPlugin").get("enabled")
            or Wordpress.SECURITY_PLUGIN_ENABLED
        )
        self.installation_task: asyncio.Task | None = None
        self.deleting_task: asyncio.Task | None = None

    async def create_sink(self, loop):
        pass

    async def create_source(self, loop, sink):
        self._loop = loop
        self._sink = sink

    def _task_in_progress(self, task_attr_name):
        if not hasattr(self, task_attr_name):
            logger.error("Unknown task '%s'", task_attr_name)
        task = getattr(self, task_attr_name)

        return task is not None and not task.done() and not task.cancelled()

    async def process_installation(self, coro, for_new_sites=False):
        if self._task_in_progress("deleting_task"):
            if for_new_sites:
                return

            self.deleting_task.cancel()
            await self.deleting_task

        if self._task_in_progress("installation_task"):
            logger.warning("Installation is already running")
            return

        self.installation_task = asyncio.create_task(coro)

    async def process_deleting(self, coro):
        if self._task_in_progress("installation_task"):
            self.installation_task.cancel()
            await self.installation_task

        if self._task_in_progress("deleting_task"):
            logger.warning("Deleting is already running")
            return

        self.deleting_task = asyncio.create_task(coro)

    async def get_valid_users(self):
        users = await HostingPanel().get_users()
        valid_users = set()
        invalid_users = set()

        for user in users:
            default_action, _ = choose_value_from_config(
                "MALWARE_SCANNING",
                "default_action",
                username=user,
            )

            if default_action == "cleanup":
                valid_users.add(user)
            else:
                invalid_users.add(user)

        if invalid_users:
            logger.info(
                "WordPress plugin installation is currently available only"
                ' with the following config settings: {"MALWARE_SCANNING":'
                ' {"default_action": "cleanup"}.  Additional options will be'
                " supported later."
            )
            logger.info(f"Skipping installation for {invalid_users=}")
        return valid_users

    @expect(MessageType.WordpressPluginAction)
    async def manage_plugin_action(self, message):
        logger.info(
            "ImunifySecurityPlugin received message action: %s method: %s",
            message.action,
            message.method,
        )

        if message.action == "install_on_new_sites":
            if not self.installation_completed:
                # The installation is not completed yet. We cannot know reliably which sites are new.
                return

            valid_users = await self.get_valid_users()
            await self.process_installation(
                plugin.install_for_users(users=valid_users, sink=self._sink),
                for_new_sites=True,
            )
        elif message.action == "tidy_up":
            # Tidy up sites form which the WordPress plugin was deleted manually.
            await plugin.tidy_up_manually_deleted(sink=self._sink)

            if not Wordpress.SECURITY_PLUGIN_ENABLED:
                # If the feature is disabled, remove the WordPress plugin from all sites.
                await self.process_deleting(
                    plugin.remove_all_installed(sink=self._sink)
                )
                self.installation_completed = False

    @expect(MessageType.ConfigUpdate)
    async def manage_plugin_installation(self, message):
        if not isinstance(message["conf"], SystemConfig):
            return

        current_config_value = Wordpress.SECURITY_PLUGIN_ENABLED
        if current_config_value == self.last_config_value:
            return

        # Update last config value immediately to prevent multiple installations
        self.last_config_value = current_config_value

        if current_config_value and not self.installation_completed:
            valid_users = await self.get_valid_users()

            await self.process_installation(
                plugin.install_for_users(users=valid_users, sink=self._sink)
            )

            self.installation_completed = True

        elif not current_config_value and self.installation_completed:
            await self.process_deleting(
                plugin.remove_all_installed(sink=self._sink)
            )
            self.installation_completed = False

        save_state(
            "ImunifySecurityPlugin",
            {
                "installed": self.installation_completed,
                "enabled": current_config_value,
            },
        )

    @expect(HookEvent.MalwareCleanupFinished)
    async def handle_malware_cleanup_finished(self, message):
        """
        INFO    [2025-02-24 12:00:20,384] imav.plugins.wordpress: Malware cleanup finished:
        HookEvent.MalwareCleanupFinished(
            {
                'cleanup_id': 'fa4fe7e48dbf45588f53b24366cd8893',
                'started': 1740398411.786418,
                'error': None,
                'total_files': 3,
                'total_cleaned': 3,
                'status': 'ok'
            }
        )
        """
        # Skip if plugin is disabled
        if not self.last_config_value:
            return

        # Leave early if status is not ok or the started time is missing.
        if message.get("status") != "ok" or not message.get("started"):
            return

        # load all malware hits cleaned since the cleanup started
        hits = MalwareHit.cleaned_since(message["started"])

        site_paths = set()

        # Collect all site paths that need to be updated.
        for hit in hits:
            if hit.resource_type == "file":
                try:
                    user_info = pwd.getpwnam(hit.user)
                    uid = user_info.pw_uid
                    user_sites = plugin.get_sites_for_user(uid)

                    for site_path in user_sites:
                        if hit.orig_file.startswith(site_path):
                            site_paths.add((site_path, uid))
                            break

                except KeyError:
                    pass

        if not site_paths:
            logger.debug("Cleanup finished => no sites found for cleaned hits")
            return

        logger.info(
            "Cleanup finished => %s site(s) need to be updated",
            len(site_paths),
        )

        # Convert paths to WPSite objects with empty domain and update data on the sites that need to be updated.
        # We need to work with paths here because sometimes the domain is not set, see https://cloudlinux.atlassian.net/browse/DEF-32238.
        wordpress_sites = [
            WPSite(docroot=site_path, domain="", uid=uid)
            for site_path, uid in site_paths
        ]

        await plugin.update_data_on_sites(self._sink, wordpress_sites)

        logger.info("%s site(s) updated after a cleanup", len(wordpress_sites))

    @expect(HookEvent.MalwareScanningFinished)
    async def handle_malware_scan_finished(self, message):
        """
        INFO    [2025-02-24 11:57:17,968] imav.plugins.wordpress: Malware scan finished:
        HookEvent.MalwareScanningFinished(
            {
                'scan_id': 'b9bd136aff0a4d87a248c859cfe41c47',
                'scan_type': 'user',
                'path': '/home/user1'
            }
        )
        INFO    [2025-02-24 12:00:10,740] imav.plugins.wordpress: Malware scan finished:
        HookEvent.MalwareScanningFinished(
            {
                'scan_id': 'a74271d2cdd04e0c9bd49ef6de23e0d8',
                'scan_type': 'user',
                'path': '/home/user4',
                'started': 1740398383,
                'total_files': 39229,
                'total_malicious': 3,
                'error': None,
                'status': 'ok',
                'scan_params': {'intensity_cpu': 2, 'intensity_io': 2, 'intensity_ram': 2048, 'initiator': None, 'file_patterns': None, 'exclude_patterns': None, 'follow_symlinks': False, 'detect_elf': True},
                'stats': {'scan_time': 27, 'mem_peak': 28217344, 'smart_time_hs': 0.004, 'scan_time_hs': 1.1751, 'smart_time_preg': 0, 'scan_time_preg': 2.7391, 'finder_time': 13.5896, 'cas_time': 0.7562, 'deobfuscate_time': 0.8998, 'total_files': 39229}
            }
        )
        """
        # Skip if plugin is disabled
        if not self.last_config_value:
            return

        # Leave early if status is not ok or path or stats are missing.
        if (
            message.get("status") != "ok"
            or not message.get("path")
            or not message.get("stats")
        ):
            return

        # Malware scan is finished, figure out what sites need to be updated based on the path.
        path = message["path"]
        sites = plugin.get_sites_by_path(path)
        if not sites:
            logger.debug("Scan finished => no sites found for path=%s", path)
            return

        # Update data on the sites that need to be updated.
        logger.info(
            "Scan finished => %s site(s) need to be updated", len(sites)
        )

        await plugin.update_data_on_sites(self._sink, sites)

        logger.info("%s site(s) updated after a scan", len(sites))

VaKeR 2022