Summary

Source code review of the Novarain/Tassos Framework revealed three critical primitives – unauthenticated file read, unauthenticated file deletion, and SQL injection leading to arbitrary database read – across five widely deployed Joomla! extensions (Convert Forms, EngageBox, Google Structured Data, Advanced Custom Fields, and Smile Pack).

Taken together, these flaws enable reliable remote code execution (RCE) and administrator account takeover on unpatched Joomla! instances.

The vendor has released updates, you can find them in the Downloads section of our site: https://www.tassos.gr/downloads

Credit

The vulnerabilities have been discovered by, an independent security researcher p1r0x, working with SSD Secure Disclosure.

Affected Versions
  • Novarain/Tassos Framework (plg_system_nrframework) v4.10.14 – v6.0.37
  • Convert Forms v3.2.12 – v5.1.0
  • EngageBox v6.0.0 – v7.1.0
  • Google Structured Data v5.1.7 – v6.1.0
  • Advanced Custom Fields v2.2.0 – v3.1.0
  • Smile Pack v1.0.0 – v2.1.0
Vulnerability Details

All affected extensions ship the same system plugin, historically named Novarain Framework and later rebranded as Tassos Framework. The vulnerable code resides in plg_system_nrframework, retrieved from the vendor release site.

Environment Setup

Testing leveraged the official Joomla! Docker image alongside MariaDB. A ready-to-use Compose specification is available at env-setup/compose.yml:

db:
    image: mariadb:11
    container_name: joomla-db
    restart: unless-stopped
    environment:
      MARIADB_DATABASE: joomla
      MARIADB_USER: joomla
      MARIADB_PASSWORD: test123#
      MARIADB_ROOT_PASSWORD: admin123#
    volumes:
      - db_data:/var/lib/mysql

Deploy the stack, complete the Joomla! installer, and then upload any target extension via System → Manage → Extensions → Install. Installation method does not affect exploitability, but ensure the extension status is set to Enabled.

Source code analysis

In plg_system_nrframework folder, there is a nrframework.php file.

    /**
     *  Listens to AJAX requests on ?option=com_ajax&format=raw&plugin=nrframework
     *
     *  @return void
     */
    public function onAjaxNrframework()
    {
        Session::checkToken('request') or jexit(Text::_('JINVALID_TOKEN'));

        // Check if we have a valid task
        $task = $this->app->input->get('task', null);

        $non_admin_tasks = [
            'include'
        ];

        // Only in backend
        if (!in_array(strtolower($task), $non_admin_tasks) && !$this->app->isClient('administrator'))
        {
            return;
        }

        // Check if we have a valid method task
        $taskMethod = 'ajaxTask' . $task;

        if (!method_exists($this, $taskMethod))
        {
            die('Task not found');
        }

        $this->$taskMethod();
    }

In this code block, we can see, that this is an AJAX listener, which to unauthorized users only allows non_admin_tasks, which is only include.

Therefore, the user is only allowed to call the function ajaxTaskInclude (method names in PHP are case insensitive).

private function ajaxTaskInclude()
    {
        $input = $this->app->input;

        $file  = $input->get('file');
        $path  = JPATH_SITE . '/' . $input->get('path', '', 'RAW');
        $class = $input->get('class');

        $file_to_include = $path . $file . '.php';

        if (!file_exists($file_to_include))
        {
            die('FILE_ERROR');
        }

        @include_once $file_to_include;

        if (!class_exists($class))
        {
            die('CLASS_ERROR');
        }

        if (!method_exists($class, 'onAJAX'))
        {
            die('METHOD_ERROR');
        }

        (new $class())->onAJAX($input->getArray());
    }

This function takes the file, path and class input, and then checks if a PHP file with the stated class and method called onAjax exists.

If it does, it includes the class and then calls the onAjax method in the given class.

This is not a secure coding practice, since it can call any class in any file that contains method onAjax under JPATH_SITE root.

Arbitrary File Read

A file at plugins/system/nrframework/fields/nrchainedfields.php is one of the candidates, that has a method onAjax.

    public function onAjax($options)
    {
        $options = new Registry($options);

        if (!$select_id = $options->get('select_id'))
        {
            echo json_encode([
                'error' => true,
                'response' => Text::_('NR_CANNOT_PROCESS_REQUEST')
            ]);
            jexit();
        }

        if (!$value = $options->get('value'))
        {
            echo json_encode([
                'error' => true,
                'response' => Text::_('NR_CANNOT_PROCESS_REQUEST')
            ]);
            jexit();
        }

        if (!$value = json_decode($options->get('value'), true))
        {
            echo json_encode([
                'error' => true,
                'response' => Text::_('NR_CANNOT_PROCESS_REQUEST')
            ]);
            jexit();
        }

        if (!$data_source = $options->get('data_source'))
        {
            echo json_encode([
                'error' => true,
                'response' => Text::_('NR_CANNOT_PROCESS_REQUEST')
            ]);
            jexit();
        }

        if (!$csv = $options->get('csv'))
        {
            echo json_encode([
                'error' => true,
                'response' => Text::_('NR_CANNOT_PROCESS_REQUEST')
            ]);
            jexit();
        }

        $csv = json_decode($csv, true);

        // get field ID from select ID
        $id = preg_replace('/_[0-9]+$/', '', $select_id);

        if (!$id || !$select_id || !$value || !$csv)
        {
            echo json_encode([
                'error' => true,
                'response' => Text::_('NR_CANNOT_PROCESS_REQUEST')
            ]);
            jexit();
        }

        $this->id = $id;

        // get all CSV data
        if (!$this->choices = \Tassos\Framework\Helpers\ChainedFields::loadCSV($csv, $data_source, $this->separator, $this->id . '_', $this->name))
        {
            echo json_encode([
                'error' => true,
                'response' => Text::_('NR_CANNOT_PROCESS_REQUEST')
            ]);
            jexit();
        }

        // find next select options
        $this->findNextOptions($id, $select_id, $value);
    }

This function takes an input of data, which is then used to load csv. But because the file type checks are implemented incorrectly, we can read any file, that Joomla! running user has the permissions to read.

Arbitrary File Deletion

A file at plugins/system/nrframework/fields/nrinlinefileupload.php is another of the candidates, that has a method onAjax.

/**
     * Handles the AJAX request
     *
     * @param   array    $options
     *
     * @return  array
     */
    public function onAjax($options)
    {
        $options = new Registry($options);

        if ($options->get('action') == 'remove')
        {
            $this->onRemove($options);
            return;
        }

        $this->onUpload();
    }

In this method, we will focus on the scenario, when the attacker give the input action=remove, which calls the method onRemove.

/**
     * On file removal.
     * 
     * @return  string
     */
    private function onRemove($options)
    {
        if (!$file = $options->get('remove_file'))
        {
            echo json_encode([
                'error' => true,
                'response' => Text::_('NR_UPLOAD_ERROR_INVALID_FILE')
            ]);
            jexit();
        }

        // If file exists, remove it
        if (file_exists($file))
        {
            unlink($file);
        }

        echo json_encode([
            'error' => false,
            'response' => Text::_('NR_FILE_DELETED')
        ]);
        jexit();
    }

This is the method onRemove, in which we can see, it unlinks the file, which causes the deletion.

Arbitrary DB Read

Third file is located at plugins/system/nrframework/fields/ajaxify.php, which also has onAjax method.

public function onAjax($options)
    {
        $this->options = new Registry($options);

        // Reinitialize Field
        $this->element = (array) $this->options->get('field_attributes');
        $this->init();

        $this->limit       = $this->options->get('limit', $this->limit);
        $this->page        = $this->options->get('page', 1);
        $this->search_term = $this->options->get('term');

        $rows = $this->getItems();

        $hasMoreRecords = false;

        if ($this->limit > 0)
        {
            $total = $this->getItemsTotal();
            $hasMoreRecords = ($this->page * $this->limit) < $total;
        }

        $data = [
            'results' => $rows,
            'pagination' => ['more' => $hasMoreRecords]
        ];

        echo json_encode($data);
    }

Another file at plugins/system/nrframework/fields/componentitems.php extends JFormFieldAjaxify class, which is the main class at ajaxify.php, which therefore inherits it’s methods.

If we inspect this file with class JFormFieldComponentItems, we can see it contains the code for database accessing and reading. Since we can influence the data, this effectivly makes it an SQLi with the impact of reading any table and columns, that the Joomla! designated user can read.

Compound Impact

The combination of filesystem read/write primitives and SQL injection reliably escalates to RCE. A typical kill chain involves:

  1. Use SQL injection to poll and dump active super administrator sessions(Super admin login could also be influenced with file deletion causing the website to malfunction).
  2. Authenticate to /administrator and upload a malicious extension or modify template PHP files for code execution.

Hardening techniques such as /administrator password gates or custom security parameters are ineffective because attackers can delete .htpasswd or recover plugin secrets from the database.

Mitigation Guidance
Immediate Actions
  • Disable the affected extensions or the plg_system_nrframework plugin on internet-facing Joomla! instances until patches are released.
  • Restrict access to ?option=com_ajax endpoints via WAF rules or web server ACLs to reduce exposure.
  • Monitor logs for suspicious AJAX requests invoking task=include, unexpected CSV loads, or mass file deletions.

?

Get in touch

Skip to content