17-bo‘lim
Xavfsizlik
XSS, CSRF, parol xeshlash, sirlarni saqlash va xavfsiz sarlavhalar - amaliy himoya qatlamlari.
Ushbu bo‘lim mundarijasi
Xavfsizlik - alohida bo'lim emas, har bir qatorda qilinadigan ish. Lekin eng ko'p uchraydigan hujumlarni bir joyda ko'rib chiqish foydali.
Bu bo'lim faqat himoya haqida: har bir hujumni tushuntirib, undan qanday saqlanishni ko'rsatamiz.
XSS - begona kodni sahifaga kiritish #
// Har bir misol qat'iy turlar rejimida ishlaydi.
function e(?string $matn): string
{
return htmlspecialchars($matn ?? "", ENT_QUOTES, "UTF-8");
}
$xavfli = '<script>alert("xss")</script>';
// Atribut APOSTROF bilan yozilgan hujum
$atributHujumi = "' onmouseover='alert(1)";
echo "1. Ekranlanmagan (XAVFLI):\n";
echo " <p>{$xavfli}</p>\n";
echo "\n2. htmlspecialchars bilan:\n";
echo " <p>" . e($xavfli) . "</p>\n";
echo "\n3. Atribut apostrofda - ENT_COMPAT YETARLI EMAS:\n";
echo " ENT_COMPAT: <input value='"
. htmlspecialchars($atributHujumi, ENT_COMPAT, "UTF-8") . "'>\n";
echo " ENT_QUOTES: <input value='" . e($atributHujumi) . "'>\n";
echo "\n";
echo "Birinchi qatorda apostrof EKRANLANMAGAN - hujumchi\n";
echo "atributdan chiqib, onmouseover qo'sha oldi.\n";
echo "Ikkinchi qatorda esa u ' ga aylandi.\n";
1. Ekranlanmagan (XAVFLI):
<p><script>alert("xss")</script></p>
2. htmlspecialchars bilan:
<p><script>alert("xss")</script></p>
3. Atribut apostrofda - ENT_COMPAT YETARLI EMAS:
ENT_COMPAT: <input value='' onmouseover='alert(1)'>
ENT_QUOTES: <input value='' onmouseover='alert(1)'>
Birinchi qatorda apostrof EKRANLANMAGAN - hujumchi
atributdan chiqib, onmouseover qo'sha oldi.
Ikkinchi qatorda esa u ' ga aylandi.
htmlspecialchars faqat HTML matni uchun. Boshqa
kontekstlarda u yetarli emas yoki noto'g'ri:
| Qayerga chiqadi | To'g'ri usul |
|---|---|
| HTML matni | htmlspecialchars($x, ENT_QUOTES, "UTF-8") |
| HTML atributi | Xuddi shu + atribut qo'shtirnoqda bo'lsin |
| URL parametri | urlencode($x) yoki rawurlencode($x) |
| JavaScript ichida | json_encode($x, JSON_HEX_TAG | JSON_HEX_AMP) |
| CSS ichida | Umuman foydalanuvchi ma'lumotini qo'ymang |
| SQL | Tayyorlangan so'rov (16-bo'lim) |
Eng xavfli chalkashlik - HTML uchun ekranlangan matnni JavaScript ichiga qo'yish:
<script>
var ism = "<?= e($ism) ?>"; // YETARLI EMAS
</script>
Foydalanuvchi </script><script>alert(1) yozsa,
htmlspecialchars < ni ekranlaydi va hujum
ishlamaydi. Lekin \ va yangi qator kabi belgilar
boshqa muammolar tug'diradi.
To'g'ri usul:
<script>
var ism = <?= json_encode($ism, JSON_HEX_TAG | JSON_HEX_AMP
| JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
</script>
json_encode qo'shtirnoqlarni ham o'zi qo'yadi.
Eng yaxshi yechim esa - ma'lumotni data- atributiga
qo'yish va JavaScript da uni o'qish. Shunda kod va
ma'lumot umuman aralashmaydi.
CSRF - begona saytdan yuborilgan forma #
// CSRF tokeni: sessiyada saqlanadi, formaga qo'yiladi, qaytganda solishtiriladi
final class CsrfHimoya
{
private array $sessiya = [];
public function token(): string
{
if (!isset($this->sessiya["csrf"])) {
// Haqiqiy kodda: bin2hex(random_bytes(32))
$this->sessiya["csrf"] = str_repeat("a1b2", 8);
}
return $this->sessiya["csrf"];
}
public function maydon(): string
{
return '<input type="hidden" name="_token" value="' . $this->token() . '">';
}
public function tekshir(?string $kelgan): bool
{
$saqlangan = $this->sessiya["csrf"] ?? null;
if ($saqlangan === null || $kelgan === null) {
return false;
}
// hash_equals - VAQT hujumidan himoya
return hash_equals($saqlangan, $kelgan);
}
}
$csrf = new CsrfHimoya();
echo "Formaga qo'yiladigan maydon:\n";
echo " " . $csrf->maydon() . "\n";
echo "\nTekshiruv natijalari:\n";
$sinovlar = [
"to'g'ri token" => $csrf->token(),
"noto'g'ri token" => "boshqa-token-qiymati",
"bo'sh" => null,
"qisqartirilgan" => substr($csrf->token(), 0, 10),
];
foreach ($sinovlar as $nom => $qiymat) {
printf(" %-18s %s\n", $nom, $csrf->tekshir($qiymat) ? "QABUL" : "rad etildi");
}
Formaga qo'yiladigan maydon:
<input type="hidden" name="_token" value="a1b2a1b2a1b2a1b2a1b2a1b2a1b2a1b2">
Tekshiruv natijalari:
to'g'ri token QABUL
noto'g'ri token rad etildi
bo'sh rad etildi
qisqartirilgan rad etildi
=== emas, hash_equals ishlatingOddiy === solishtiruvi birinchi farq topilganda
darhol to'xtaydi. Ya'ni ikki satr qanchalik o'xshash
bo'lsa, solishtirish shunchalik uzoq davom etadi.
Hujumchi bu vaqt farqini o'lchab, tokenni belgima-belgi topib olishi mumkin. Bu vaqt hujumi (timing attack) deb ataladi.
hash_equals esa har doim bir xil vaqt sarflaydi -
satrlar qanchalik o'xshash bo'lishidan qat'i nazar.
Uni qayerda ishlatish kerak:
| Nima solishtirilyapti | Usul |
|---|---|
| CSRF tokeni | hash_equals |
| API kaliti | hash_equals |
| Sessiya identifikatori | hash_equals |
| Parol xeshi | password_verify (o'zi qiladi) |
| Oddiy matn | === yetarli |
CSRF himoyasi to'liq bo'lishi uchun uch narsa kerak:
- Token - forma va sessiyada mos kelsin.
SameSite=Laxcookie (10-bo'lim).- O'zgartiruvchi amallar faqat POST orqali (8-bo'lim).
Uchtasi birga ishlaganda CSRF amalda imkonsiz bo'ladi.
Parollarni xeshlash #
$parol = "MeningKuchliParolim2024";
// 1. Xeshlash - har safar BOSHQA natija beradi
$xesh1 = password_hash($parol, PASSWORD_DEFAULT);
$xesh2 = password_hash($parol, PASSWORD_DEFAULT);
echo "Xesh uzunligi: " . strlen($xesh1) . " belgi\n";
echo "Ikki xesh bir xilmi: " . ($xesh1 === $xesh2 ? "ha" : "yo'q") . "\n";
echo "(Har xeshda tasodifiy TUZ bor - shuning uchun har xil)\n";
echo "\n";
// 2. Tekshirish
printf("To'g'ri parol: %s\n", password_verify($parol, $xesh1) ? "QABUL" : "rad");
printf("Noto'g'ri parol: %s\n", password_verify("boshqaParol", $xesh1) ? "QABUL" : "rad");
printf("Ikkinchi xesh: %s\n", password_verify($parol, $xesh2) ? "QABUL" : "rad");
echo "\n";
// 3. Xesh ichida nima bor
$qismlar = explode("$", $xesh1);
echo "Xesh tuzilishi:\n";
printf(" algoritm: %s\n", $qismlar[1]);
printf(" narx: %s\n", $qismlar[2]);
printf(" tuz+xesh: %d belgi\n", strlen($qismlar[3]));
echo "\n";
// 4. Algoritm eskirganini tekshirish
$eskiXesh = password_hash($parol, PASSWORD_BCRYPT, ["cost" => 4]);
$yangilash = password_needs_rehash($eskiXesh, PASSWORD_DEFAULT);
echo "Eski xesh yangilanishi kerakmi: " . ($yangilash ? "ha" : "yo'q") . "\n";
$info = password_get_info($xesh1);
echo "Joriy xesh algoritmi: " . $info["algoName"] . "\n";
Xesh uzunligi: 60 belgi
Ikki xesh bir xilmi: yo'q
(Har xeshda tasodifiy TUZ bor - shuning uchun har xil)
To'g'ri parol: QABUL
Noto'g'ri parol: rad
Ikkinchi xesh: QABUL
Xesh tuzilishi:
algoritm: 2y
narx: 10
tuz+xesh: 53 belgi
Eski xesh yangilanishi kerakmi: ha
Joriy xesh algoritmi: bcrypt
md5, sha1 yoki sha256 ishlatmangBu funksiyalar tez ishlash uchun yaratilgan - va aynan shu ularni parol uchun yaroqsiz qiladi.
Zamonaviy videokarta soniyasiga milliardlab SHA-256 xeshini hisoblaydi. Ya'ni oddiy parol bir necha soniyada topiladi.
password_hash esa ataylab sekin:
| Funksiya | Soniyada nechta urinish |
|---|---|
md5 | Milliardlab |
sha256 | Milliardlab |
password_hash (bcrypt) | O'nlab |
Uchta qoida:
1. PASSWORD_DEFAULT ishlating. U vaqt o'tishi
bilan eng yaxshi algoritmga o'zgaradi - kodingizni
o'zgartirmasdan.
2. Ustunni VARCHAR(255) qiling. Bugun bcrypt 60
belgi, lekin kelajakdagi algoritm uzunroq bo'lishi
mumkin.
3. Kirishda password_needs_rehash ni tekshiring:
if (password_verify($parol, $xesh)) {
if (password_needs_rehash($xesh, PASSWORD_DEFAULT)) {
$yangiXesh = password_hash($parol, PASSWORD_DEFAULT);
// bazaga yozing
}
}
Shunda foydalanuvchilar tizimga kirgani sari ularning xeshlari avtomatik yangilanadi.
Va tuzni o'zingiz qo'shmang - password_hash uni
o'zi yaratadi va xesh ichiga joylaydi.
Sirlarni saqlash #
<?php
// config/sozlama.php - bu fayl git ga QO'SHILMAYDI
declare(strict_types=1);
return [
"baza" => [
"dsn" => getenv("DB_DSN") ?: "mysql:host=localhost;dbname=dokon;charset=utf8mb4",
"user" => getenv("DB_USER") ?: "root",
"parol" => getenv("DB_PASSWORD") ?: "",
],
"pochta" => [
"kalit" => getenv("MAIL_KEY") ?: "",
],
];
Eng ko'p uchraydigan jiddiy xato - .env yoki
sozlama faylini git omboriga qo'shish.
Bir marta qo'shilgan sir tarixda abadiy qoladi. Uni keyinchalik o'chirsangiz ham, eski kommitlardan o'qib olish mumkin.
Majburiy .gitignore qatorlari:
.env
.env.*
!.env.example
config/sozlama.php
vendor/
Va .env.example faylini qiymatlarsiz qo'shing:
DB_DSN=mysql:host=localhost;dbname=NOMI;charset=utf8mb4
DB_USER=
DB_PASSWORD=
Shunda yangi dasturchi nima kerakligini biladi, lekin haqiqiy parollar omborga tushmaydi.
Agar sir allaqachon git ga tushgan bo'lsa:
| Qadam | Amal |
|---|---|
| 1 | Darhol parolni almashtiring |
| 2 | Kalitlarni bekor qiling |
| 3 | Faqat keyin tarixni tozalashni o'ylang |
Birinchi qadam eng muhimi. Tarixni tozalash sirni allaqachon ko'rgan odamdan himoya qilmaydi.
14-bo'limda aytilgan qoidani ham unutmang: config/
papkasi veb ildizidan tashqarida bo'lsin.
Xavfsizlik sarlavhalari #
// Har bir javobga qo'yiladigan sarlavhalar
$sarlavhalar = [
"Content-Security-Policy" => [
"default-src 'self'",
"Begona manbadan skript yuklanmasin - XSS ta'sirini keskin kamaytiradi",
],
"X-Content-Type-Options" => [
"nosniff",
"Brauzer fayl turini o'zi taxmin qilmasin",
],
"X-Frame-Options" => [
"DENY",
"Sahifa begona iframe ichiga joylanmasin (clickjacking)",
],
"Referrer-Policy" => [
"strict-origin-when-cross-origin",
"Begona saytga to'liq URL yuborilmasin",
],
"Strict-Transport-Security" => [
"max-age=31536000; includeSubDomains",
"Brauzer faqat HTTPS orqali ulansin",
],
];
foreach ($sarlavhalar as $nom => [$qiymat, $izoh]) {
echo "{$nom}:\n";
echo " qiymat: {$qiymat}\n";
echo " nima uchun: {$izoh}\n\n";
}
echo "PHP da qo'yish:\n";
echo " header(\"X-Frame-Options: DENY\");\n";
echo " (yoki veb-server sozlamasida - bu afzalroq)\n";
Content-Security-Policy:
qiymat: default-src 'self'
nima uchun: Begona manbadan skript yuklanmasin - XSS ta'sirini keskin kamaytiradi
X-Content-Type-Options:
qiymat: nosniff
nima uchun: Brauzer fayl turini o'zi taxmin qilmasin
X-Frame-Options:
qiymat: DENY
nima uchun: Sahifa begona iframe ichiga joylanmasin (clickjacking)
Referrer-Policy:
qiymat: strict-origin-when-cross-origin
nima uchun: Begona saytga to'liq URL yuborilmasin
Strict-Transport-Security:
qiymat: max-age=31536000; includeSubDomains
nima uchun: Brauzer faqat HTTPS orqali ulansin
PHP da qo'yish:
header("X-Frame-Options: DENY");
(yoki veb-server sozlamasida - bu afzalroq)
Kirish tizimi - to'liq namuna #
<?php
declare(strict_types=1);
function kirishgaUrinish(PDO $pdo, string $email, string $parol): ?array
{
// 1. Foydalanuvchini topamiz
$stmt = $pdo->prepare("SELECT id, email, parol_xesh, rol FROM foydalanuvchilar WHERE email = ?");
$stmt->execute([$email]);
$f = $stmt->fetch();
// 2. MUHIM: foydalanuvchi topilmasa ham xeshni tekshiramiz
// Aks holda javob vaqti email mavjudligini oshkor qiladi
$xesh = $f["parol_xesh"] ?? '$2y$12$aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
if (!password_verify($parol, $xesh) || $f === false) {
return null; // bir xil xabar: "email yoki parol noto'g'ri"
}
// 3. Xesh eskirgan bo'lsa - yangilaymiz
if (password_needs_rehash($f["parol_xesh"], PASSWORD_DEFAULT)) {
$yangi = password_hash($parol, PASSWORD_DEFAULT);
$pdo->prepare("UPDATE foydalanuvchilar SET parol_xesh = ? WHERE id = ?")
->execute([$yangi, $f["id"]]);
}
// 4. Sessiya identifikatorini YANGILAYMIZ (10-bo'lim)
session_regenerate_id(true);
$_SESSION["user_id"] = (int) $f["id"];
$_SESSION["kirgan_vaqt"] = time();
unset($f["parol_xesh"]);
return $f;
}
Yuqoridagi kodda ikki nozik detal bor.
1. Bir xil xabar. "Bunday email yo'q" va "Parol noto'g'ri" xabarlari hujumchiga qaysi emaillar ro'yxatdan o'tganini aytadi. Har doim bitta umumiy xabar bering: "Email yoki parol noto'g'ri".
2. Bir xil vaqt. Foydalanuvchi topilmasa, kod
password_verify ni baribir chaqiradi. Aks holda
javob sezilarli darajada tezroq qaytadi va bu
mavjudlikni oshkor qiladi.
Bundan tashqari kirish tizimida yana uch narsa kerak:
| Himoya | Nima uchun |
|---|---|
| Urinishlar chegarasi | Parolni tanlab topishga qarshi |
| Kechikish oshirish | Har xato urinishdan keyin kutish vaqtini oshiring |
| Ikki bosqichli tasdiqlash | Parol o'g'irlansa ham hisob himoyalanadi |
Parolni tiklash havolasi uchun ham qoidalar bor:
tasodifiy token (random_bytes(32)), qisqa muddat
(1 soat) va bir marta ishlatilgach bekor qilish.
<script>tegini ekranlamasdan chiqaring va brauzerda oching.htmlspecialcharsqo'shing va farqni ko'ring.ENT_QUOTESsiz atribut ichida sinab ko'ring.- CSRF tokeni yaratuvchi sinf yozing.
===o'rnigahash_equalsishlating.- Bir xil parolni ikki marta
password_hashqiling - natijalar bir xilmi? password_verifybilan ikkalasini ham tekshiring.md5bilan xeshlab, natija uzunligini solishtiring.password_needs_rehashni eskicostbilan sinang..gitignorega.envni qo'shing va.env.exampleyarating.
Xulosa #
- Har bir hujumning mohiyati bitta: ma'lumot kodga aylanib qoladi.
- Ekranlash chiqish joyiga bog'liq: HTML, atribut, URL va JavaScript uchun har xil.
- HTML uchun
htmlspecialchars($x, ENT_QUOTES, "UTF-8")-ENT_QUOTESshart. - JavaScript ichiga ma'lumotni
json_encodebilan qo'ying yokidata-atributdan o'qing. - CSRF himoyasi uch qismdan iborat: token,
SameSite=Lax, POST. - Token va kalitlarni
hash_equalsbilan solishtiring -===vaqt hujumiga ochiq. - Parol uchun
md5/sha256yaroqsiz - ular ataylab tez. password_hash(PASSWORD_DEFAULT)ishlating; tuzni o'zingiz qo'shmang.- Parol ustunini
VARCHAR(255)qiling vapassword_needs_rehashni tekshiring. - Kirish xatosida bir xil xabar va bir xil vaqt bo'lsin.
- Sirlarni
.envda saqlang; uni hech qachon git ga qo'shmang. - Sir git ga tushgan bo'lsa - birinchi navbatda parolni almashtiring.
Keyingi bo'limda JSON va API yaratishni o'rganamiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.