JavaScript is disabled in your browser. Please enable it for the full experience.

Hogyan írj biztonságos WordPress plugint? (XSS, CSRF, SQL Injection nélkül)

A biztonság nem valami opcionális extra, amit majd egyszer, ha ráérsz, hozzácsapsz a kódhoz. Ez ugyanolyan alap, mint hogy a programod egyáltalán elindul. Ha ezeket az – elsőre talán feleslegesnek tűnő – lépéseket kihagyod, ne csodálkozz, ha a rendszeredet hamarabb „auditálják” kívülről, mint ahogy te magad odajutsz. I. Mik azok a támadások? 1. XSS –…

⏳ 20 perc

Lőrincz András

A biztonság nem valami opcionális extra, amit majd egyszer, ha ráérsz, hozzácsapsz a kódhoz. Ez ugyanolyan alap, mint hogy a programod egyáltalán elindul. Ha ezeket az – elsőre talán feleslegesnek tűnő – lépéseket kihagyod, ne csodálkozz, ha a rendszeredet hamarabb „auditálják” kívülről, mint ahogy te magad odajutsz.

I. Mik azok a támadások?

1. XSS – Cross-Site Scripting

Az XSS (Cross-Site Scripting) olyan biztonsági rés, amikor a támadó rosszindulatú JavaScript kódot tud bejuttatni az oldaladra, amit más felhasználók böngészője automatikusan lefuttat. Ez gyakorlatilag azt jelenti, hogy a támadó távolról képes vezérelni más felhasználók böngészőjét, amikor azok az érintett oldalt meglátogatják.

Mi a célja?

  • Adatok ellopása: pl. belépési adatok, űrlapok tartalma, sütik (cookie-k), session tokenek megszerzése.
  • Felhasználók átirányítása: kártékony oldalakra.
  • Felhasználói felület manipulációja: hamis űrlapok, gombok elhelyezése, adathalászat.
  • Fiókátvétel: session-lopás után belépés a felhasználó nevében.

Hogyan történik?

A támadó bármilyen felhasználói input mezőt (komment, kereső, profilnév, űrlapmező, stb.) használhat arra, hogy rosszindulatú JavaScript kódot juttasson be.
Például, ha a hozzászólás mezőbe ezt írja:

<script>alert('Hacked!');</script>

és ez az input szűrés, escape-elés nélkül kerül ki az oldalra, akkor:

  • A hozzászólás megjelenik az oldalon.
  • Minden felhasználó böngészője lefuttatja a beillesztett kódot, amikor megnyitja az oldalt.

Eredmény:

  • Az oldalad minden látogatóját elérheti a támadás.
  • Adatlopás, UI tönkretétel, fiókátvétel, akár teljes oldal-feltörés (ha az admin is érintett).

Különböző típusai:

  • Tárolt XSS: az ártalmas kód az adatbázisba kerül (pl. kommentben), minden megjelenítéskor fut.
  • Reflektált XSS: az ártalmas kód egyből a böngészőhöz jut pl. keresési paraméterként.

Kritikus tanulság:

Soha ne jeleníts meg felhasználótól származó adatot „nyersen”! Mindig szűrj és escape-elj minden kimenetet – a megfelelő contextben!
WordPress-ben: esc_html(), esc_attr(), esc_url() stb.

2. CSRF – Cross-Site Request Forgery

CSRF: Keresztoldali kérelemhamisítás

A CSRF egy olyan támadási forma, amikor egy bejelentkezett felhasználó nevében indít a támadó háttérben egy kérést (pl. űrlap beküldése, beállítás módosítása) a felhasználó tudta nélkül, kihasználva azt, hogy a böngésző automatikusan elküldi a hitelesítő sütiket minden kéréshez.

Hogyan néz ki a támadás?

Például: Egy adminisztrátor be van jelentkezve a WordPress admin felületére.

Közben megnyit egy támadó által készített oldalt, ahol egy rejtett űrlap vagy egy automatikus JavaScript van elhelyezve:

<form action="https://a-te-oldalad.hu/wp-admin/options-general.php" method="POST">
  <input type="hidden" name="blogname" value="Hacked by attacker">
</form>
<script>document.forms[0].submit();</script>

A böngésző elküldi a WordPress-hez a session azonosítókat (cookie), hiszen a felhasználó be van jelentkezve.

Ha az adott művelet nincs védve (nincs nonce), a WordPress elfogadja a változtatást.

Eredmény:

A támadó a felhasználó nevében végrehajtott valamilyen műveletet: beállítást módosított, jelszót cserélt, akár új admin fiókot hozott létre.

Miért működik?

A böngésző automatikusan minden, a saját domainre irányuló kéréshez csatolja a session cookie-t, függetlenül attól, hogy azt a felhasználó vagy egy másik weboldal kódja indította.

Védekezés:

  • Minden állapotot módosító műveletnél (pl. POST, fontosabb GET kéréseknél) használj nonce-ot!
  • Mindig ellenőrizd a nonce-ot a feldolgozó oldalon (check_admin_referer() vagy check_ajax_referer() WordPress-ben).
  • Soha ne bízz abban, hogy „csak admin” éri el a funkciót – a támadás akkor is működik, ha egy admin be van jelentkezve és máshol böngészik.

Kritikus tanulság:

Ne hagyj soha felügyelet nélkül módosító űrlapot vagy AJAX műveletet nonce nélkül! A WordPress natív nonce funkcióit kötelező használni.

Gyakorlati példa:

Ha megnézed a WordPress adminban például a Bejegyzések listáját, és az egérrel ráállsz a Lomtárba helyezés linkre, akkor az oldal alján (vagy a böngésző státusz sorában) jól láthatóvá válik az URL.

Ebben az URL-ben megtalálod a következő GET paramétert:

_wpnonce=0657ae1ff

Ez a nonce – egyedi, időhöz kötött biztonsági token –, amit a WordPress automatikusan generál és ellenőriz minden érzékeny műveletnél, például törlésnél, módosításnál, jelszóváltásnál stb.

A WordPress így védi az admin funkciókat a CSRF támadásoktól. Ha a kérés nem tartalmaz érvényes _wpnonce értéket, a rendszer megtagadja a műveletet – függetlenül attól, hogy a session cookie egyébként érvényes.

WordPress doksi:
https://developer.wordpress.org/apis/security/nonces/

3. SQL Injection (SQL befecskendezés)

Az SQL Injection akkor fordul elő, amikor a támadó olyan adatot ad be (pl. űrlap, URL, cookie, HTTP header, API hívás stb.), amelyet a szerver nyersen illeszt be egy SQL lekérdezésbe.
Ha nincs szűrés vagy paraméterezés, a támadó saját SQL parancsokat fűzhet a lekérdezéshez, így például kilistázhat minden adatot, törölhet táblákat, vagy admin hozzáférést szerezhet.

Példa támadásra:

A) Felhasználói input.

Egy keresőmezőbe ezt írja a támadó:

' OR 1=1;--

B) Backend feldolgozás (helytelenül!):

$query = "SELECT * FROM users WHERE name = '$input'";

Ha $input tartalma a fenti, az SQL így néz ki:

SELECT * FROM users WHERE name = '' OR 1=1;--'

A -- után mindent kommentnek vesz az SQL, így minden felhasználó rekordját visszaadja.

C) Súlyosabb esetek:

A támadó akár ilyen inputot is megadhat:

'; DROP TABLE users;--

Ez törli a teljes users táblát, ha a backend engedi!

Milyen adatokat használhat fel a támadó?

  • HTTP fejléceks users táblát, ha a backend engedi!
  • Űrlapok (regisztráció, kereső, bejelentkezés)
  • URL paraméterek (pl. ?id=)
  • Sütik (cookie)

Védekezés SQL-injection ellen

Soha ne fűzz be bemeneti adatot közvetlenül SQL lekérdezésbe!
Mindig használj paraméterezett lekérdezést (prepared statement):
WordPress-ben a $wpdb->prepare() az alapértelmezett módszer.

global $wpdb;
$name = $_GET['name'];
// Mindig szűrj, majd paraméterezett lekérdezést használj!
$sql = $wpdb->prepare("SELECT * FROM users WHERE name = %s", $name);
$results = $wpdb->get_results($sql);

Ebben a példában a %s helyére a WordPress biztonságosan illeszti be a bemeneti adatot, így a támadó nem tud „kiszökni” a lekérdezésből.

Eredmény:

SQL injection esetén a támadó:

  • Kilistázhat minden adatot
  • Törölhet, módosíthat táblákat
  • Adminisztrátori jogosultságot szerezhet
  • Teljes adatvesztést okozhat

Kritikus tanulság:

Minden felhasználói adatot, amit SQL-be küldesz, előbb szűrj, validálj, majd kizárólag paraméterezett lekérdezéssel dolgozz!

II. A WordPress hivatalos ajánlása az adatkezelésre

Három kulcsszó:

Escape: Kimenet előtt az adott kontextushoz igazítod az adatot, hogy böngésző, adatbázis, attribútum, URL stb. ne értelmezze veszélyesnek.

Validate: Megnézed, hogy a bemenet megfelel-e annak, amit elvársz (pl. szám, email, url stb.).

Sanitize: Eltávolítod vagy lecseréled a nem megengedett karaktereket a bemenetből.

Minden bemenetnél (GET/POST/Cookie/Request) követendő sorrend:

  1. Validáld!
  2. Sanitizáld!
  3. Csak ezután dolgozd fel/adatbázisba vagy kimenetre tedd!
  4. Kimenetnél mindig escape-elj! (echo, print, stb.)!

WordPress nem véletlenül ad rengeteg beépített függvényt:

esc_html(), esc_attr(), esc_url(), esc_js() – kimenetnél, minden kontextushoz külön!

sanitize_text_field() # szöveg (HTML nélkül)

sanitize_email() # csak érvényes emailt enged át

sanitize_url() vagy esc_url_raw()

sanitize_key() # azonosítókra

/* kimenetnél, MINDEN kontextushoz külön!*/
esc_html(), esc_attr(), esc_url(), esc_js() 

Gyakori hibák – Mit NE csinálj?

Rossz példa:

// Hibás! Nyersen ír ki, SQL injection is!
$name = $_POST['name'];
$wpdb->query("INSERT INTO users (name) VALUES ('$name')");
echo "<div>Név: $name</div>";

Mi a baj?

  • Nincs CSRF védelem
  • Nincs validálás, se szűrés!
  • SQL injection védelem nulla (egy ‘ vagy ; vége a táblának)
  • Nincs escape: XSS sebezhetőség

Helyes:

// Biztonságos verzió!
$name = isset($_POST['name']) ? sanitize_text_field($_POST['name']) : '';
if (!empty($name)) {
    $wpdb->query($wpdb->prepare("INSERT INTO users (name) VALUES (%s)", $name));
    echo '<div>Név: ' . esc_html($name) . '</div>';
}

Miért jó?

  • (CSRF védelem még nem látszik – erről lentebb!)
  • Bemenetet sanitize-olod
  • SQL paraméterezés (prepare)
  • Kimenet escape

Példa: egy teljes, biztonságos WordPress admin űrlap

Lépésenként, kommentelve, magyarázattal!

<?php
/**
 * Plugin Name:     Biztonságos Felhasználó Hozzáadás
 * Description:     Minden alap WordPress biztonsági lépés, érthetően, példákkal.
 */

// 1. Menü csak adminnak, hogy ne a "Gyula felhasználó" próbálkozzon...
add_action('admin_menu', function() {
    add_menu_page(
        'Biztonságos űrlap',
        'Biztonságos űrlap',
        'manage_options', // csak admin
        'bizt_forma',
        'bizt_forma_render'
    );
});

function bizt_forma_render() {
    // A hibaüzeneteket itt gyűjtjük
    $hibak = array();

    // 2. POST feldolgozás – csak ha elküldték az űrlapot!
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        // 2a. Jogosultság-ellenőrzés – bárhogy lehet próbálkozni, de csak admin nyerhet!
        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('Nincs jogod hozzá!', 'biztforma'));
        }

        // 2b. Nonce ellenőrzés – ez blokkolja a CSRF támadást
        if (!isset($_POST['biztforma_nonce']) || !wp_verify_nonce($_POST['biztforma_nonce'], 'biztforma_csekk')) {
            wp_die(esc_html__('Érvénytelen kérés.', 'biztforma'));
        }

        // 3. Validáció + sanitize – még mindig nem vagyunk biztonságban
        $nev = isset($_POST['nev']) ? sanitize_text_field($_POST['nev']) : '';
        $email = isset($_POST['email']) ? sanitize_email($_POST['email']) : '';

        if (empty($nev)) {
            $hibak[] = 'A név mező kötelező!';
        }
        if (!is_email($email)) {
            $hibak[] = 'Érvénytelen email cím!';
        }

        // 4. Ha minden oké, jöhet az adatbázis, de csak prepare-rel!
        if (empty($hibak)) {
            global $wpdb;
            $tabla = $wpdb->prefix . 'users';

            $res = $wpdb->query(
                $wpdb->prepare(
                    "INSERT INTO $tabla (display_name, user_email) VALUES (%s, %s)",
                    $nev,
                    $email
                )
            );
            if ($res !== false) {
                echo '<div class="updated"><p>' . esc_html__('Sikeres mentés!', 'biztforma') . '</p></div>';
            } else {
                $hibak[] = 'Valami hiba történt a mentés során.';
            }
        }
    }
    ?>
    <div class="wrap">
        <h1><?php esc_html_e('Új felhasználó hozzáadása', 'biztforma'); ?></h1>
        <?php
        // 5. Hibák kiírása, minden escape-elve!
        if (!empty($hibak)) {
            echo '<div class="error"><ul>';
            foreach ($hibak as $hiba) {
                echo '<li>' . esc_html($hiba) . '</li>';
            }
            echo '</ul></div>';
        }
        ?>
        <form method="post">
            <?php wp_nonce_field('biztforma_csekk', 'biztforma_nonce'); // Nonce generálás ?>
            <table class="form-table">
                <tr>
                    <th><label for="nev"><?php esc_html_e('Név', 'biztforma'); ?></label></th>
                    <td><input type="text" id="nev" name="nev"
                        value="<?php echo isset($nev) ? esc_attr($nev) : ''; ?>" class="regular-text"></td>
                </tr>
                <tr>
                    <th><label for="email"><?php esc_html_e('Email', 'biztforma'); ?></label></th>
                    <td><input type="email" id="email" name="email"
                        value="<?php echo isset($email) ? esc_attr($email) : ''; ?>" class="regular-text"></td>
                </tr>
            </table>
            <?php submit_button(esc_html__('Mentés', 'biztforma')); ?>
        </form>
    </div>
    <?php
}

Mit csinál ez a kód?

Minden kimenet escape-elve:

  • Hibák: esc_html()
  • Input mezők: esc_attr()
  • Minden, amit bármilyen módon a böngésző lát!

Menüpont csak adminnak:

Más nem fér hozzá az oldalhoz (ez csak az admin felületen érhető el, de mindig legyen jogosultság-ellenőrzés).

Nonce használat minden POST művelethez:

Megakadályozza, hogy valaki máshonnan, rejtett űrlappal küldhessen kérelmet.

Minden bemenet validálva és sanitize-olva:

  • Szöveg: sanitize_text_field()
  • Email: sanitize_email()
  • Emailt még egyszer validáljuk: is_email() (WordPress beépített email validátor).

Adatbázis csak $wpdb->prepare()-en keresztül:

SQL injection esélye nulla, mert a függvény paraméterezetten kezeli az értékeket.

Külön kontextusok – Escape függvények magyarázata

Mi az a kontextus?

Nem mindegy, hogy hová írsz ki adatot!

  • JavaScriptbe → esc_js()
  • HTML szövegbe → esc_html()
  • HTML attribútumba → esc_attr()
  • URL-be → esc_url()

Példa:

// Egy sima szöveg: 
echo esc_html($name); // helyes

// Egy input value attribútumban: 
echo '<input value="' . esc_attr($name) . '">'; // helyes

// Egy hivatkozás: 
echo '<a href="' . esc_url($url) . '">Link</a>'; // helyes

Soha ne hagyd el a megfelelő esc függvényt!

Elég egyetlen helyen kifelejteni, és a támadó átveszi az oldalad felett az irányítást!

Haladó példák – hogyan lehet elrontani?

// --- Példa 1: XSS (Cross-Site Scripting) veszély ---

// Hibás megoldás – SOHA ne használd így!
// Ha valaki ezt írja az URL-be: ?username=<script>alert('XSS')</script>
// akkor a böngésző lefuttatja a támadó scriptjét!
echo $_GET['username']; // XSS – a támadó bármit beírhat az URL-be!

// Helyes megoldás:
// esc_html() minden HTML karaktert kódol, így a böngésző szövegként jeleníti meg.
echo esc_html($_GET['username']); // minden HTML karakter kódolva lesz


// --- Példa 2: Veszélyes URL és attribútum kezelés ---

// Hibás megoldás – bármilyen URL vagy akár javascript: futtatható!
$url = $_POST['url'];
echo "<a href=\"$url\">Kattints ide</a>"; // veszélyes!

// Helyes megoldás:
// esc_url_raw() POST-ból olvasáskor, esc_url() kimenetnél – így csak érvényes és biztonságos URL megy ki.
$url = isset($_POST['url']) ? esc_url_raw($_POST['url']) : '';
echo '<a href="' . esc_url($url) . '">Kattints ide</a>';


// --- Példa 3: SQL injection veszély ---

// Hibás megoldás – támadó "kiszabadulhat" az SQL-ből!
$name = $_POST['name'];
$result = $wpdb->get_results("SELECT * FROM users WHERE name = '$name'"); // SQL injection veszély!

// Helyes megoldás:
// sanitize_text_field() kiszűri a veszélyes karaktereket,
// $wpdb->prepare() paraméterezetten futtatja az SQL-t.
$name = isset($_POST['name']) ? sanitize_text_field($_POST['name']) : '';
$result = $wpdb->get_results(
    $wpdb->prepare("SELECT * FROM users WHERE name = %s", $name)
);

Miután a plugint legalább 68-szor átnézted, aludtál rá egyet-kettőt, majd újra nekifutottál, jöhet a hivatalos Plugin Check bővítmény. Ez szépen kipipálható gombbal minden pluginodat egyesével „kikérdezi”, és kíméletlenül kilistáz minden hibát, hiányosságot vagy gyanús sort – gyakran még azt is, ami igazából nem is hiba, csak ő szeret pánikot kelteni. De ettől még ez az egyik legfontosabb utolsó védvonalad!

Soha ne feledd:

2025-ben már a kezdők is AI-t használnak kódíráshoz – sőt, lassan a kutya is promptol –, de attól, hogy valamit az AI dobott össze, a biztonság még nem lesz automatikus. Mindig marad benne valami elfelejtett szűrés, egy kifelejtett nonce vagy egy túlságosan optimista echo. (Vagy olykor, lusta echo…)
Ezeket csak te tudod kiszúrni, ha saját szemmel mindent átnézel.
A felelősség pedig – bármilyen modern is a világ – mindig a tiéd.

Lőrincz András avatar

Lőrincz András

Lőrincz András vagyok, így hívnak. WordPress fejlesztőként weboldalak készítésével foglalkozom.

Amikor épp nem weboldalakat rakok össze, kvantumfizikáról vagy sci-firől olvasok, nézek videókat. Néha pedig elmerengek azon, hogy vajon mit csinál a másik énem egy párhuzamos univerzumban. Talán ő is épít valamit – vagy épp megőrült.

Vélemény, hozzászólás?

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük