Compromitere: 21 iulie 2026, 07:56:34–07:56:42 UTC
Campanie extinsă împotriva serverului: 18–21 iulie 2026
Sistem afectat: cerespir.ro (interfața WordPress)
Impact asupra rețelei de senzori uRADMonitor, API-ului și datelor de măsurare: niciunul
Status: izolat, curățat, actualizat, repus în functiune

Ce este cerespir.ro și ce nu este

cerespir.ro este platforma noastră publică, în limba română, pentru date de calitate a aerului și radioactivitate. Afișează harta live a senzorilor uRADMonitor instalați în România, preluând tot ce arată din API-ul uRADMonitor, exclusiv în regim de citire. Găzduiește și un blog în limba română, acesta, unde documentăm munca din spatele uRADMonitor : hardware nou, versiuni de firmware, instalări, rezultate din teren și lecții învățate.

Această arhitectură este esențială pentru a înțelege incidentul. Serverul cerespir.ro nu stochează date de la senzori, nu deține credențiale de dispozitiv, nu are chei de API cu drept de scriere și nu conține date ale clienților. Este un simplu strat de prezentare. Backend-ul uRADMonitor, flota de dispozitive, pipeline-ul de colectare a datelor și infrastructura destinată clienților rulează pe sisteme complet separate, cu credențiale separate, și nu au fost în niciun moment în perimetrul acestui eveniment.

Pe 21 iulie 2026, instalarea WordPress din spatele cerespir.ro a fost compromisă, la capătul unei campanii de patru zile împotriva serverului, purtată de cel puțin șase actori automatizați distincți. Ceea ce urmează este relatarea tehnică completă, reconstruită din logurile de acces Apache, din debug.log-ul PHP al WordPress, din baza de date WordPress și din artefactele lăsate de atacator. O publicăm integral – inclusiv părțile care nu ne avantajează – pentru că asta credem că datorează utilizatorilor o companie care construiește infrastructură de monitorizare a mediului.

Buletinul de securitate care a precedat atacul

Atacul nu a venit din senin. Cu două zile înainte de incident, pe 19 iulie 2026, Security Affairs a publicat un buletin despre o pereche de vulnerabilități critice în nucleul WordPress, denumite generic wp2shell:

  • CVE-2026-63030 — o eroare de confuzie a rutei „batch” din REST API, introdusă în WordPress 6.9
  • CVE-2026-60137 — o injecție SQL de severitate ridicată în parametrul author__not_in al WP_Query

Înlănțuite, cele două defecte permit execuție de cod la distanță fără autentificare prealabilă, pe o instalare WordPress implicită 6.9.x sau 7.0.x — echipa de cercetare care le-a descoperit a subliniat că atacul nu are precondiții și funcționează pe o instalare standard, fără niciun plugin. Lanțul complet de exploatare afectează WordPress 6.9.0–6.9.4 și 7.0.0–7.0.1 și este remediat în 7.0.2 și 6.9.5; din cauza severității, WordPress a activat actualizări automate forțate pentru versiunile afectate.

cerespir.ro rula WordPress 7.0.1. Fiecare resursă de nucleu cerută în logurile noastre în intervalul incidentului poartă amprenta versiunii:

/wordpress/wp-includes/js/dist/dom-ready.min.js?ver=7.0.1

7.0.1 este ultima versiune vulnerabilă înaintea corecției. Eram, fără niciun echivoc, în perimetrul afectat.

Exploatarea a început înainte de publicarea buletinului

Aceasta este descoperirea la care ne așteptam cel mai puțin și provine chiar din debug.log-ul WordPress.

Articolul Security Affairs a fost publicat la 05:04 UTC, pe 19 iulie 2026. Primul cont de administrator fraudulos a fost creat 55 de minute mai târziu, la 05:59:29 , deja un interval brutal între divulgare și exploatare.

Dar logul arată că suprafața de atac era testată cu o zi înainte:

[18-Jul-2026 13:48:46 UTC] PHP Notice:  Undefined offset: 1 in
  /home/cerespir/public_html/wordpress/wp-includes/rest-api/class-wp-rest-server.php on line 1836
[18-Jul-2026 13:48:46 UTC] PHP Notice:  Trying to access array offset on value of type null in
  /home/cerespir/public_html/wordpress/wp-includes/rest-api/class-wp-rest-server.php on line 1848

Aceste două notificări sunt amprenta parserului de rute „batch” din CVE-2026-63030 alimentat cu date malformate. Apar la 13:48, pe 18 iulie , cu aproximativ cincisprezece ore înaintea buletinului public. La 53 de minute după, componenta de injecție SQL începe să tragă în serverul nostru.

Deci secvența este următoarea: exploatarea privată era deja în desfășurare înainte ca analiza să fie publicată; publicarea a lărgit apoi câmpul, de la cei care aveau exploitul devreme la oricine deține un scanner. Ambele părți contează. O politică de actualizare construită pe principiul „acționez când citesc despre asta” pornește cronometrul prea târziu, iar intervalul de după publicare se măsoară în zeci de minute, nu în zile.

Cronologia intruziunii

Toate orele sunt UTC. Sursele marcate (debug.log) nu au atribuire de IP: logul de acces Apache pe care îl păstrăm începe la 19 iulie 03:44, așa că activitatea din 18 iulie este vizibilă doar în logul de erori PHP al WordPress.

OraSursăEveniment
18 iul 13:48:46(debug.log)Prima intrare malformată în parserul de rute „batch”. Cu 15 ore înainte de divulgarea publică.
18 iul 14:41:28(debug.log)112 interogări de injecție SQL oarbă într-o singură secundă, extrăgând CONCAT_WS(0x7c, user_login, user_pass) — o încercare de a extrage credențialele WordPress. Toate cele 112 încercări înregistrate returnează erori de sintaxă SQL.
19 iul 05:04Publicarea buletinului wp2shell
19 iul 05:59:21–3534.71.230.24 (Google Cloud)Rulare wp2shell #1. Admin fraudulos wpsvc_9544603bc3f4 (ID 4) creat la 05:59:29. Tentativa de autentificare eșuează pe o cale greșită.
20 iul 00:31:00–5243.249.38.40Rulare wp2shell #2. Admin fraudulos w2s_4a9c5cfc0ac5 (ID 5) creat la 00:31:53. Urmează o scanare de enumerare a pluginurilor.
20 iul 17:46:05–3491.202.233.61Rulare wp2shell #3. Admin fraudulos wpenginebot (ID 6) creat la 17:46:35.
20 iul 22:55:18(debug.log)112 interogări de injecție SQL oarbă într-o singură secundă, enumerând INFORMATION_SCHEMA.TABLES — cartografierea schemei bazei de date.
21 iul 02:42:43–5035.204.145.27 (Google Cloud)Rulare wp2shell #4 — identică octet cu octet cu rularea #1. Admin fraudulos wpsvc_c4a812fa9714 (ID 7) creat la 02:42:47.
21 iul 03:06:36, 03:50:59(debug.log)Injecție SQL bazată pe erori, prin XPATH (marcajul ~414243).
21 iul 03:42:03–082a0e:d604:1:f6::2Autentificare cu aspect interactiv pe contul wpenginebot. Încearcă încărcarea unui plugin la /wp-admin/…, primește 404 de trei ori, abandonează.
21 iul 07:35:10–07:37:29203.175.125.118Rulare wp2shell #5, 130 de cereri. Admin fraudulos JLG_90e5950a3fca (ID 8) creat la 07:37:24. Se autentifică, dar nu reușește să ajungă în panoul de administrare.
21 iul 07:56:01–07:56:29154.92.130.89 (Hong Kong)Rulare wp2shell #6. Admin fraudulos 7ef49fe74ce3 (ID 9) creat la 07:56:29.
21 iul 07:56:34–42154.92.130.89Compromitere completă. Autentificare → wp-admin → încărcare plugin → activare → execuția webshell-ului. 41 de secunde cap-coadă; 8 secunde de la autentificare la shell.
21 iul 07:56:42 → 19:21Nicio altă activitate a atacatorului, de niciun fel. Backdoor-ul nu mai este atins niciodată.
21 iul 19:2186.120.200.157 (noi)Începe răspunsul la incident.
21 iul 19:25noiPluginul malițios și webshell-ul sunt exportate pentru păstrarea probelor.
21 iul 19:46noiRotația cheilor SSH din authorized_keys.
21 iul 19:53–20:30noiBackdoor eliminat, conturi frauduloase șterse, audit de procese/socketuri/cron/passwd, întărirea securității.

Etapa 1: Exploatarea automată a endpointului REST „batch”

Fiecare dintre cele șase rulări urmează același tipar: o rafală de cereri POST către endpointul „batch” al WordPress, într-una dintre cele două forme adresabile.

203.175.125.118 - - [21/Jul/2026:07:35:12 +0000] "POST /?rest_route=/batch/v1 HTTP/1.1" 207 1017 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Chrome/126.0 Safari/537.36"
203.175.125.118 - - [21/Jul/2026:07:35:12 +0000] "POST /?rest_route=/batch/v1 HTTP/1.1" 207 1017 "-" "..."
203.175.125.118 - - [21/Jul/2026:07:35:13 +0000] "POST /?rest_route=/batch/v1 HTTP/1.1" 207 1017 "-" "..."

HTTP 207 Multi-Status este codul normal de răspuns al endpointului „batch”, și tocmai de aceea acest trafic este atât de ușor de ratat: nimic aici nu este o eroare. Nu există 4xx, nu există 5xx, nu există autentificări eșuate. Pentru o revizuire clasică a logurilor sau pentru fail2ban arată ca o aplicație care vorbește cu ea însăși.

În întregul log am numărat peste 1.300 de cereri către /batch/v1, de la peste 40 de adrese IP distincte, în trei zile. Cea mai activă sursă, 62.60.130.128, a trimis 843 de cereri în 36 de ore, folosind o variantă de confuzie de cale (POST /wp-includes/id3/license.txt/?rest_route=/batch/v1) și nu a reușit niciodată. A fost exploatare de masă, la scara internetului, nediscriminatorie și oportunistă. cerespir.ro nu a fost o țintă; a fost un rezultat de scanare.

Amprenta dimensiunii răspunsului

Cel mai util artefact criminalistic din tot acest incident este coloana dimensiunii răspunsului. Cererile exploitului produc o bandă foarte strânsă de dimensiuni în faza de sondare: și apoi un răspuns vizibil diferit exact în momentul în care se creează un cont privilegiat.

154.92.130.89, adnotat:

07:56:01  POST /?rest_route=/batch/v1   207     619   ← handshake / sondare capabilități
07:56:02  POST /?rest_route=/batch/v1   207 1767759   ← etapa SQLi, ieșire de eroare voluminoasă
07:56:05  POST /?rest_route=/batch/v1   207 1767789
07:56:09  GET  /?rest_route=%2Fwp%2Fv2%2Fposts&per_page=1&_fields=link
                                        200     112   ← află URL-ul real al sitului
07:56:09  POST /?rest_route=/batch/v1   207    6319
07:56:12  POST /?rest_route=/batch/v1   207 1767977
07:56:16  POST /?rest_route=/batch/v1   207 1768026
07:56:19  POST /?rest_route=/batch/v1   207 1767973
07:56:22  POST /?rest_route=/batch/v1   207 1767973
07:56:26  POST /?rest_route=/batch/v1   207 1767973
07:56:29  POST /?rest_route=/batch/v1   207    3337   ← ★ CONT DE ADMIN CREAT
07:56:30  POST /?rest_route=/batch/v1   207 1767879
07:56:33  GET  /?rest_route=/           200  192629   ← enumerarea endpointurilor

Răspunsurile de ~1,77 MB reprezintă etapa de injecție care returnează un payload voluminos. Răspunsul de 3.337 de octeți de la 07:56:29 este excepția iar baza de date WordPress confirmă că utilizatorul cu ID 9 a fost creat exact la 07:56:29.

Semnătura se păstrează la fiecare rulare reușită:

RulareIPDimensiune răspuns „succes”Cont creatMarcaj temporal în BD
#134.71.230.243391wpsvc_9544603bc3f419 iul 05:59:29
#243.249.38.403370w2s_4a9c5cfc0ac520 iul 00:31:53
#391.202.233.613339wpenginebot20 iul 17:46:35
#435.204.145.273391wpsvc_c4a812fa971421 iul 02:42:47
#5203.175.125.1183371JLG_90e5950a3fca21 iul 07:37:24
#6154.92.130.8933377ef49fe74ce321 iul 07:56:29

Un răspuns 207 în banda 3,3–3,4 KB de la /batch/v1 este un indicator sigur că s-a creat un cont. Dacă administrați un WordPress care a rulat o versiune afectată, acesta este singurul grep care merită rulat pe logurile arhivate.

De remarcat și că rulările #1 și #4 au produs o secvență de dimensiuni de răspuns identică octet cu octet (619, 2879, 3043, 3008, 3116, 817, 3063, 3063, 3063, 3391), de la două IP-uri Google Cloud diferite, la 44 de ore distanță. Același instrument, aceeași listă, același payload , un serviciu de scanare găzduit, care parcurge o listă de ținte.

Ambele CVE-uri, prinse în propriul nostru log de erori

Pe acest sit, WordPress avea WP_DEBUG_LOG activat , accidentul unei sesiuni de depanare de demult, pe care nimeni nu a dezactivat-o. S-a dovedit a fi cel mai valoros artefact criminalistic pe care îl aveam, pentru că a surprins lanțul de exploatare executându-se, linie cu linie.

Zgomotul de fond al acestui log este de 1–14 intrări pe zi. Apoi, dintr-o dată, intrările explodează:

DataIntrări
17 iul 20261
18 iul 2026128
19 iul 20262.597
20 iul 20261.049
21 iul 2026655

Atacul CVE-2026-63030: confuzia rutei „batch”

Iată urma de stivă înregistrată de WordPress exact în secunda în care a fost creat ultimul administrator fraudulos. De observat finalul ei:

[21-Jul-2026 07:56:29 UTC] WordPress database error You have an error in your SQL syntax ...
  for query SELECT SQL_CALC_FOUND_ROWS wp_posts.*
    FROM wp_posts
    WHERE 1=1  AND wp_posts.post_author NOT IN (1) AND 1=0 UNION ALL SELECT 0,1,0x3230...
  made by require('wp-blog-header.php'), wp, WP->main, WP->parse_request,
  do_action_ref_array('parse_request'), WP_Hook->do_action, WP_Hook->apply_filters,
  rest_api_loaded, WP_REST_Server->serve_request, WP_REST_Server->dispatch,
  WP_REST_Server->respond_to_request, WP_REST_Server->serve_batch_request_v1,
  WP_REST_Server->respond_to_request, WP_REST_Server->serve_batch_request_v1,

serve_batch_request_v1 apare de două ori. Endpointul „batch” s-a dispecerizat în el însuși: o cerere „batch” imbricată. Această recursie este defectul de confuzie: la trecerea interioară, cererea nu mai este evaluată în contextul de securitate stabilit de trecerea exterioară, iar verificarea de permisiuni care ar fi trebuit să respingă un utilizator anonim ce creează un administrator pur și simplu nu se mai execută.

Am numărat 233 de astfel de urme de dispecerizare imbricată în cele trei zile.

Și avaria de parser asociată este înregistrată:

[21-Jul-2026 07:56:29 UTC] PHP Notice:  Undefined offset: 4 in
  .../wp-includes/rest-api/class-wp-rest-server.php on line 1836
[21-Jul-2026 07:56:29 UTC] PHP Notice:  Trying to access array offset on value of type null in
  .../wp-includes/rest-api/class-wp-rest-server.php on line 1848

Undefined offset: N la class-wp-rest-server.php:1836, însoțit de notificarea de offset null de la linia 1848, este cea mai curată semnătură unică de detecție pentru tentativele de exploatare a CVE-2026-63030. Doar pe 21 iulie am înregistrat 287 de instanțe cu offset 2, 139 cu offset 3 și 3 cu offset 4 și continuă la intervale de aproximativ o jumătate de oră mult după încheierea incidentului nostru, pe măsură ce alte scannere își continuă baleiajul.

Atacul CVE-2026-60137: injecția SQL prin author__not_in

Interogarea injectată de mai sus arată al doilea CVE, în aceeași cerere:

WHERE 1=1 AND wp_posts.post_author NOT IN (1) AND 1=0 UNION ALL SELECT 0,1,0x3230...

Clauza post_author NOT IN (...) este generată din parametrul author__not_in al WP_Query. Atacatorul a închis lista și a adăugat AND 1=0 UNION ALL SELECT ..., cu toate literalele codificate hexazecimal, ca să supraviețuiască escapării. Decodat, payloadul falsifică o înregistrare de articol:

0x323032302d30312d30312030303a30303a3030  →  "2020-01-01 00:00:00"
0x5b656d6265642077696474683d...           →  '[embed width="500" height="750"]https://www.cerespir.ro/…'

Fabrică un articol al cărui conținut este un shortcode forțând WordPress să treacă înregistrarea falsificată prin mecanismul de procesare a conținutului încorporat. Aceasta este puntea de la „îți pot citi baza de date” la „pot face serverul tău să execute lucruri”.

În campanie apar trei tehnici distincte de injecție:

TehnicăCândVolumȚintă
Blind boolean (ASCII(SUBSTRING(...)))18 iul 14:41:28112 interogări în 1 secundăuser_login, user_pass
Blind boolean (ASCII(SUBSTRING(...)))20 iul 22:55:18112 interogări în 1 secundăINFORMATION_SCHEMA.TABLES
Bazată pe erori, prin XPATH (~414243)21 iul 03:06:36, 03:50:592divulgare versiune/configurație
Falsificare de rând prin UNION ALL SELECTla fiecare creare de contcâte 1puntea către RCE

Endpointul „batch” este un amplificator

Să vedem ce înseamnă „112 interogări într-o singură secundă”. Nu sunt 112 cereri HTTP. Este o singură cerere HTTP care transportă circa 112 subcereri grupate, fiecare fiind o sondă de injecție separată.

Acest singur fapt explică trei lucruri deodată: de ce logul nostru de acces arată atât de puține cereri pentru atâta activitate; de ce răspunsurile „batch” reușite au 1,7 MB, în timp ce eșecurile au ~1 KB; și de ce limitarea ratei după numărul de cereri este inutilă împotriva acestui exploit. O linie de log, un POST, peste o sută de interogări în baza de date. Un WAF care numără cereri pe secundă nu vede nimic demn de semnalat.

Etapa 2: Șase administratori frauduloși și ce ne spun ei

Exportul din baza de date face faza de semănare a conturilor lipsită de ambiguitate:

ID  user_login             user_email                                     user_registered
1   cerespir               ***@***                                        2020-06-27 07:44:42   ← legitim
2   Tiberiu                ***@***                                        2021-10-19 18:16:07   ← legitim
3   Marius                 ***@***                                        2021-11-11 11:41:35   ← legitim
4   wpsvc_9544603bc3f4     wpsvc_9544603bc3f4@wordpress-svc.internal      2026-07-19 05:59:29   ← fraudulos
5   w2s_4a9c5cfc0ac5       w2s_4a9c5cfc0ac5@shellcode.lol                 2026-07-20 00:31:53   ← fraudulos
6   wpenginebot            wpenginebot@wpengine.com                       2026-07-20 17:46:35   ← fraudulos
7   wpsvc_c4a812fa9714     wpsvc_c4a812fa9714@wordpress-svc.internal      2026-07-21 02:42:47   ← fraudulos
8   JLG_90e5950a3fca       JLG_90e5950a3fca@wp2shell.local                2026-07-21 07:37:24   ← fraudulos
9   7ef49fe74ce3           7ef49fe74ce3@google.com                        2026-07-21 07:56:29   ← fraudulos

Toate șase aveau capabilități complete de administrator:

wp_capabilities = a:1:{s:13:"administrator";b:1;}
wp_user_level   = 10

Nu este ceva ce WordPress ar face vreodată de la sine. Înregistrarea pe sit era închisă, iar rolul implicit era subscriber, deci niciun flux normal de înregistrare nu putea produce aceste conturi.

Iar WordPress a înregistrat fiecare creare, în momentul în care s-a produs:

[19-Jul-2026 05:59:29 UTC] PHP Warning:  wp_insert_user(): The user_pass field is required when creating a new user. ...
[20-Jul-2026 00:31:53 UTC] PHP Warning:  wp_insert_user(): The user_pass field is required when creating a new user. ...
[20-Jul-2026 17:46:34 UTC] PHP Warning:  wp_insert_user(): The user_pass field is required when creating a new user. ...
[21-Jul-2026 02:42:47 UTC] PHP Warning:  wp_insert_user(): The user_pass field is required when creating a new user. ...
[21-Jul-2026 07:37:24 UTC] PHP Warning:  wp_insert_user(): The user_pass field is required when creating a new user. ...
[21-Jul-2026 07:56:29 UTC] PHP Warning:  wp_insert_user(): The user_pass field is required when creating a new user. ...

Șase avertismente. Șase conturi. Marcaje temporale care corespund bazei de date la secundă și anomaliei de 3,3 KB din logul de acces, tot la secundă. Trei surse independente, dar cu un singur scop.

wp_insert_user() declanșat pe un site cu înregistrarea dezactivată este, în sine, o alertă de compromitere. Dacă rețineți o singură regulă de detecție din acest raport, rețineți-o pe aceasta: nu costă nimic, nu are fals-pozitive pe un sit cu înregistrare închisă și ne-ar fi spus pe 19 iulie ceea ce am aflat abia pe 21.

Există un aspect ridicat de aceste avertismente pe care nu îl putem închide complet. Exploitul creează fiecare cont fără parolă . Și totuși, pentru trei dintre aceste conturi s-au stabilit sesiuni autentificate valide în câteva secunde. Prin urmare, kitul trebuie să seteze credențialele printr-un apel ulterior pe care WordPress nu îl înregistrează; vedem cererea în logul de acces, dar nu și conținutul ei.

Domeniile de e-mail sunt indiciul evident: wordpress-svc.internal, shellcode.lol, wp2shell.local. Doar wpenginebot@wpengine.com face vreun efort să pară plauzibil și este contul folosit cel mai mult. Sunt identități generate de un kit de exploatare, nu munca cuiva care tastează.

Care conturi frauduloase au fost efectiv folosite

WordPress stochează sesiunile active în wp_usermeta.session_tokens, și aici ne uitam la baza de date care contine detaliile importante: Trei dintre cele șase conturi nu au niciun token de sesiune , conturile 4, 5 și 7 au fost create și niciodată folosite pentru autentificare. Erau inventar: un operator care seamănă accese pentru a le vinde sau folosi ulterior.

Trei au fost folosite:

utilizator 6 (wpenginebot)
  ip    2a0e:d604:1:f6::2
  ua    Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ... Chrome/146.0.0.0
  login 1784605324  → 2026-07-21 03:42:04 UTC

utilizator 8 (JLG_90e5950a3fca)
  ip    203.175.125.118
  ua    Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Chrome/126.0
  login 1784619447  → 2026-07-21 07:37:27 UTC   (și un al doilea token la 07:37:29)

utilizator 9 (7ef49fe74ce3)
  ip    154.92.130.89
  ua    Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ... Chrome/137.0.0.0
  login 1784620595  → 2026-07-21 07:56:35 UTC

Vedem neconcordanța de la utilizatorul 6: contul a fost creat pe 20 iulie, la 17:46, de la 91.202.233.61, dar autentificarea s-a produs nouă ore mai târziu, dintr-o rețea IPv6 complet diferită, cu altă amprentă de browser. Fie același operator care își rotește infrastructura, fie mai probabil având în vedere tiparul : un acces creat de o parte și consumat de alta.

Ce ne-a salvat de trei ori

cerespir.ro rulează WordPress într-un subdirector (/wordpress/), nu în rădăcina documentelor. Trei dintre actori aveau căile către rădăcină scrise fix în cod și pur și simplu s-au blocat:

2a0e:d604:1:f6::2 - - [21/Jul/2026:03:42:07 +0000] "GET /wp-admin/plugin-install.php HTTP/1.1" 404 57953
2a0e:d604:1:f6::2 - - [21/Jul/2026:03:42:07 +0000] "GET /wp-admin/plugin-install.php HTTP/1.1" 404 57953
2a0e:d604:1:f6::2 - - [21/Jul/2026:03:42:08 +0000] "GET /wp-admin/plugin-install.php HTTP/1.1" 404 57953

Trei erori 404 și sesiunea se încheie. Aceeași poveste pentru 203.175.125.118, care s-a autentificat cu succes la 07:37:27 și apoi s-a aruncat asupra POST /wp-admin/ și GET /wp-admin/users.php, colecționând coduri 302 și un 404 înainte de a renunța:

203.175.125.118 - - [21/Jul/2026:07:37:27 +0000] "POST /wp-admin/ HTTP/1.1" 302 -
203.175.125.118 - - [21/Jul/2026:07:37:29 +0000] "GET /wp-admin/users.php HTTP/1.1" 404 57949

Instrumentul care a reușit a fost singurul care a rezolvat mai întâi calea reală, folosind endpointul REST de articole pentru a citi URL-ul canonic al sitului, la 07:56:09, înainte de a comuta toate cererile ulterioare pe /wordpress/….

Merită tinut minte: o cale de instalare non-implicită nu este o măsură de securitate și nu o prezentăm ca atare. Dar împotriva automatizării de serie este un obstacol real și repetat eficient. Trei din șase atacatori au fost opriți de numele unui director.

Etapa 3: Compromiterea de 41 de secunde

La 07:56:34, un instrument a nimerit totul corect. Iată întreaga breșă, cuvânt cu cuvânt:

154.92.130.89 - - [21/Jul/2026:07:56:34 +0000] "GET /wordpress/wp-login.php HTTP/1.1" 200 6932 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
154.92.130.89 - - [21/Jul/2026:07:56:35 +0000] "POST /wordpress/wp-login.php HTTP/1.1" 302 - "-" "..."
154.92.130.89 - - [21/Jul/2026:07:56:36 +0000] "GET /wordpress/wp-admin/ HTTP/1.1" 200 120060 "-" "..."
154.92.130.89 - - [21/Jul/2026:07:56:37 +0000] "GET /wordpress/wp-admin/plugin-install.php?tab=upload HTTP/1.1" 200 66256 "-" "..."
154.92.130.89 - - [21/Jul/2026:07:56:38 +0000] "POST /wordpress/wp-admin/update.php?action=upload-plugin HTTP/1.1" 200 59018 "-" "..."
154.92.130.89 - - [21/Jul/2026:07:56:40 +0000] "GET /wordpress/wp-admin/plugins.php?action=activate&plugin=wp2p_265bd548%2Fwp2p_265bd548.php&_wpnonce=78797da9f3 HTTP/1.1" 302 - "-" "..."
154.92.130.89 - - [21/Jul/2026:07:56:41 +0000] "GET /wordpress/wp-admin/plugins.php?activate=true&plugin_status=all&paged=1&s= HTTP/1.1" 200 101936 "-" "..."
154.92.130.89 - - [21/Jul/2026:07:56:42 +0000] "GET /wordpress/wp-content/plugins/wp2p_265bd548/4b0958118c8f.php HTTP/1.1" 200 1862 "-" "..."

De la formularul de autentificare la webshell în execuție: opt secunde. Sesiunea totală, incluzând faza de exploatare: 41 de secunde, 21 de cereri.

Aceasta este piesa vizuală centrală a articolului.

Om sau script? Cronometrajul spune script, fără echivoc

Una dintre ipotezele noastre inițiale și cea pe care secvența brută din log o susține superficial a fost că avem de-a face cu o persoană care conduce un browser, pentru că ordinea cererilor este exact ce ar produce un administrator care dă clic prin wp-admin. La o citire atentă, este greșit. Șase linii de dovezi independente arată un client HTTP neinteractiv:

1. Numai documente HTML. În toate cele 21 de cereri de la 154.92.130.89: zero CSS, zero JavaScript, zero imagini, zero favicon.ico, zero admin-ajax.php, zero POST-uri de heartbeat. O sesiune Chrome reală care încarcă /wp-admin/ declanșează zeci de cereri de subresurse și apoi menține un heartbeat la fiecare 15–60 de secunde. Nu există niciuna.

2. Toate referrer-ele sunt goale. Toate cele 21 de cereri înregistrează "-" în câmpul referrer. Un browser care navighează de la plugin-install.php la update.php trimite un Referer. Un POST scriptat nu.

3. Încărcarea se produce la o secundă după formular. La 07:56:37 se cere formularul de încărcare; la 07:56:38 se trimite arhiva ZIP. Un om trebuie să deschidă un selector de fișiere, să navigheze prin sistemul de fișiere, să selecteze un fișier și să dea clic. Pentru nimeni asta nu este o operație de o secundă.

4. Nonce-ul a fost parsat și reutilizat în două secunde. URL-ul de activare conține _wpnonce=78797da9f3, o valoare care există doar după randarea răspunsului la încărcare. A fost extrasă din HTML și rejucată în 2 secunde. Asta e o expresie regulată, nu un mouse.

5. Cadență uniformă, de mașină. Faza de exploatare rulează metronomic, la 3–4 secunde pe cerere; faza de după autentificare, la exact o secundă pe pas. Interacțiunea umană este neregulată, în rafale. Aceasta este un sleep() într-o buclă.

6. Rulările eșuate expun mecanismul. 203.175.125.118 a trimis POST /wp-login.php de două ori, la o secundă distanță, apoi POST /wp-admin/ — unde un browser ar fi emis un GET. Este o listă de cereri scrisă fix în cod, fără gestionarea redirecționărilor și fără logică de sesiune. Cele două tokenuri de sesiune ale sale, create la două secunde distanță (07:37:27 și 07:37:29), sunt același script care se autentifică de două ori, pentru că nu a observat că era deja autentificat.

Prin urmare, user-agentul Chrome/137.0.0.0 pe macOS este falsificat, și falsificat neglijent : celelalte rulări ale aceluiași operator pretind Chrome 126, 131 și 146. Concluzie: complet automatizat, cap-coadă, fără niciun om în buclă, în niciun moment. Nimeni nu ne-a citit blogul, nimeni nu s-a uitat la harta senzorilor, nimeni nu a căutat nimic anume. Un kit de exploatare a găsit un șir de versiune care i-a plăcut și a executat un script fix.

Etapa 4: Payloadul: wp2p_265bd548 și semnătura „chinafans”

Pluginul încărcat este un pachet de două fișiere. Nucleul pluginului are patru linii funcționale:

<?php
/*
Plugin Name: WP2P
Description: temp
Version: 1.0
*/
@file_put_contents(ABSPATH.'0x.txt','chinafans');

Acesta este întregul plugin. La activare, depune un fișier numit 0x.txt în rădăcina WordPress, conținând un singur șir: chinafans.

De ce contează marcajul

Acest fișier nu are absolut nicio funcție ofensivă. Nu îl ajută pe atacator să facă nimic sitului. Scopul lui este inventarul: odată ce https://<victimă>/0x.txt returnează chinafans, operatorul poate confirma o compromitere reușită printr-un singur GET neautentificat și mai important poate redescoperi situl mai târziu, la scară, interogând servicii de scanare la nivelul întregului internet după acel șir. Este un steag înfipt pe teritoriu cucerit, vizibil de la distanță.

Același șir este afișat de webshell la fiecare încărcare de pagină. chinafans este cartea de vizită a campaniei și este cea mai puternică legătură dintre artefactele de pe serverul nostru și valul mai larg de exploatare wp2shell. Este o semnătură de campanie, nu o atribuire. Ne spune ce kit a fost folosit. Nu ne spune nimic sigur despre cine l-a folosit , șirul este scris fix într-un instrument pe care oricine îl poate descărca și rula, iar cele șase IP-uri sursă observate se întind pe Google Cloud (SUA și Olanda), Hong Kong și mai multe intervale de găzduire „bulletproof”.

Pentru oricine își auditează propria instalare WordPress după wp2shell, aceasta este cea mai rapidă verificare posibilă:

ls -la /cale/catre/wordpress/0x.txt
grep -rl 'chinafans' /cale/catre/wordpress/

Analiza sumară a webshell-ului

Al doilea fișier, 4b0958118c8f.php (4.894 de octeți), este un backdoor de tip manager de fișiere prin browser. Îi descriem capabilitățile în loc să îi publicăm sursa; fișierul a fost păstrat intact ca probă.

Comportament:

  • Anonim. Fără parolă, fără token, fără listă albă de IP-uri, fără ofuscare. Oricine îi știe URL-ul îl poate folosi integral. Acesta este aspectul pe care l-am considerat cel mai grav , backdoor-ul era la fel de disponibil terților ca și operatorului care l-a instalat.
  • Navigare completă în sistemul de fișiere, oriunde poate citi utilizatorul apache, printr-un parametru ?dir= transmis direct către realpath(). Nu este limitat la rădăcina web.
  • Citire, scriere, creare, redenumire și ștergere pe orice fișier sau director accesibil utilizatorului serverului web, inclusiv un editor în browser care suprascrie conținutul fișierelor dintr-un corp POST.
  • Încărcare arbitrară de fișiere în orice director cu drept de scriere , mecanismul pentru pregătirea unui payload de etapa a doua.
  • Recunoașterea permisiunilor: colorează fiecare intrare listată în verde/roșu/alb, după drepturile de citire și scriere, astfel încât operatorul vede dintr-o privire care directoare sunt puncte viabile de depunere.

Indicii criminalistice:

  • Fișierul începe cu octeții literali GIF89A; înaintea blocului PHP, un prefix de „numere magice” folosit pentru a păcăli filtrele naive de încărcare și scannerele care clasifică după antet. Aici este inofensiv, pentru că a sosit în interiorul unei arhive ZIP de plugin, dar confirmă că fișierul este o piesă de serie dintr-o colecție de shell-uri, nu ceva scris pentru această țintă.
  • echo "chinafans" la fiecare cerere , același marcaj de campanie.
  • Conține o eroare reală: funcția de redenumire referențiază o constantă inexistentă, DIRECTORY_SEPARATO (fără R), care ar arunca o excepție la utilizare. Este cod de serie, copiat, testat minimal.

Ce nu este: nu există nicio primitivă de execuție de comenzi (system, exec, passthru, shell_exec, proc_open, backtick-uri), niciun cod de reverse shell, nicio logică de escaladare a privilegiilor, niciun mecanism de persistență în afara propriei existențe ca plugin activ și niciun apel de rețea. Este un manager de fișiere. Un manager de fișiere cu drept complet de scriere într-o rădăcină web rămâne o compromitere totală a acelei aplicații dar are o rază de acțiune semnificativ mai mică decât un shell de comenzi și este consecvent cu tot ce am găsit pe server.

Ce nu a făcut atacatorul

Această secțiune există pentru că rapoartele de incident care listează doar veștile proaste nu sunt rapoarte oneste.

Webshell-ul a fost accesat exact o dată, la 07:56:42, returnând 1.862 de octeți , dimensiunea unei listări de director gol. Nu a mai fost cerut niciodată, nici de acel IP, nici de altul, în cele 11 ore și jumătate rămase cât a existat pe disc. Am căutat în tot logul de acces după calea pluginului; singurele alte accesări sunt exporturile noastre criminalistice de la 19:25, de la IP-ul biroului.

Confirmat absent, pe bază de dovezi:

  • Tentativă de extragere a credențialelor , concluzie revizuită. Prima noastră evaluare internă a concluzionat că nu a existat exfiltrare de date. debug.log ne-a obligat să nuanțăm. Pe 18 iulie la 14:41 și pe 20 iulie la 22:55, atacatorul a rulat două serii de câte 112 interogări de injecție SQL oarbă, țintind direct CONCAT_WS(0x7c, user_login, user_pass) și INFORMATION_SCHEMA.TABLES. Toate cele 224 de încercări înregistrate au returnat o eroare de sintaxă MariaDB, deci acele sonde specifice nu au extras nimic. Dar o injecție reușită nu produce eroare și, prin urmare, nici intrare în log , vedem ce a eșuat, nu ce a funcționat. În consecință, am tratat toate cele trei hash-uri de parolă WordPress legitime ca fiind compromise și le-am rotit, și recomandăm oricui auditează un incident wp2shell să facă la fel, în loc să raționeze pe baza absenței dovezilor. Factorul atenuant este ce conțin efectiv tabelele wp_posts și wp_users de pe acest server: trei conturi de personal și conținut public de blog. Fără date de la senzori, fără credențiale de dispozitiv, fără chei de API, fără date ale clienților, fără date personale ale cititorilor.
  • Fără exfiltrare masivă de conținut. Niciun dump de bază de date, nicio creare de arhive, niciun transfer voluminos spre exterior, nicio citire în masă de conținut.
  • Fără modificarea conținutului. Articolele, paginile, fișierele temei și încărcările sunt toate intacte și nemodificate. Fără defacement, fără spam SEO, fără redirecționări injectate, fără criptominer.
  • Fără compromitere la nivel de sistem de operare. /etc/passwd arată exact un cont non-sistem (cerespir) — niciun cont adăugat. Listarea completă a proceselor nu arată nici minere, nici procese pornite din /tmp, /var/tmp sau /dev/shm, niciun interpretor neexplicat, niciun reverse shell. Socketurile în ascultare sunt Apache, Dovecot, Postfix, Webmin și SSH (pe un port non-standard), toate așteptate. Cron este nemodificat: /etc/cron.d, cron.daily, cron.hourly, cron.weekly, cron.monthly și crontab-ul utilizatorului conțin doar intrările prezente acolo din 2019–2021.
  • Nicio cheie SSH neautorizată. Am vehiculat posibilitatea unei chei instalate de atacator. Nu a fost cazul.
  • Atacatorul nu a atins SSH-ul. Rotația cheilor de la 19:46 a fost preventivă, nu remedierea unui punct de sprijin activ
  • Niciun impact asupra uRADMonitor. API-ul uRADMonitor, flota de dispozitive, distribuția de firmware, pipeline-ul de date și toate sistemele destinate clienților se află pe infrastructură separată, cu credențiale separate. Niciunul nu era accesibil de pe serverul compromis și niciunul nu prezintă vreo anomalie în aceeași perioadă.

Încadrarea ferestrei de expunere. Interacțiunea activă a atacatorului cu sistemul nostru a durat 41 de secunde. Fișierul backdoor a rămas apoi pe disc, neutilizat, aproximativ 11 ore și jumătate, până când l-am eliminat.

Răspunsul nostru

Răspunsul la incident a început la 19:21 UTC, pe 21 iulie. Secvența:

  1. Mai întâi păstrăm probele. Înainte de a șterge orice, pluginul malițios și webshell-ul au fost exportate intacte, iar logurile Apache brute, debug.log-ul WordPress și tabelele relevante din baza de date au fost arhivate. Extragerea în sine este în evidențe: prima încercare de a descărca directorul pluginului ca arhivă ZIP a eșuat pentru că extensia PHP ZipArchive nu este instalată pe acest server [21-Jul-2026 19:25:39 UTC] PHP Fatal error: Uncaught Error: Class 'ZipArchive' not found in .../wp-content/plugins/theme-editor/app/controller/theme_controller.php:419 corespunzând unui răspuns 500 în logul de acces, în aceeași secundă. Ambele fișiere au fost apoi exportate individual, la 19:25:46 și 19:25:55. Nu poți analiza ce ai șters deja, iar probele păstrate înainte de curățare sunt singura versiune în care oricine altcineva va avea încredere.
  2. Tăierea accesului. Toate cele șase conturi de administrator fraudulos și metadatele lor au fost șterse, invalidând sesiunile asociate. Credențialele celor trei conturi legitime au fost rotite.
  3. Eliminarea backdoor-ului. wp-content/plugins/wp2p_265bd548/ a fost șters integral, împreună cu marcajul 0x.txt; setul de pluginuri rămas a fost verificat față de sume de control cunoscute ca fiind corecte.
  4. Închiderea breșei. WordPress actualizat dincolo de ramura vulnerabilă. Accesul anonim la /wp-json/batch/v1 și ?rest_route=/batch/v1 a fost blocat la nivelul serverului web, conform recomandărilor de mitigare din avizul wp2shell.
  5. Rotația secretelor. authorized_keys SSH rotit (19:46), salt-urile și cheile WordPress regenerate, credențialele bazei de date schimbate.
  6. Verificarea serverului. Audit complet al proceselor, socketurilor în ascultare, cron-ului pentru toți utilizatorii, /etc/passwd, fișierelor modificate recent sub rădăcina web și în directoarele temporare, precum și al binarelor SUID. Curat peste tot.
  7. Reducerea suprafeței de atac. theme-editor un plugin care oferă drept de scriere în fișiere direct din panoul de administrare, instalat de noi pentru comoditate a fost eliminat. Instrumentele de conveniență care duplică funcțiile unui webshell nu au ce căuta pe un sit de producție.
  8. Detecție pentru data viitoare. Monitorizarea integrității fișierelor pe wp-content/plugins/, alertare la crearea de utilizatori WordPress, alertare la răspunsuri 207 de la /batch/v1 și blocarea IP-urilor sursă.

Indicatori de compromitere

Îi publicăm pentru ca alți administratori de WordPress să își poată verifica propriile loguri.

IP-uri sursă — creare reușită de cont

34.71.230.24        19 iul 05:59 UTC   Google Cloud (SUA)
43.249.38.40        20 iul 00:31 UTC
91.202.233.61       20 iul 17:46 UTC
35.204.145.27       21 iul 02:42 UTC   Google Cloud (Olanda)
203.175.125.118     21 iul 07:37 UTC
154.92.130.89       21 iul 07:56 UTC   ← încărcare plugin + webshell (geolocalizare Hong Kong)
2a0e:d604:1:f6::2   21 iul 03:42 UTC   ← reutilizarea sesiunii unui cont creat anterior

Fișiere

wp-content/plugins/wp2p_265bd548/wp2p_265bd548.php
wp-content/plugins/wp2p_265bd548/4b0958118c8f.php
0x.txt                                   (rădăcina WordPress, conținut: "chinafans")

Rețineți că wp2p_<8 hex> / <12 hex>.php sunt generate pentru fiecare țintă în parte. Căutați după tipar și după șirul chinafans, nu după aceste nume exacte de fișiere.

Tipare de conturi frauduloase

wpsvc_<12 hex>@wordpress-svc.internal
w2s_<12 hex>@shellcode.lol
<aleatoriu>@wp2shell.local
wpenginebot@wpengine.com
<12 hex>@google.com

Semnături în logul de acces

POST /?rest_route=/batch/v1                             → 207, 3,3–3,4 KB   ★ cont creat
POST /wp-json/batch/v1                                  → 207, 3,3–3,4 KB   ★ cont creat
POST /wp-includes/id3/license.txt/?rest_route=/batch/v1 → 207               variantă de confuzie de cale

Semnături în debug.log (cele mai fidele detecții de aici)

PHP Warning:  wp_insert_user(): The user_pass field is required when creating a new user
   → creare de cont; pe un sit cu înregistrarea închisă, aceasta este o compromitere, punct

PHP Notice:  Undefined offset: N in .../wp-includes/rest-api/class-wp-rest-server.php on line 1836
PHP Notice:  Trying to access array offset on value of type null in ... on line 1848
   → parserul de rute „batch" din CVE-2026-63030, sondat

... WP_REST_Server->serve_batch_request_v1, WP_REST_Server->respond_to_request,
    WP_REST_Server->serve_batch_request_v1,
   → dispecerizare „batch" imbricată: defectul de confuzie chiar declanșându-se

WordPress database error ... wp_posts.post_author NOT IN (1) AND 1=0 UNION ALL SELECT
WordPress database error ... ASCII(SUBSTRING(COALESCE((SELECT CONCAT_WS(0x7c, user_login, user_pass
WordPress database error XPATH syntax error: '~414243'
   → injecția CVE-2026-60137 prin author__not_in

Triaj rapid pe orice server WordPress care a rulat o versiune afectată:

grep -c 'wp_insert_user()' wp-content/debug.log
grep -c 'class-wp-rest-server.php on line 1836' wp-content/debug.log
grep -c 'user_login, user_pass' wp-content/debug.log

Interogări în baza de date

SELECT ID, user_login, user_email, user_registered
FROM wp_users ORDER BY user_registered DESC;

SELECT user_id, meta_value FROM wp_usermeta
WHERE meta_key = 'wp_capabilities' AND meta_value LIKE '%administrator%';

SELECT user_id, meta_value FROM wp_usermeta
WHERE meta_key = 'session_tokens';   -- arată care conturi frauduloase au fost folosite și de unde

SELECT * FROM wp_options WHERE option_value LIKE '%wp2p%';

Avertisment privind atribuirea. Șirul chinafans și geolocalizarea în Hong Kong a adresei 154.92.130.89 identifică un kit de instrumente, nu un actor. Geolocalizarea comercială a unui IP nu înseamnă atribuire: noduri de ieșire VPN, instanțe VPS compromise, infrastructură proxy închiriată și rețele de anonimizare stau toate între o adresă IP și o persoană. Două dintre cele șase IP-uri sursă sunt adrese Google Cloud, iar nimeni nu ar sugera că asta implică Google. Nu putem sa formulăm nicio afirmație de atribuire.

Ce reținem din toate acestea

Fereastra de actualizare se poate închide înainte să se deschidă. Exploatarea cerespir.ro a început cu aproximativ cincisprezece ore înainte de buletinul public, iar primul administrator fraudulos a fost creat la cincizeci și cinci de minute după el. Niciuna dintre cifre nu lasă loc unei bucle de decizie umană. Actualizările automate de securitate nu sunt o facilitate de confort; pe un CMS folosit la scară largă, sunt singurul control care operează pe scara de timp corectă.

Exploatarea reușită poate fi invizibilă pentru monitorizarea clasică. Fiecare cerere din faza de exploatare a returnat HTTP 207. Fără 4xx, fără 5xx, fără autentificări eșuate, nimic de văzut pentru fail2ban, nimic care să declanșeze o alertă de prag. Și pentru că endpointul „batch” împachetează peste o sută de operații într-o singură cerere, nici limitarea ratei nu vede nimic : 112 injecții SQL au sosit ca un singur POST. Dacă detecția voastră este construită pe rate de eroare, numărul de autentificări eșuate și cereri pe secundă, un atac de această formă trece nestingherit prin toate trei.

Lăsați logul de depanare activat. WP_DEBUG_LOG a fost activat pe acest sit din greșeală și uitat acolo. În mod normal este considerat o mică eroare de configurare, și ne-a oferit întregul caz: urmele imbricate de serve_batch_request_v1 care dovedesc defectul de confuzie, SQL-ul injectat în întregime, șase avertismente wp_insert_user() cu marcaj temporal la secundă și activitatea din 18 iulie, anterioară divulgării, pe care nimic altceva din setul nostru de probe nu ar fi putut-o vedea. Țineți-l activat, țineți-l în afara rădăcinii web, rotiți-l și citiți-l.

Sistemele auxiliare sunt tot sistemele voastre. cerespir.ro este un strat de prezentare fără date operaționale. Această separare arhitecturală este exact motivul pentru care incidentul a rămas mic, și a fost o decizie de proiectare deliberată. Dar un sit compromis sub domeniul nostru este un sit compromis, și îl tratăm ca atare : poate fi folosit pentru a servi malware vizitatorilor, pentru a eroda încrederea de care depinde proiectul uRADMonitor și ca punct de sprijin pentru sondarea altor sisteme. Nu există așa ceva, o componentă care „nu contează”.

Păstrați probele înainte de a curăța. Aproape tot ce are valoare în acest raport , amprenta dimensiunii răspunsului, IP-urile din tokenurile de sesiune, tiparul de semănare a celor șase conturi, retragerea concluziei despre cheia SSH provine din artefacte care ar fi fost distruse de o curățare rapidă. Instinctul sub presiune este să ștergi imediat lucrul rău. Copiază-l mai întâi.

Spuneți ce s-a întâmplat. uRADMonitor există pentru că oamenii au nevoie de date de mediu pe care le pot verifica și în care pot avea încredere. Standardul acesta nu se oprește la senzori. Când ceva merge prost în infrastructura noastră, răspunsul este o relatare tehnică completă, cu marcaje temporale, linii de log, indicatori și părțile pe care le-am greșit publicată, astfel încât alți operatori care rulează același software să își poată verifica propriile sisteme chiar în seara asta.

Urmăriți proiectul uRADMonitor pe blogul cerespir.ro sau la uradmonitor.com.