Under The Hood 9.8.3
English version: Under The Hood 9.8.3
Deze release betreft een onderhouds- en beveiligingscyclus in plaats van een nieuwe functie. Het meeste werk is verricht op plekken waar website-eigenaren zelden kijken: de datalaag die uw beveiligingslogboeken leest, de code die notificatiemails samenstelt en de exportpaden die u een CSV-bestand met uw activiteitsgegevens leveren. Niets hiervan verandert het uiterlijk van de plugin, maar alles verandert wel de betrouwbaarheid en veiligheid ervan wanneer een website drukbezocht is, wanneer een logboek groot is of wanneer een aanvaller een kwetsbaarheid probeert te vinden die we nog niet hadden aangepakt.
Hieronder volgt een beschrijving van de implementatie voor beheerders en ontwikkelaars die willen begrijpen wat er precies is veranderd.
Het sluiten van een injectievector voor e-mailontvangers
WP Cerber verstuurt twee soorten transactionele e-mails waarin de naam van een persoon is opgenomen: het bericht met de pincode voor tweefactorauthenticatie en de activiteitsmelding. Beide berichten hebben de ontvanger opgebouwd in de bekende RFC 5322-vorm Name <email> en beide namen die naam rechtstreeks over uit WordPress-profielvelden ( user_firstname , user_lastname en display_name ) zonder deze te escapen.
Het probleem zit hem in de manier waarop wp_mail() een ontvanger als tekenreeks verwerkt. De functie splitst de tekenreeks op een letterlijke komma en negeert aanhalingstekens. Een gebruiker die de juiste tekens in zijn of haar profielnaam heeft geplaatst, zou daardoor een extra adres kunnen toevoegen aan de ontvangerslijst van de 2FA-pincode-e-mail die wordt gegenereerd door CRB_2FA::send_user_pin() en van de waarschuwingsmail die wordt gegenereerd via de user_list tak van cerber_get_email() . Op een site waar iedereen zich kan registreren en een weergavenaam kan instellen, is dat een reële mogelijkheid om stiekem een kopie van een beveiligingsbericht door te sturen.
We hebben een speciale sanitizer, crb_sanitize_mail_display_name() , toegevoegd aan cerber-common.php en deze toegepast op beide aanroeplocaties. Deze verwijdert precies de tekens die relevant zijn voor dit type injectie: de komma, het aanhalingsteken, de backslash, de punthaken en besturingstekens. De zichtbare naam wordt nog steeds normaal weergegeven voor legitieme gebruikers en de ontvangerslijst kan niet langer worden gemanipuleerd door een speciaal geconstrueerd profielveld.
Een opgeslagen XSS-vector verwijderen uit de ownership scanner.
De malware-scanner meldt wanneer het eigenaarschap van een geïnstalleerde plugin verandert in de WordPress.org-repository. Hiervoor gebruikt de scanner de metadata over het eigenaarschap die door die repository wordt aangeleverd. Tot nu toe werd een deel van deze metadata als pure HTML in de beheerdersmelding verwerkt, inclusief handmatig gemaakte ankertags voor links naar eigenaarsprofielen.
Die repository is een betrouwbare bron en de kans op schadelijke metadata is klein, maar externe data mag nooit als niet-geëscapte markup in de beheerdersinterface terechtkomen. Het is juist die aanname dat de repository betrouwbaar is, waar we liever niet op vertrouwen.
Om de vector correct te sluiten in plaats van deze lokaal te patchen, hebben we een nieuw UI Factory-element geïntroduceerd, formatted_text , dat is geconstrueerd met behulp van de helper crb_ui_formatted_text() . Dit element rendert een platte tekstsjabloon met genummerde %N$s placeholders. Letterlijke fragmenten van de sjabloon en scalaire argumenten worden HTML-escaped, terwijl elk argument dat zelf een UI-element is, wordt gerenderd door de actieve renderer. Elke dynamische waarde wordt dus geëscapt in de context waarin deze daadwerkelijk wordt uitgezonden, wat de enige betrouwbare manier is om fouten in de uitvoercontext te voorkomen.
Het eigendomsbericht in crb_check_ownership() gebruikt nu dit element, en de profiellinks van de eigenaar worden opgebouwd met crb_ui_link() in plaats van samengevoegde ankerstrings, waardoor profiel-URL's en weergavenamen respectievelijk als URL's en als tekst worden geëscapt. De vertaalstring is ongewijzigd, dus lokalisaties blijven werken. Het opgeslagen XSS-risico voor beheerders is verdwenen.
Het filter "Softwarefouten" in de verkeersinspecteur corrigeren.
In het Traffic Inspector-logboek kunt u via het formulier 'Geavanceerd zoeken' meerdere voorwaarden combineren. Een daarvan is het selectievakje 'Elke softwarefout'. Wanneer dit selectievakje werd gecombineerd met andere filters, konden verzoeken met een geregistreerde PHP-fout in de resultaten verschijnen, zelfs als ze niet voldeden aan de andere door u opgegeven voorwaarden.
De hoofdoorzaak lag in de operatorprioriteit bij de samenstelling van de WHERE-clausule. De oude code voegde de filterfragmenten samen als tekenreeksen en de gegroepeerde foutconditie stond niet tussen haakjes, waardoor een OR erin de omringende AND logica kon omzeilen. Door de verkeersquery naar de querybuilder te verplaatsen, staat de gegroepeerde foutconditie nu correct tussen haakjes en gedraagt het gecombineerde filter zich zoals de vorm voorschrijft. Wanneer u een zoekopdracht verfijnt, houden de resultaten nu rekening met alle door u ingestelde voorwaarden.
Het letterlijk matchen van zoekjokertekens
De geavanceerde zoekfunctie voor verkeerslogboeken stond voorheen toe dat een % of _ in een zoekterm fungeerde als een LIKE metateken, omdat de term zonder escapen in het patroon werd geplaatst. Dat is op zijn best een onbedoelde functie en op zijn slechtst een onnodig laadpatroon.
Elke LIKE term wordt nu eerst verwerkt door CRB_Database::escape_like() voordat de omringende wildcards worden toegevoegd, waardoor % en _ worden herkend als de letterlijke tekens die een gebruiker heeft ingevoerd. Zoekopdrachten gedragen zich voorspelbaar en de query kan niet worden gemanipuleerd om meer te scannen dan de bedoeling is.
Correctie van de afhandeling van geheugenlimieten voor exports
Het exporteren van een groot activiteiten- of verkeerslogboek is geheugenintensief, daarom verhoogt WP Cerber het beschikbare geheugen vóór de bewerking. In sommige omgevingen werd een numerieke geheugenlimietwaarde, zoals 512 geïnterpreteerd als bytes in plaats van megabytes. Wanneer dit gebeurde, verhoogde de plugin de limiet niet zoals bedoeld, waardoor een export eerder dan verwacht kon stoppen.
De waarde wordt nu correct geïnterpreteerd met de juiste eenheid, waardoor de geheugenvergroting zoals bedoeld wordt toegepast en grote exportprocessen succesvol worden uitgevoerd in de omgevingen die voorheen problemen ondervonden.
Logexporten opnieuw opbouwen rondom streaming
De exportfuncties voor activiteit en verkeer lazen de overeenkomende rijen in stukken, waarbij dezelfde SELECT telkens met een toenemende OFFSET werd uitgevoerd. Bij grote logbestanden komt dit neer op een diepe offset-scan, waarbij elk stuk meer kost dan het vorige en het resultaat steeds groter wordt in het geheugen.
Beide exports lezen hun rijen nu in één enkele, niet-gebufferde doorgang via CRB_Database::query_stream() , verpakt in een generator die één rij per keer oplevert. Het geheugen blijft gelijk, ongeacht hoeveel records overeenkomen, en de database voert het werk één keer uit in plaats van één keer per chunk. Omdat een niet-gebufferde stream de verbinding blokkeert tijdens het verwerken, worden het totaal aantal rijen en het datumbereik vooraf bepaald met afzonderlijke gebufferde COUNT en MIN / MAX -query's die zijn opgebouwd uit dezelfde filters, voordat de stream wordt geopend.
Er zijn twee response-headers toegevoegd aan de helperfunctie voor het downloaden van gedeelde bestanden, crb_file_headers() . X-Accel-Buffering: no geeft Nginx de instructie om, wanneer het PHP-FPM als front-end gebruikt, elk chunk direct door te sturen in plaats van de hele export te bufferen voordat deze wordt verzonden. Dit verbetert de time-to-first-byte en voorkomt dat de proxy een groot CSV-bestand in het geheugen vasthoudt. Deze header is specifiek voor Nginx en wordt door Apache met mod_php en andere proxies zonder problemen genegeerd. Cache-Control: no-store voorkomt dat de browser of een tussenliggende proxy een gevoelige beveiligingslog-export in de cache opslaat.
We hebben ook de opschoning van de stream deterministisch gemaakt. De export verbruikt de generator binnen een try / finally blok en geeft de enige resterende handle vrij in de finally , zodat het ongebufferde resultaat wordt vrijgegeven en de verbinding wordt ontgrendeld bij elk exitpad: normale voltooiing, een vroegtijdige stop of een uitzondering die wordt gegenereerd tijdens het schrijven van een rij. Voorheen kon een vroegtijdige breuk of een fout vóór volledige verwerking het resultaat open laten staan en de verbinding vergrendeld, waardoor de volgende query in dat verzoek zou mislukken.
Het datumbereik van de export rapporteren
De CSV-exportheader gaf voorheen alleen de actieve filters weer. Zowel de activiteits- als de verkeersexport voegen nu twee rijen toe aan de header met de tijdstempels van de oudste en nieuwste records die door de geëxporteerde gegevens worden gedekt. Omdat de header vóór de eerste rij wordt geschreven, is het bereik afkomstig van een bijbehorende MIN / MAX -query die is opgebouwd uit dezelfde gefilterde query als de export, en wordt het weggelaten wanneer er geen rijen overeenkomen. Wanneer u een export archiveert, registreert het bestand nu het exacte tijdsvenster dat het vertegenwoordigt.
Exportfouten zichtbaar maken in plaats van ze te verzwijgen.
Het oude exportpad kon ongemerkt falen. Als de database niet beschikbaar was of de rijstroom niet kon worden geopend, produceerde de oude code doorgaans een leeg CSV-bestand zonder verdere uitleg. Dit is de slechtste uitkomst voor iemand die tijdens een incident gegevens probeert op te halen.
De lees- en exportpaden voor beide logbestanden zijn herzien om een Revalt resultaat te retourneren dat ofwel de datapayload ofwel een gestructureerde foutmelding bevat, met verschillende codes zoals activity_export_query_build_failed , activity_export_db_unavailable en activity_export_stream_failed . Een fout op een lager niveau wordt in het resultaat opgenomen, zodat de oorspronkelijke oorzaak behouden blijft in plaats van te worden genegeerd. Bij fouten in de configuratie wordt de export nu beëindigd met wp_die() voordat er ook maar één CSV-byte wordt verzonden, in plaats van een leeg bestand te streamen.
Wanneer een export mislukt, ziet een beheerder met de manage_options bevoegdheid de onderliggende oorzaak, bijvoorbeeld de databasefout, toegevoegd aan het bericht. Gebruikers zonder die bevoegdheid zien dit niet, waardoor de relevante details de mensen bereiken die actie kunnen ondernemen, terwijl interne database-informatie voor anderen verborgen blijft. Het bericht wordt ontsnapt met behulp van crb_escape_html() .
Het samenvoegen van de activiteits- en verkeerslogboeken in domeinklassen.
Een groot deel van deze cyclus was structureel van aard. Zowel het activiteitenlogboek als het verkeerslogboek bevatten SQL-strings en resultaatverwerking die verspreid waren over de dashboard- en exportcode. We hebben die logica verplaatst naar de klassen CRB_Activity en CRB_Traffic_Log , waardoor het samenstellen van query's en het ophalen van rijen nu plaatsvindt achter duidelijke methoden zoals fetch() en stream_log() in plaats van dat ze als ruwe SQL door de presentatiecode worden verwerkt.
Beide klassen bouwen hun query's nu met de DB Warp query builder die verkregen wordt via warp_get_db() in plaats van handmatig WHERE-, JOIN- en LIMIT-fragmenten samen te voegen. Dit is niet alleen belangrijk voor de overzichtelijkheid. Door elke door de gebruiker aangeleverde filterwaarde via één ontsnappingslaag te leiden, wordt de eerdere mix van handmatig ontsnappen, $wpdb->prepare() en handmatig geciteerde samenvoeging geëlimineerd. Dit is precies het soort inconsistentie dat injectiefouten verbergt. Wanneer een gedegradeerde databaselaag geen query kan bouwen, zal de code nu een veilige foutmelding geven in plaats van een ongefilterde query uit te voeren.
Deze herstructureringen zijn intern en veranderen niets aan wat de schermen weergeven, maar ze vormen de basis waardoor de bovenstaande beveiligings- en betrouwbaarheidsverbeteringen klein, lokaal en verifieerbaar zijn.
Twee verborgen bugs gevonden tijdens het extraheren van de waarschuwingscode.
Door de afhandeling van beheerderswaarschuwingen te verplaatsen van CRB_Activity::log() naar een aparte CRB_Activity_Alerts -klasse kwamen twee reeds bestaande bugs aan het licht. Deze werden veroorzaakt door een positionele array waarvan de numerieke indexen niet meer correct waren uitgelijnd.
Het eerste probleem trof de dashboardlinks in waarschuwingsmails. De koppeling van een sleutel met weinig waarden zorgde voor verschuivingen in de waarden, waardoor een linkparameter zoals filter_ip het begin van een IP-bereik kon ontvangen in plaats van het beoogde adres. Door de waarden aan benoemde sleutels toe te wijzen, werd de uitlijning hersteld en verwijzen de links in waarschuwingsmails nu naar de juiste pagina.
Het tweede probleem betrof het matchen van gebruikers op basis van zoekstrings. De code die wp_get_current_user() aanriep, retourneerde een WP_User object, zelfs voor gebruikers-ID 0, waardoor de match de verkeerde identiteit gebruikte. Nu wordt de eigen gebruiker van het evenement opgezocht met crb_get_userdata() en wordt voorkomen dat een gebruiker ontbreekt. Meldingen die overeenkomen met een gebruiker, matchen nu de juiste gebruiker.
Een stiller dashboard: prioriteit voor de operator bij de controle van de wijzigingen.
CRB_Activity::is_modified_since() vergeleek een tijdstempel met behulp van $stamp < $status['data_modified'] ?? PHP_INT_MAX . Operatorprioriteit bindt dit als (... < ...) ?? PHP_INT_MAX , waardoor het samenvoegen van null-waarden dode code werd en een melding over een ongedefinieerde sleutel werd gegenereerd wanneer data_modified ontbrak. Het samenvoegen is nu tussen haakjes geplaatst, waardoor een ontbrekende wijzigingsstempel wordt behandeld als "gewijzigd", overeenkomend met de verwante is_modified() methode, en de onterechte melding is verdwenen.
Centralisatie van databaseschema-definities
Het laatste structurele onderdeel is het onderhoud van het schema. Installatie- en upgradecode gebruikte inline CREATE TABLE SQL voor de logtabellen, wat betekende dat dezelfde tabel op meer dan één plek beschreven kon worden. Deze declaraties komen nu uit één bron, CRB_Schema_Definitions , waardoor installatie en upgrade werken vanuit één canonieke definitie en CRB_Schema_Manager schema-afwijkingen in bestaande installaties consistenter kan detecteren. De ruwe DROP INDEX SQL is eveneens vervangen door CRB_Schema_Manager::drop_index_if_exists() .
We hebben het decoderen van opgeslagen request-field data ook toleranter gemaakt. Nullable legacy-waarden, lege waarden, ongeldige JSON en niet-ondersteunde geserialiseerde payloads worden nu allemaal op dezelfde veilige manier verwerkt, door te resulteren in een lege array in plaats van een onjuist opgemaakt record de leesbewerking te laten verstoren.
Waarom deze publicatie belangrijk is
Er zijn geen nieuwe knoppen in versie 9.8.3. Wat er wél is, is een datalaag die veilig faalt in plaats van stilzwijgend, notificatie-e-mails die niet kunnen worden gemanipuleerd door een speciaal geconstrueerde profielnaam, een beheerdersmelding die externe gegevens correct versleutelt, logexports die in het platte geheugen draaien en je laten weten wanneer er iets misgaat, en zoekfilters die precies doen wat het formulier aangeeft. Dit soort wijzigingen zorgen ervoor dat een beveiligingsplugin op de lange termijn betrouwbaar blijft, en we doen dit werk liever openlijk dan te doen alsof de eerdere code al perfect was.