119 —Joomla
De Joomla 3 #__extensions-tabel: een veldgids in zes kolommen
Zes kolommen in #__extensions vertellen je wat Joomla daadwerkelijk laadt. Een veldgids voor het moment dat je een site overneemt waarvan de admin niet opent.
Een Nederlands bureau waar we mee samenwerken nam vorige maand een Joomla 3.10-site over. Het admin-wachtwoord werkte nog, maar de Extensions Manager gaf op elk tabblad een witte pagina. Ze moesten weten wat er werkelijk op de site draaide voordat ze een migratie konden offreren. Niet wat de brochure beweerde. Wat de database zei.
Dat antwoord zit in één tabel: #__extensions. Elke component, plugin, module, template, library en taalpakket registreert zichzelf daar bij installatie. Bestaat de rij, dan beschouwt Joomla de extensie als geïnstalleerd. Staat enabled op 1, dan draait hij. Al het andere is decoratie.
Dit is een veldgids om die tabel met de hand te lezen, in phpMyAdmin of een willekeurige MySQL-client. Zes kolommen vertellen je, zonder twijfel, wat er werkelijk draait op een verouderde site die voor je ligt.
De tabel in één oogopslag
Draai dit tegen de database (vervang jos_ door je eigen prefix):
SELECT extension_id, type, element, folder, client_id, enabled
FROM jos_extensions
WHERE state = 0
ORDER BY type, element;Op een gemiddelde site krijg je een paar honderd rijen terug. Het grootste deel is core. Voor een audit gaat het om de rijen van derden, en die herken je door drie kolommen samen te lezen: type, element en folder. Die drie vormen de primary key van de extensie voor zover de installer van Joomla het ziet.
Kolom 1: type
De kolom type vertelt je welk soort extensie de rij vertegenwoordigt. De waarden die je tegenkomt zijn:
component: een volledige back-end applicatie zoalscom_contentofcom_virtuemart.plugin: een event listener die afgaat op systeem- of content-events.module: een UI-blok dat in een template-positie wordt gerenderd.template: het thema voor de site of de admin.library: gedeelde PHP-code die door componenten wordt gebruikt.file: een extensie op bestandsniveau, zeldzaam bij door gebruikers geïnstalleerde code.language: een taalpakket.package: een meta-extensie die meerdere van bovenstaande in één keer installeert.
Bij een audit van een verouderde Joomla 3-site zijn de rijen die er het meest toe doen component en plugin. Componenten bevatten doorgaans de meeste code en het grootste aanvalsoppervlak. Plugins zijn waar backdoors zich verstoppen, omdat ze bij elke page load worden uitgevoerd.
Kolom 2: element
De kolom element bevat de technische naam. Voor componenten is dat com_xxx. Voor modules mod_xxx. Voor plugins is het simpelweg de korte naam van de plugin (akeebabackup, jce, regularlabsmanager).
Dit is de waarde waar je in het bestandssysteem op zou greppen. Een rij met type='plugin' en element='akeebabackup' betekent dat de bestanden onder /plugins/<folder>/akeebabackup/ staan. Bestaat de rij wel maar de map niet, dan is de extensie half geïnstalleerd. Dat is een veelvoorkomende fout na een mislukte FTP-sync. Zie de Joomla-docs over de extension directory structure voor de volledige indeling.
Kolom 3: folder
Alleen plugins gebruiken folder. Die vertelt je tot welke plugingroep de rij behoort: system, content, authentication, user, editors, extension en nog een tiental andere. De bestanden van de plugin staan onder /plugins/<folder>/<element>/.
De groep is van belang, omdat die je vertelt wanneer de plugin draait. Een system-plugin gaat af bij elke request, voor de routing. Een content-plugin gaat alleen af wanneer content wordt gerenderd. Jaag je op een trage page load of een vreemde redirect, sorteer dan de tabel op folder='system' en lees elke rij in die subset.
Kolom 4: client_id
Een van de meest verkeerd gelezen kolommen in de tabel. client_id = 0 betekent dat de extensie bij de publieke site hoort. client_id = 1 betekent dat hij bij de administrator back-end hoort. Hetzelfde element kan twee keer voorkomen met verschillende client_id-waarden, eenmaal voor elk.
Dat is relevant wanneer je iets uitzet om een witte pagina te debuggen. Door enabled = 0 op de verkeerde client_id te zetten, sluit je jezelf buiten de back-end terwijl de bug aan de voorkant gewoon doorloopt. Controleer altijd beide rijen voor je de UPDATE schrijft.
Kolom 5: enabled
De eenvoudigste kolom. 1 betekent dat Joomla hem laadt. 0 betekent dat hij geïnstalleerd is maar niets doet. Je kunt dit vanuit SQL omzetten wanneer de admin-UI kapot is:
UPDATE jos_extensions
SET enabled = 0
WHERE type = 'plugin'
AND folder = 'system'
AND element = 'rogueplugin';Die ene statement is vaak het verschil tussen een fix van 90 seconden en vier uur herbouwen vanuit een back-up. Goed om te weten voor je hem nodig hebt.
Kolom 6: manifest_cache
De interessante. manifest_cache is een JSON-blob die de installer wegschrijft op het moment van installeren. Hij bevat de versie, auteur, e-mailadres van de auteur, copyright, licentie en creatiedatum zoals het XML-manifest van de extensie die heeft opgegeven. Zo kom je erachter welke versie van een extensie er feitelijk op disk staat, zonder ook maar één bestand te openen.
SELECT element,
JSON_EXTRACT(manifest_cache, '$.version') AS version,
JSON_EXTRACT(manifest_cache, '$.author') AS author
FROM jos_extensions
WHERE type = 'component'
AND state = 0;Vergelijk elke versie met de changelog van de leverancier of de Vulnerable Extensions List die het Joomla VEL-team bijhoudt. Is de versie van een rij ouder dan de vroegste gepatchte release, dan heb je een bevestigde kwetsbaarheid op een nog actieve extensie. Dat is bijna altijd de eerste bevinding die op een auditrapport thuishoort.
De zes samenleggen
Zes kolommen: type, element, folder, client_id, enabled en manifest_cache. In die volgorde gelezen vertellen ze je wat de extensie is, hoe hij heet, waar hij staat, op welke kant van de site hij draait, of hij actief is, en welke versie er op disk staat. Al het andere in de tabel (params, custom_data, access, ordering) is configuratie, geen identiteit.
Een nuttige auditquery die alle zes combineert:
SELECT type,
element,
folder,
CASE client_id WHEN 0 THEN 'site' ELSE 'admin' END AS side,
CASE enabled WHEN 1 THEN 'on' ELSE 'off' END AS state,
JSON_UNQUOTE(JSON_EXTRACT(manifest_cache, '$.version')) AS ver
FROM jos_extensions
WHERE state = 0
AND protected = 0
ORDER BY type, element;Draai die op een site die je overneemt en je hebt, in één pagina output, het dichtst dat Joomla bij een bill of materials komt. Vergelijk de output met het bestandssysteem onder /components, /plugins, /modules en /templates, en elke afwijking is een half geïnstalleerde extensie die een tweede blik verdient.
Wat je met de output doet
De reden om deze tabel met de hand te lezen, in plaats van de Extensions Manager te vertrouwen, is dat de manager zelf ook een extensie is. Op een half kapotte Joomla 3-site liegt hij net zo vaak als hij de waarheid vertelt. SQL niet.
Toen we Pier bouwden liepen we tegen precies dit aan bij klantaudits. Wat er uiteindelijk uitkwam, was de MySQL editor naast de bestandsboom plaatsen, met elke wijziging vastgelegd in de versiehistorie, zodat een vraag als 'is deze plugin nu écht geïnstalleerd' vanuit SQL beantwoord kan worden en op dezelfde manier ongedaan gemaakt als je dat in de admin zou doen.
Zit je op een Joomla 3-site die je nog niet volledig in kaart hebt, dan is het kleinste dat vandaag de moeite waard is de auditquery hierboven draaien tegen een read-only kopie van de database. Sla de output op als CSV. Dat bestand is het beginpunt van elk migratieplan dat je de komende zes maanden gaat schrijven.
— Vragen —
Wat is het verschil tussen state en enabled in #__extensions?
state = 0 betekent geïnstalleerd en actueel. state = -1 markeert een uninstall die nog in de wacht staat. state = 2 betekent ontdekt maar niet geïnstalleerd. enabled is een aparte runtime-vlag op een normaal geïnstalleerde rij.
Kan ik een extensie de-installeren door de rij uit #__extensions te verwijderen?
Nee. De uninstaller van Joomla verwijdert ook de bestanden, databasetabellen en assets die de extensie heeft aangemaakt. Alleen de rij verwijderen laat de rest als wezen achter. DELETE alleen wanneer de bestanden al weg zijn.
Waarom verschijnt dezelfde extensie twee keer in de tabel?
client_id 0 is de publieke site, 1 is de admin back-end. Een paar extensies installeren componenten aan beide kanten en registreren één rij per kant. Controleer altijd beide voor je enabled omzet.