19-bo‘lim

Ma'lumotlar bazasi xavfsizligi

SQL injection va parametrli so'rovlar, foydalanuvchi huquqlari, parol saqlash, maxfiy ma'lumot va zaxira nusxa.

🕑 20 daqiqa o‘qish 📄 1 112 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. SQL injection
  2. Yagona to'g'ri yechim - parametrli so'rov
  3. Eng kam imtiyoz
  4. Parol saqlash
  5. Maxfiy ma'lumotni ajratish
  6. Audit izi
  7. Zaxira nusxa
  8. Ulanish xavfsizligi
  9. Xulosa

Ma'lumotlar bazasi - ilovaning eng qimmatli qismi. Kod yo'qolsa qayta yozish mumkin, ma'lumot yo'qolsa - yo'q.

SQL injection #

Satr birlashtirish - eng keng tarqalgan zaiflik Xavfli kod $sql = "SELECT * FROM users WHERE ism = '" . $_GET['ism'] . "'"; Kirish: ' OR '1'='1 Hosil bo'lgan so'rov SELECT * FROM users WHERE ism = '' OR '1'='1' Barcha foydalanuvchi qaytadi - shart har doim rost DROP TABLE, ma'lumot o'g'irlash, huquq ko'tarish ham mumkin
Foydalanuvchi kiritgan matn kodga aylanib qoladi
SQL
CREATE TABLE foydalanuvchilar (
    id     INT PRIMARY KEY AUTO_INCREMENT,
    ism    VARCHAR(50)  NOT NULL,
    email  VARCHAR(100) NOT NULL,
    rol    ENUM('foydalanuvchi', 'admin') NOT NULL DEFAULT 'foydalanuvchi'
);

INSERT INTO foydalanuvchilar (ism, email, rol) VALUES
    ('Husanboy', '[email protected]', 'foydalanuvchi'),
    ('Malika',   '[email protected]',   'foydalanuvchi'),
    ('Admin',    '[email protected]',    'admin');
SQL
-- Normal so'rov
SELECT id, ism, rol FROM foydalanuvchilar WHERE ism = 'Husanboy';
Natija
+----+----------+---------------+
| id | ism      | rol           |
+----+----------+---------------+
|  1 | Husanboy | foydalanuvchi |
+----+----------+---------------+
SQL
-- Hujum natijasida hosil bo'lgan so'rov
SELECT id, ism, rol FROM foydalanuvchilar WHERE ism = '' OR '1'='1';
Natija
+----+----------+---------------+
| id | ism      | rol           |
+----+----------+---------------+
|  1 | Husanboy | foydalanuvchi |
|  2 | Malika   | foydalanuvchi |
|  3 | Admin    | admin         |
+----+----------+---------------+

Bitta belgi bilan hujumchi butun jadvalni oldi.

Yagona to'g'ri yechim - parametrli so'rov #

SQL
-- Tayyorlangan so'rov: SQL va MA'LUMOT ajratilgan
PREPARE qidirish FROM 'SELECT id, ism, rol FROM foydalanuvchilar WHERE ism = ?';

SET @kirish = 'Husanboy';
EXECUTE qidirish USING @kirish;
Natija
+----+----------+---------------+
| id | ism      | rol           |
+----+----------+---------------+
|  1 | Husanboy | foydalanuvchi |
+----+----------+---------------+
SQL
PREPARE sanash FROM
    'SELECT COUNT(*) AS topildi FROM foydalanuvchilar WHERE ism = ?';

-- Hujum urinishi - endi u shunchaki MATN
SET @kirish = ''' OR ''1''=''1';
EXECUTE sanash USING @kirish;

DEALLOCATE PREPARE sanash;
Natija
+---------+
| topildi |
+---------+
|       0 |
+---------+

Hech qanday qator topilmadi - hujumchi kiritgan matn shunchaki ism sifatida qidirildi, kod sifatida emas.

Parametrli so'rov - muzokara qilinmaydigan qoida

Parametrli so'rovda SQL oldindan tahlil qilinadi, keyin ma'lumot qo'yiladi. Ma'lumot hech qachon kodga aylanmaydi.

PHP (PDO):

PHP
// XAVFLI
$db->query("SELECT * FROM users WHERE ism = '$ism'");

// TO'G'RI
$stmt = $db->prepare("SELECT * FROM users WHERE ism = ?");
$stmt->execute([$ism]);

Bu loyihada ham shu naqsh ishlatiladi - bootstrap.php da PDO ATTR_EMULATE_PREPARES => false bilan sozlangan, ya'ni so'rovlar haqiqatan serverda tayyorlanadi.

Ekranlash (escaping) yetarli emas. mysqli_real_escape_string kabi funksiyalar kodlash sozlamalariga bog'liq va chetlab o'tish usullari ma'lum. Parametrli so'rov esa strukturaviy himoya beradi.

Parametr qo'yib bo'lmaydigan joylar:

SQL
SELECT * FROM ? WHERE ...          -- jadval nomi - MUMKIN EMAS
ORDER BY ?                          -- ustun nomi - MUMKIN EMAS

Bunday joylarda oq ro'yxat ishlating:

PHP
$ruxsat = ['ism', 'sana', 'narx'];
$ustun = in_array($kirish, $ruxsat, true) ? $kirish : 'id';
$sql = "SELECT * FROM mahsulotlar ORDER BY $ustun";

Foydalanuvchi kiritgan qiymat hech qachon to'g'ridan-to'g'ri so'rovga tushmasin.

Eng kam imtiyoz #

SQL
-- Ilova foydalanuvchisi uchun huquqlar
SELECT
    'CREATE USER ilova@localhost IDENTIFIED BY ...' AS qadam
UNION ALL SELECT 'GRANT SELECT, INSERT, UPDATE, DELETE ON dukon.* TO ilova@localhost'
UNION ALL SELECT '-- DROP, ALTER, CREATE BERILMAYDI'
UNION ALL SELECT '-- Migratsiya uchun ALOHIDA foydalanuvchi';
Natija
+--------------------------------------------------------------------+
| qadam                                                              |
+--------------------------------------------------------------------+
| CREATE USER ilova@localhost IDENTIFIED BY ...                      |
| GRANT SELECT, INSERT, UPDATE, DELETE ON dukon.* TO ilova@localhost |
| -- DROP, ALTER, CREATE BERILMAYDI                                  |
| -- Migratsiya uchun ALOHIDA foydalanuvchi                          |
+--------------------------------------------------------------------+
Ilova root bilan ulanmasin

Ko'p loyiha root yoki to'liq huquqli foydalanuvchi bilan ulanadi. Bu SQL injection ning zararini keskin oshiradi.

HuquqInjection bilan nima mumkin
Faqat SELECTMa'lumot o'qish
+ INSERT/UPDATEMa'lumot buzish
+ DROPJadvallarni o'chirish
+ FILEServerdagi fayllarni o'qish
rootButun server

Har vazifa uchun alohida foydalanuvchi:

SQL
-- Ilova: kundalik ish
GRANT SELECT, INSERT, UPDATE, DELETE ON dukon.* TO 'ilova'@'10.0.%';

-- Migratsiya: faqat joylashtirish paytida
GRANT ALL ON dukon.* TO 'migratsiya'@'10.0.0.5';

-- Hisobot: faqat o'qish
GRANT SELECT ON dukon.* TO 'hisobot'@'10.0.%';

-- Zaxira nusxa
GRANT SELECT, LOCK TABLES, SHOW VIEW ON dukon.* TO 'zaxira'@'localhost';

Xost qismiga ham e'tibor bering: 'ilova'@'%' istalgan joydan ulanishga ruxsat beradi. Aniq IP yoki tarmoqni ko'rsating.

Huquqlarni ko'rish:

SQL
SHOW GRANTS FOR 'ilova'@'10.0.%';

Parol saqlash #

SQL
-- NOTO'G'RI: ochiq matn
SELECT 'parol123' AS ochiq_parol,
       MD5('parol123')  AS md5_xesh,
       SHA1('parol123') AS sha1_xesh;
Natija
+-------------+----------------------------------+------------------------------------------+
| ochiq_parol | md5_xesh                         | sha1_xesh                                |
+-------------+----------------------------------+------------------------------------------+
| parol123    | e9aba73de1a9715fd100fe111585cebc | b4844d172402510660f33b6e12d310e69a4c6631 |
+-------------+----------------------------------+------------------------------------------+
MD5 va SHA1 parol uchun yaramaydi

Yuqoridagi MD5('parol123') qiymatini qidiruv tizimiga kiritsangiz, darhol asl parolni topasiz - bunday jadvallar internetda tayyor turibdi.

Muammo algoritm buzilganida emas, tezligida: zamonaviy GPU sekundiga milliardlab MD5 hisoblaydi.

Parol uchun ataylab sekin algoritm kerak:

AlgoritmBaho
MD5, SHA1, SHA256Yaramaydi - juda tez
bcryptYaxshi, keng qo'llanadi
scryptYaxshi, xotira talab qiladi
argon2idEng yaxshi - zamonaviy standart

Ular ikki narsani beradi:

  1. Tuz (salt) - har parolga tasodifiy qo'shimcha, shuning uchun bir xil parollar turli xesh beradi;
  2. Sekinlik - minglab iteratsiya, brute-force qimmatlashadi.

PHP da:

PHP
$xesh = password_hash($parol, PASSWORD_DEFAULT);   // bcrypt yoki argon2
password_verify($kiritilgan, $xesh);               // tekshirish

Xeshlashni ma'lumotlar bazasida qilmang - u ilova darajasida bajarilishi kerak. Aks holda parol so'rov jurnalida va tarmoqda ochiq ko'rinadi.

Ustun turi: CHAR(60) bcrypt uchun, VARCHAR(255) universal.

Maxfiy ma'lumotni ajratish #

SQL
CREATE TABLE mijozlar (
    id           INT PRIMARY KEY AUTO_INCREMENT,
    ism          VARCHAR(100) NOT NULL,
    email        VARCHAR(100) NOT NULL,
    telefon      VARCHAR(20)  NOT NULL,
    karta_oxiri  CHAR(4)      NULL,
    yaratilgan   DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO mijozlar (ism, email, telefon, karta_oxiri, yaratilgan) VALUES
    ('Husanboy Qodirov', '[email protected]', '901234567', '4242', '2026-01-10 10:00:00'),
    ('Malika Yusupova',  '[email protected]',   '911112233', NULL,   '2026-02-15 11:00:00');
SQL
-- Qo'llab-quvvatlash xodimi uchun ko'rinish
CREATE VIEW mijozlar_qisqa AS
SELECT
    id,
    ism,
    CONCAT(LEFT(email, 2), '***@', SUBSTRING_INDEX(email, '@', -1)) AS email,
    CONCAT('***', RIGHT(telefon, 4)) AS telefon,
    yaratilgan
FROM mijozlar;

SELECT id, ism, email, telefon FROM mijozlar_qisqa ORDER BY id;
Natija
+----+------------------+--------------+---------+
| id | ism              | email        | telefon |
+----+------------------+--------------+---------+
|  1 | Husanboy Qodirov | hu***@edu.uz | ***4567 |
|  2 | Malika Yusupova  | ma***@edu.uz | ***2233 |
+----+------------------+--------------+---------+
Ko'rinish (view) orqali huquq berish

Xodimga butun jadvalni ko'rsatish o'rniga ko'rinish yarating va faqat unga huquq bering:

SQL
GRANT SELECT ON dukon.mijozlar_qisqa TO 'qollab_quvvatlash'@'%';
-- mijozlar jadvaliga huquq BERILMAYDI

Endi xodim to'liq email va telefonni ko'ra olmaydi.

Bu ma'lumotni minimallashtirish prinsipi: har kishi faqat o'z ishi uchun kerakli ma'lumotni ko'rsin.

Umumiy qoidalar:

Ma'lumotQanday saqlash
ParolXesh (argon2id)
Karta raqamiSaqlamang - to'lov tizimiga qoldiring
Karta oxirgi 4 raqamiSaqlash mumkin
Pasport, JSHSHIRShifrlangan holda, kerak bo'lsagina
Email, telefonOddiy, lekin huquqlar bilan cheklangan
Sog'liq, moliyaShifrlangan, audit bilan

Umuman saqlamaslik - eng xavfsiz variant. Yo'q ma'lumot o'g'irlanmaydi.

Audit izi #

SQL
CREATE TABLE audit (
    id          INT PRIMARY KEY AUTO_INCREMENT,
    jadval      VARCHAR(64)  NOT NULL,
    qator_id    INT          NOT NULL,
    amal        ENUM('INSERT', 'UPDATE', 'DELETE') NOT NULL,
    kim         VARCHAR(100) NOT NULL,
    qachon      DATETIME     NOT NULL,
    eski_qiymat JSON         NULL,
    yangi_qiymat JSON        NULL
);

INSERT INTO audit (jadval, qator_id, amal, kim, qachon, eski_qiymat, yangi_qiymat) VALUES
    ('mijozlar', 1, 'UPDATE', '[email protected]', '2026-03-01 09:15:00',
     '{"telefon":"901234567"}', '{"telefon":"999999999"}'),
    ('mijozlar', 2, 'DELETE', '[email protected]', '2026-03-02 14:20:00',
     '{"ism":"Test"}', NULL);
SQL
SELECT jadval, qator_id, amal, kim, qachon,
       JSON_VALUE(eski_qiymat, '$.telefon') AS eski_telefon
FROM audit
ORDER BY id;
Natija
+----------+----------+--------+--------------+---------------------+--------------+
| jadval   | qator_id | amal   | kim          | qachon              | eski_telefon |
+----------+----------+--------+--------------+---------------------+--------------+
| mijozlar |        1 | UPDATE | [email protected] | 2026-03-01 09:15:00 | 901234567    |
| mijozlar |        2 | DELETE | [email protected] | 2026-03-02 14:20:00 | NULL         |
+----------+----------+--------+--------------+---------------------+--------------+
Audit jadvalida nima bo'lishi kerak

Muhim ma'lumot ustidagi har o'zgarishni yozib borish - qoidalar talabi bo'lishi ham mumkin.

UstunNima uchun
KimFoydalanuvchi identifikatori
QachonAniq vaqt
NimaJadval va qator
Eski qiymatNima o'zgargani
Yangi qiymatNimaga o'zgargani
IP manzilQayerdan

Amalga oshirish usullari:

UsulAfzalligiKamchiligi
TriggerHech narsa o'tkazib yubormaydi"Kim" ni bilmaydi
Ilova kodi"Kim" ni biladiTo'g'ridan-to'g'ri UPDATE ni ko'rmaydi
Binlog tahliliHammasiMurakkab

Trigger kim ni bilmasligi asosiy muammo - u faqat CURRENT_USER() (ma'lumotlar bazasi foydalanuvchisi) ni ko'radi, ilova foydalanuvchisini emas.

Yechim - ilova sessiya o'zgaruvchisiga yozadi:

SQL
SET @ilova_foydalanuvchi = '[email protected]';
UPDATE mijozlar SET ...;   -- trigger @ilova_foydalanuvchi ni o'qiydi

Audit jadvaliga DELETE va UPDATE huquqini bermang - faqat INSERT. Aks holda hujumchi izini o'chira oladi.

Zaxira nusxa #

SQL
SELECT
    'mysqldump --single-transaction --routines --triggers' AS buyruq,
    'InnoDB uchun jadvalni qulflamaydi' AS izoh
UNION ALL SELECT '--single-transaction', 'Izchil surat oladi'
UNION ALL SELECT '--routines --triggers', 'Protsedura va triggerlarni ham oladi'
UNION ALL SELECT '--set-gtid-purged=OFF', 'Replikatsiya bilan muammo bo''lmasin';
Natija
+------------------------------------------------------+--------------------------------------+
| buyruq                                               | izoh                                 |
+------------------------------------------------------+--------------------------------------+
| mysqldump --single-transaction --routines --triggers | InnoDB uchun jadvalni qulflamaydi    |
| --single-transaction                                 | Izchil surat oladi                   |
| --routines --triggers                                | Protsedura va triggerlarni ham oladi |
| --set-gtid-purged=OFF                                | Replikatsiya bilan muammo bo'lmasin  |
+------------------------------------------------------+--------------------------------------+
Sinab ko'rilmagan zaxira - zaxira emas

Eng ko'p uchraydigan halokat naqshi:

  1. Zaxira nusxa har kuni olinadi;
  2. Hech kim uni tiklab ko'rmaydi;
  3. Halokat kuni ma'lum bo'ladiki, fayllar bo'sh yoki buzilgan.

3-2-1 qoidasi:

RaqamMa'nosi
3Ma'lumotning uchta nusxasi
2Ikki xil saqlash muhitida
1Bittasi boshqa joyda (offsite)

Tekshirish ro'yxati:

  • Zaxira muntazam olinadimi?
  • U boshqa serverda saqlanadimi?
  • Shifrlanganmi? (maxfiy ma'lumot bor)
  • Tiklash sinab ko'rilganmi? Qachon oxirgi marta?
  • Tiklash qancha vaqt oladi? (RTO)
  • Qancha ma'lumot yo'qolishi mumkin? (RPO)

Oxirgi ikkitasi biznes qarori: "1 soatlik ma'lumot yo'qolishi maqbulmi?" degan savolga javob zaxira chastotasini belgilaydi.

Nuqtaviy tiklash (point-in-time recovery) uchun binlog ham saqlanishi kerak:

SQL
SHOW VARIABLES LIKE 'log_bin';

Bu to'liq zaxira + binlog bilan istalgan lahzaga qaytish imkonini beradi.

Ulanish xavfsizligi #

Amaliy tekshiruv ro'yxati
ChoraIzoh
Ma'lumotlar bazasi internetga ochiq emasFaqat ichki tarmoq
TLS bilan ulanishREQUIRE SSL
Kuchli parollarHar muhit uchun boshqa
Parollar .env da, kodda emasGit ga tushmasin
root uzoqdan ulanmaydi'root'@'localhost'
Anonim foydalanuvchilar o'chirilganmysql_secure_installation
Sinov bazasi o'chirilganDROP DATABASE test
Yangilanishlar o'rnatiladiXavfsizlik yamoqlari

Tekshirish:

SQL
-- Kim qayerdan ulana oladi?
SELECT user, host FROM mysql.user ORDER BY user, host;

-- Parolsiz foydalanuvchi bormi?
SELECT user, host FROM mysql.user WHERE authentication_string = '';

Bu loyihaning .env fayli .gitignore da - ma'lumotlar bazasi paroli hech qachon Git ga tushmasligi kerak.

Agar parol tasodifan commit qilingan bo'lsa: uni darhol almashtiring. Git tarixidan o'chirish yetarli emas - u allaqachon nusxalangan bo'lishi mumkin.

Amaliy topshiriq
  1. SQL injection hujumini simulyatsiya qiling.
  2. PREPARE bilan xuddi shu hujumni to'xtating.
  3. Jadval nomini parametr qilib bo'lmasligini tushuntiring.
  4. Ilova uchun cheklangan huquqli foydalanuvchi yarating.
  5. root bilan ulanish nima uchun xavfli ekanini ayting.
  6. MD5 parol xeshini qidiruv tizimida sinab ko'ring.
  7. Parol uchun to'g'ri algoritmlarni sanang.
  8. Maxfiy ma'lumotni yashiruvchi ko'rinish yarating.
  9. Audit jadvalini loyihalang.
  10. 3-2-1 zaxira qoidasini tushuntiring.

Xulosa #

  • SQL injection - satr birlashtirishdan kelib chiqadi.
  • Parametrli so'rov - yagona ishonchli yechim.
  • Ekranlash yetarli emas.
  • Jadval va ustun nomi uchun oq ro'yxat ishlating.
  • Ilova root bilan ulanmasin - eng kam imtiyoz.
  • Har vazifa uchun alohida foydalanuvchi.
  • MD5/SHA1 parol uchun yaramaydi - juda tez.
  • argon2id yoki bcrypt ishlating, ilova darajasida.
  • Maxfiy ma'lumotni ko'rinish orqali cheklang.
  • Karta raqamini umuman saqlamang.
  • Audit jadvaliga faqat INSERT huquqi.
  • Sinab ko'rilmagan zaxira - zaxira emas; 3-2-1 qoidasi.

Keyingi va oxirgi bo'limda to'liq tizim sxemasini loyihalaymiz.

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.