— Artikel — № 025

025 —Architecture

CMS herkennen via het filesystem: een korte veldgids

Je krijgt een FTP-login, geen documentatie, en 20 minuten voor het klantgesprek. Vijf plekken die een CMS benoemen voor je één PHP-bestand opent.

Messing sleutel met touwlus op crème kaart met gestempeld serienummer, rode lakzegel, op linnen doek.
Hero · gestileerd stilleven№ 025

De FTP-credentials komen binnen in een Slack-DM zonder verdere context. Het vorige bureau is in maart van de radar verdwenen, de klant verwacht morgenochtend een fix, en niemand in de thread kan je vertellen of dit nou WordPress is, Drupal, of 'iets custom dat Greg in 2014 heeft gebouwd'. Je hebt een uur tot het gesprek. Je gaat de site niet lokaal installeren om erachter te komen.

Het goede nieuws: elk groot PHP-CMS laat genoeg sporen achter in de docroot dat een nette inspectie van de bestandsstructuur, plus één blik in .htaccess en de database, het binnen zestig seconden benoemt. Wat volgt is de veldgids die ik tien jaar geleden had willen hebben, toen ik dit op uurtarief stond uit te zoeken.

De root vertelt je bijna alles

De snelste aanwijzing is de top-level listing. Elk groot PHP-CMS laat daar een mappensignatuur achter die versies, hosting providers en de ergste 'we hebben het even opgeruimd'-refactor van een vorige freelancer overleeft.

WordPress. Zoek naar wp-admin/, wp-content/, wp-includes/, plus wp-config.php in de root (of, bij beter beveiligde installs, één niveau boven de docroot). Staat wp-config-sample.php er nog naast, dan is de site handmatig geïnstalleerd in plaats van via een one-click installer.

Drupal. Hangt af van de hoofdversie. Drupal 7 zet sites/, modules/, themes/, includes/, misc/ en profiles/ in de root, met een herkenbare CHANGELOG.txt die opent met 'Drupal X.x.x'. Drupal 8 en later verplaatsen het meeste van het framework naar core/ en voegen een vendor/-directory toe. Composer-gebaseerde Drupal-installs (vrijwel alle installs sinds 2019) zetten de publieke docroot onder web/ en de project-metadata één niveau hoger.

Joomla. De folders administrator/, components/, modules/, plugins/, templates/, libraries/, media/, language/, plus een configuration.php in de root. De zekerste indicator is administrator/. Niets anders in deze lijst gebruikt die naam.

Magento 1. app/code/, skin/, js/, media/, var/, plus app/Mage.php. Als Mage.php bestaat, kijk je naar Magento 1 en heb je een beveiligingsprobleem: geen security patches meer sinds juni 2020, volgens Adobe's end-of-life-aankondiging.

Magento 2. app/etc/env.php, pub/, bin/magento, vendor/magento/. De bin/magento CLI is de makkelijkste indicator.

PrestaShop. classes/, controllers/, override/, config/defines.inc.php, plus een front controller index.php die verwijst naar de PrestaShop-bootstrap.

Verborgen aanwijzingen als de root verplaatst is

Soms heeft een vorige developer de docroot gealiased naar public/, web/ of httpdocs/, en wat je op het hoogste niveau ziet lijkt op een generiek Composer-project. Duik dan in de echte docroot en draai:

ls -la | head -30
head -5 index.php

De eerste vijf regels van index.php benoemen vrijwel altijd het framework. Uit het geheugen:

// WordPress
require __DIR__ . '/wp-blog-header.php';

// Joomla
define('_JEXEC', 1);
require_once JPATH_BASE . '/includes/defines.php';

// Drupal 8+
use Drupal\Core\DrupalKernel;

// Magento 2
require __DIR__ . '/app/bootstrap.php';

// October / Winter CMS
require __DIR__.'/bootstrap/autoload.php';

Is index.php leeg of bevat het alleen een redirect, dan staat de rewrite-logica ernaast.

De .htaccess-vingerafdruk

De blokcommentaren in standaard .htaccess-bestanden zijn ongewoon eerlijk. Zoek naar deze letterlijke strings:

# BEGIN WordPress
# Joomla! .htaccess
## Mage_Core      (Magento 1)
## Magento        (Magento 2)
# Drupal

WordPress schrijft zijn rewrite rules tussen de markers # BEGIN WordPress en # END WordPress, en herschrijft ze bij elke Permalinks-save, dus het blok blijft meestal intact. Joomla's default levert bovenaan het letterlijke commentaar 'Joomla! .htaccess'. Magento 2's default opent met een lang blok dat de MAGE_MODE-omgevingsvariabele uitlegt.

Heeft een site een custom .htaccess zonder een van deze markers, terwijl elke request naar één front controller wordt gemapt, dan kijk je waarschijnlijk naar een Laravel- of Symfony-app die de docroot met de hand heeft opgezet. Bevestig door composer.json te lezen.

De database lezen zonder het CMS te openen

Bestandsstructuur plus één blik op het schema haalt de laatste 5% twijfel weg. Verbind met mysql of je client naar keuze en draai SHOW TABLES;. De prefixes en een paar bekende namen zijn diagnostisch:

-- WordPress
wp_options, wp_posts, wp_users, wp_postmeta

-- Drupal 7
node, node_revision, users, variable, system

-- Drupal 8+
config, key_value, node__body, users_field_data, watchdog

-- Joomla (prefix randomised at install)
_users, _extensions, _menu, _session

-- Magento 1
core_config_data, sales_flat_order, catalog_product_entity

-- Magento 2
core_config_data, sales_order, customer_entity, inventory_source_item

-- PrestaShop
ps_configuration, ps_product, ps_customer

Vind je een migrations-tabel naast duidelijk framework-achtige tabellen (geen business-velden, alleen timestamps en classnames), dan kijk je naar een Laravel-app die zich voordoet als CMS, of een Laravel-gebaseerd CMS zoals October. Lees composer.json ter bevestiging.

De vijf-seconden-triage-cheatsheet

Voor als de screen-share open staat en de klant meekijkt:

  1. ls -la op de docroot. Vergelijk met de kop-folders hierboven.
  2. head -5 index.php. Het framework noemt zichzelf meestal bij naam.
  3. Grep .htaccess op BEGIN WordPress, Joomla, Magento, Drupal. Eén match en je bent klaar.
  4. Nog steeds onduidelijk? cat wp-config.php app/etc/env.php configuration.php sites/default/settings.php 2>/dev/null | head -30. Welk bestand ook reageert, je hebt je CMS gevonden.
  5. SHOW TABLES; en scan op de prefixes hierboven.

De meeste sites geven zich al bij stap 1 over. De rest plooit bij stap 4.

Waarom dit verder gaat dan een feesttruc

Snel het CMS benoemen is het verschil tussen factureren voor de fix en factureren voor het uitzoekwerk. Het vertelt je ook welk patch-niveau er open staat: een Joomla 3.x uit 2018 met administrator/ nog publiek heeft een totaal ander threat model dan een Drupal 9 uit 2022 achter een CDN. De stack kennen binnen de eerste zestig seconden laat je realistische verwachtingen scheppen tijdens het gesprek. 'Dit is Magento 1, en dáárom gaat niemand je een hotfix offreren onder de €4k' is een snelle, verdedigbare zin.

Toen we Pier bouwden voor werk aan verouderde sites, was het eerste wat het doet na een geslaagde FTP- of SFTP-login het fingerprinten van de docroot met precies de markers hierboven, zodat de chat-interface de juiste context vooraf kan laden: waar het config-bestand staat, welke database het CMS gebruikt, welke paden je nooit moet aanraken. De MySQL-editor opent al gericht op het juiste schema, en elke wijziging die je via Pier doet wordt vastgelegd in de versiehistorie, zodat die zestig-seconden-snuif maar één keer per project hoeft.

Het kleinste dat je vandaag kunt doen: open de laatste onbekende site die je hebt overgenomen, draai ls -la en head -5 index.php, en plak de output in een comment bovenaan je projectnotities. De volgende keer dat iemand je die credentials toeschuift, scheelt het je een meeting.

— Vragen —

Kan ik de mapnaam wp-content vertrouwen als WordPress-signatuur?

Meestal wel. Hij is configureerbaar via WP_CONTENT_DIR in wp-config.php, maar in de praktijk hernoemt minder dan 5% van de installs hem. Een hernoemde wp-content naast wp-admin en wp-includes is nog steeds dezelfde vingerafdruk.

Wat als /administrator/ bestaat maar de rest niet op Joomla lijkt?

Dat is een Joomla-install waar iemand de front-end-docroot heeft verplaatst of gechmod'd. De admin werkt nog omdat hij self-contained is. Draai head -5 administrator/index.php om het te bevestigen voor je iets anders doet.

Hoe onderscheid ik Drupal 7 snel van Drupal 9?

Drupal 7 heeft een sites/all/-directory en een CHANGELOG.txt in de root. Drupal 8 en later hebben core/, vendor/ en composer.json. Zie je web/ bovenaan met core/ erin, dan zit je op Composer-managed Drupal 9 of 10.