9 minutes reading time (1848 words)

Test your Extension, Part 5: Security

Test your Extension, Part 5: Security

In this series, we explore methods and tools to test a custom Joomla extension. In this fifth and final episode we’ll check if your extension is secure.

Joomla is a secure CMS. The Joomla Security Strike Team (here is an interview from 2022) springs into action as soon as a possible security issue is reported. A security release is prepared quickly, if needed. Joomla extensions with a possible security problem are put on the Vulnerable Extensions List and blocked on the Joomla Extensions Directory until a patch is available.

When you create a Joomla extension, you want to be sure you won’t introduce a vulnerability. Your extension becomes part of the whole site and you don’t want to be the door through which that site is hacked. The building blocks that Joomla offers are secure, so use them. In this article I’ll show five of the most exploited techniques to hack a site via an extension, how to avoid those weak points in your code, and how to test for it.

In the “Fundamentals” article in the Joomla Developers Manual you can find some basic guidelines that you should follow in order to develop secure extensions.

Top five extension vulnerabilities

  1. SQL Injection:
    the open door to the database 

    Putting user input directly in a database query is like leaving your house door wide open.

    Here’s a classic example:

    // ❌ VULNERABLE: Never do this
    $id = $app->input->get('id');  // user-supplied value
    $db = $this->getDatabase();
    $query = 'SELECT * FROM #__mycomponent_items WHERE id = ' . $id;
    $db->setQuery($query);
    $result = $db->loadObjectList();

    In this example an attacker might set id=1 OR 1=1 and suddenly reads your entire table. This is a very simple example, but there are very sophisticated strings that can be created, using quotes, SQL comments, UNION statements, etc. Crafting such strings has evolved from a manual hobby of script kiddies into a largely automated, AI-assisted industry. It’s a very powerful attack method which often leads to complete takeover of a site.

    Never, ever, ever put raw user input into your query. It’s a bomb. Always filter what is given in, cast to the right type, escape values, use Joomla’s query builder and bind parameters in a query:

    // ✅ SAFE: Use prepared statements
    $id = (int) $app->input->getInt('id');  // cast to integer
    $db = $this->getDatabase();
    $query = $db->getQuery(true)
        ->select($db->quoteName('*'))
        ->from($db->quoteName('#__mycomponent_items'))
        ->where($db->quoteName('id') . ' = :id')
        ->bind(':id', $id, \Joomla\Database\ParameterType::INTEGER);
    $db->setQuery($query);
    $result = $db->loadObject();

    Strings can be difficult to filter. Sometimes the malicious string is first put into the database, and then later, in some different part of the application is retrieved and used in a query. Such second order SQL injection can take you off guard, when you don’t see it anymore as a direct user input. 

    More information about secure database queries in this article in the Developers Manual.

    Always use bound parameters in a query!

  2. Cross-Site Scripting (XSS):
    a script has been smuggled on your page

    This is to be the most frequently reported vulnerability in Joomla extensions. With XSS someone has put some JavaScript on your site that can do all kinds of unwanted things: read or alter anything the user types in, read session cookies, redirect the site visitor, put unwanted content on the site, or whatever. 

    For instance, if people can leave some review on a product’s page:

    <div class="review">  
       <?php echo $review->text; ?> // Unfiltered!
    </div>

    Someone could write something like:

    Some nice words about the product… <script>/*some malicious script, including sending document.cookie to some other site*/ </script>
    

    as a review. The script will be put on the site, you don’t immediately see it, but it can sneakily do its nasty work. 

    Always filter all user input server side! If possible don’t allow HTML tags. You’ll find more information in the articles in the Developers Manual about handling input and about server-side validation.

    Escape output! To escape user content in Joomla, use the escape() method of a component view or the htmlspecialchars() function elsewhere. In JavaScript rather use .textContent than .innerHTTML, if you want to be sure a string doesn’t contain HTML tags.

  3. Cross-Site Request Forgery (CSRF):
    they pretend to be you

    In a CSRF attack you are tempted to click on a link to site B, while you are logged in on site A. On site B there is a hidden link to a page you have access to on site A. For instance a page to create a new user… Your cookies for site A will be sent with that request, so the new user is created, without you knowing it.

    To prevent this kind of attack, add a form token to every page with user input:

    <?php echo HTMLHelper::_('form.token'); ?>

    This adds a hidden input to your form with the value of the token as name:

    <input type='hidden' name='b6162fe7cb16945f33138615699249cc' value='1' />

    Check this token on the server. In controllers you can use $this->checkToken(), elsewhere you can use Session::checkToken().

    To include the token in Ajax requests use Joomla.getOptions('csrf.token') to get the token and add it as a variable name to the POST data in order to check the token server-side in the same way as when you submit a form:

    postBackData[Joomla.getOptions('csrf.token')] = 1;
    

    See the article about CSRF Protection in the Developers Manual.

    Always use and check Joomla’s form tokens!

  4. Unrestricted File uploads:
    bring the garbage inside

    Be extremely cautious with the possibility of uploading files. Never let a user upload executable files like PHP, JavaScript etc. Look at the canUpload() method of the Joomla\CMS\Helper\MediaHelper class as an example of sanitising files: check file extensions, MIME type, size, etc. Don’t allow any more file types than strictly necessary for your extension. 

    Upload files via the MediaManager if possible. Check uploads thoroughly.

  5. Directory Traversal:
    jump over the fence

    Never allow ../, the path to the parent directory, in a filename. Joomla\Filesystem\File::makeSafe($fileName) is a handy small utility to strip such unwanted directory traversal parts from the filename.

    Always sanitise file and folder names that users can input.

There are numerous other tricks to hack a site. Most attacks combine several of those vulnerabilities. For instance stealing the form-token via XSS and using that in a CSRF attack. Combined, all the dozens of “attack vectors” form a powerful tool.

General security testing tools: hack your own site

To help you spot those weak points there are several tools to scan your site for vulnerabilities. These testing tools should never be used in production, only in staging environments.

  • OWASP ZAP (Zed Attack Proxy): a free, open-source dynamic application security testing tool. Point it at your Joomla site and it will actively probe for vulnerabilities. See the ZAP documentation.
  • Burp Suite Community: Industry-standard proxy for intercepting and manipulating requests. The Community edition is free and more than adequate for extension testing. See the BURP documentation. In the tutorial they use a deliberately vulnerable website. Burp offers free training material to learn about all kinds of vulnerabilities, how they are used,  and how to avoid them in your extension. Really great stuff to learn from!
  • SQLMap: Open source automated SQL injection detection and exploitation. You can use it to find an SQL injection vulnerability in your extension. Short documentation of SQL Map.

Testing with PHPStan, PHPUnit, and Cypress

In previous episodes of this series we tested an extension with PHPStan, PHPUnit and Cypress. Those tools can also be used for security testing. You can customise security rules and tests specifically for your extension.

Static Analysis: PHPStan

PHPStan is a static analysis tool for PHP. It catches type errors and logical issues. Running it at level 8 on your extension before shipping will surface obvious type-juggling issues that could lead to SQL injection or improper validation.

Unit Testing Security Logic: PHPUnit

PHPUnit tests should cover your sanitisation. For instance, if we would have some sanitise() or sanitisePath() method:

<?php
use PHPUnit\Framework\TestCase;

class InputSanitisationTest extends TestCase
{
    /** @test */
    public function it_escapes_xss_in_output()
    {
        $input = '<script>alert("xss")</script>';
        $sanitised = this->sanitise($input);
        $this->assertStringNotContainsString('<script>', $sanitised);     

    }

    /** @test */
    public function it_rejects_path_traversal()
    {
        $this->expectException(\InvalidFileNameException::class);


        $malicious = '../../../configuration.php';
        $basePath = JPATH_ROOT . '/components/com_test/files/';
        $resolved = $this->sanitisePath($basePath . '/' . $malicious);
     }
}

 

End-to-End Security Testing: Cypress

With Cypress end-to-end tests you can give some malicious input in the browser, and check if that is handled correctly. For instance:

// cypress/e2e/security/xss-protection.cy.js

describe('XSS Protection', () => {
  it('should not execute injected script in search results', () => {
    const xssPayload = '<script>window.__xss_executed = true</script>';

    cy.visit('/index.php?option=com_mycomponent&view=list');
    cy.get('[data-cy="search-input"]').type(xssPayload);
    cy.get('[data-cy="search-submit"]').click();

    // Verify script was not executed
    cy.window().then((win) => {
      expect(win.__xss_executed).to.be.undefined;
    });

    // Verify input is encoded in the DOM
    cy.get('[data-cy="search-results"]')
      .should('contain.text', '<script>')  // Displayed as text, not executed
      .and('not.have.html', '<script>');
  });

  it('should reject form submission without CSRF token', () => {
    cy.request({
      method: 'POST',
      url: '/index.php?option=com_mycomponent&task=item.save',
      body: { title: 'Test', id: 0 },  // No CSRF token
      failOnStatusCode: false
    }).then((response) => {
      expect(response.status).to.eq(403);
    });
  });
});

 

Standards, Organisations and CVE Listings

OWASP: Your Security Bible

Among other things the Open Web Application Security Project (OWASP) maintains the OWASP Top 10, the OWASP Testing Guide (WSTG), and the OWASP Cheat Sheet Series. These are fantastic reference guides of all kinds of vulnerabilities and their prevention. A must read for every serious developer!

CVE Listings: The Wall of Shame (and Lessons)

Common Vulnerabilities and Exposures (CVE, hosted at cve.mitre.org and NVD at nvd.nist.gov) is the global registry of publicly disclosed security vulnerabilities. Every known web application vulnerability gets a CVE number. You can browse through them and learn from others' expensive mistakes.

If you use other PHP packages in your extension, you can check them for known CVEs with composer audit. The same for Node packages with npm audit.

Joomla maintains its own Vulnerable Extensions List (VEL). If your extension gets listed there, expect a very bad week. Better prevent that by testing your extension before going into production.

The End

This concludes this series about testing your extension. Hopefully it contributes to making your extension more robust, accessible and secure. Just like Joomla core is.


Resources

Joomla Developers Manual about security

Joomla Community Magazine articles about secure Joomla sites

External resources

This “Test your Extension” series

  • PHPUnit, test an isolated piece of PHP (a “unit”) in a controlled environment.
  • Cypress, end-to-end tests, running the extension in a Joomla site.
  • PHPStan, static code analysis to check source code on errors.
  • Accessibility, avoid barriers to access your extension.
  • Security (this article)

Some articles published on the Joomla Community Magazine represent the personal opinion or experience of the Author on the specific topic and might not be aligned to the official position of the Joomla Project

1
The March Issue
 

Comments

Already Registered? Login Here
No comments made yet. Be the first to submit a comment

By accepting you will be accessing a service provided by a third-party external to https://magazine.joomla.org/