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
-
SQL Injection:
Putting user input directly in a database query is like leaving your house door wide open.
the open door to the database
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 setid=1 OR 1=1and 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! -
Cross-Site Scripting (XSS):
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.
a script has been smuggled on your page
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 theescape()method of a component view or thehtmlspecialchars()function elsewhere. In JavaScript rather use.textContentthan.innerHTTML, if you want to be sure a string doesn’t contain HTML tags. -
Cross-Site Request Forgery (CSRF):
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.
they pretend to be you
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 useSession::checkToken().
To include the token in Ajax requests useJoomla.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! -
Unrestricted File uploads:
Be extremely cautious with the possibility of uploading files. Never let a user upload executable files like PHP, JavaScript etc. Look at the
bring the garbage insidecanUpload()method of theJoomla\CMS\Helper\MediaHelperclass 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. -
Directory Traversal:
Never allow
jump over the fence../, 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
- Fundamentals of security
- Common Vulnerabilities
- handling input
- Secure Database Queries
- CSRF Protection
- Server-side Form Validation
Joomla Community Magazine articles about secure Joomla sites
- A Security Overview of Joomla’s Checking and Validation of File Uploads, Simon Stockhause & Julia Polner (September 2021)
- Best Practices to Secure your Joomla Website, Ahmed Moussa (April 2021)
- How to Avoid Joomla Security Mistakes, Ahmed Moussa (February 2021)
- Joomla 4: Using the Security Header Features, Patrick Jackson (April 2020)
External resources
- Article about Mastering Inner HTML vs Text Content in JavaScript (to prevent XSS vulnerabilities).
- OWASP ZAP: scan a staging site with your extension for vulnerabilities.
- Burp Suite Community: idem.
- SQLMap: scan your staging site for SQL injection.
- OWASP Top 10
- OWASP Testing Guide (WSTG)
- OWASP Cheat Sheet Series how to avoid vulnerabilities
- Common Vulnerabilities and Exposures (CVE, hosted at cve.mitre.org and NVD at nvd.nist.gov)
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
By accepting you will be accessing a service provided by a third-party external to https://magazine.joomla.org/
Comments