PHP / CSRF хамгаалалт
CSRF хамгаалалт
CSRF (Cross-Site Request Forgery) — буруу орчуулбал "өөр сайтаас хүсэлт хуурамчлах" гэсэн утгатай. Хэрэглэгч таны сайтад нэвтэрсэн байхад хортой вэбсайт тухайн хэрэглэгчийн нэрийн өмнөөс таны серверт хүсэлт явуулдаг. Хамгаалалт нь энгийн боловч маш чухал — нэвтрэлт шаардагдах аль ч маягтад заавал хэрэглэнэ.
CSRF халдлага хэрхэн ажилладаг вэ?
код
1. Хэрэглэгч bank.mn сайтад нэвтэрнэ (session үүснэ)
2. Хэрэглэгч хортой сайт руу орно (жишээ нь: free-prize.mn)
3. Хортой сайт хэрэглэгчийн хөтчөөр bank.mn руу хүсэлт явуулна:
<img src="https://bank.mn/transfer?to=hacker&amount=1000000">
4. Хөтөч bank.mn-ийн session cookie-г автоматаар дагуулна
5. Банк "хэрэглэгч өөрөө явуулсан" гэж үзэн гүйцэтгэнэ
Хамгаалалтгүй маягт иймэрхүү харагдана:
php
<?php
// ❌ CSRF хамгаалалтгүй — аюултай!
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$дүн = (float) $_POST['дүн'];
$данс = $_POST['данс'];
// Хүсэлт хаанаас ирснийг шалгахгүй — халдлагад нэрвэгдэнэ
гүйлгээ_хийх($данс, $дүн);
}
?>
<form method="POST" action="/transfer">
<input name="данс" value="">
<input name="дүн" value="">
<button>Шилжүүлэх</button>
</form>
CSRF token — үндсэн хамгаалалт
Санамсаргүй, тааж болшгүй token үүсгэж маягтад нуун оруулдаг. Сервер хүсэлт хүлээн авахад тухайн token-г шалгадаг:
php
<?php
session_start();
// Token үүсгэх функц
function csrf_token_үүсгэх(): string
{
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
// Token шалгах функц
function csrf_шалгах(): void
{
$token = $_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
if (!hash_equals($_SESSION['csrf_token'] ?? '', $token)) {
http_response_code(403);
die("CSRF шалгалт амжилтгүй боллоо.");
}
}
// Маягт харуулах
$token = csrf_token_үүсгэх();
?>
<form method="POST" action="/transfer">
<!-- Token нуун оруулна — хортой сайт энийг мэдэхгүй -->
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($token) ?>">
<input type="text" name="данс" placeholder="Дансны дугаар">
<input type="number" name="дүн" placeholder="Дүн">
<button type="submit">Шилжүүлэх</button>
</form>
php
<?php
// POST хүсэлт хүлээн авах
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
csrf_шалгах(); // Эхлээд token шалгана
// Token зөв бол үргэлжлүүлнэ
$дүн = filter_input(INPUT_POST, 'дүн', FILTER_VALIDATE_FLOAT);
$данс = htmlspecialchars($_POST['данс'] ?? '', ENT_QUOTES, 'UTF-8');
if ($дүн && $данс) {
гүйлгээ_хийх($данс, $дүн);
echo "Гүйлгээ амжилттай!";
}
}
?>
Дахин ашиглагдах CsrfGuard класс
Бодит төсөлд дахин дахин ашиглах боломжтой класс болгоно:
php
<?php
class CsrfGuard
{
private const TOKEN_УРТ = 32;
private const SESSION_ТҮЛХҮҮР = '_csrf';
// Token авах эсвэл шинэ үүсгэх
public static function token(): string
{
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
if (empty($_SESSION[self::SESSION_ТҮЛХҮҮР])) {
$_SESSION[self::SESSION_ТҮЛХҮҮР] = bin2hex(
random_bytes(self::TOKEN_УРТ)
);
}
return $_SESSION[self::SESSION_ТҮЛХҮҮР];
}
// HTML input field буцаана
public static function поле(): string
{
$token = htmlspecialchars(self::token(), ENT_QUOTES, 'UTF-8');
return '<input type="hidden" name="csrf_token" value="' . $token . '">';
}
// POST хүсэлт шалгана — амжилтгүй бол 403 хариулна
public static function шалга(): void
{
$оролт = $_POST['csrf_token']
?? $_SERVER['HTTP_X_CSRF_TOKEN']
?? '';
if (!hash_equals(self::token(), $оролт)) {
http_response_code(403);
header('Content-Type: application/json');
echo json_encode(['алдаа' => 'Хүсэлт баталгаажуулалт амжилтгүй.']);
exit;
}
// Нэг удаа хэрэглэсний дараа шинэчлэх (optional)
unset($_SESSION[self::SESSION_ТҮЛХҮҮР]);
}
}
?>
Ашиглах нь маш хялбар болно:
php
<?php
session_start();
?>
<!-- Маягт дотор -->
<form method="POST" action="/нийтлэл-устгах">
<?= CsrfGuard::поле() ?>
<input type="hidden" name="id" value="42">
<button type="submit">Устгах</button>
</form>
<?php
// Хүсэлт хүлээн авахад
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
CsrfGuard::шалга(); // Нэг мөрт шалгалт
// Цааш аюулгүйгээр үргэлжлүүлнэ
}
?>
AJAX хүсэлтэд CSRF хамгаалалт
php
<?php
// Хуудсын эхэнд JavaScript-д token дамжуулна
session_start();
$csrf = CsrfGuard::token();
?>
<script>
const CSRF_TOKEN = '<?= htmlspecialchars($csrf, ENT_QUOTES) ?>';
// AJAX хүсэлт бүрт header дотор явуулна
async function аюулгүй_хүсэлт(url, өгөгдөл) {
const хариу = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': CSRF_TOKEN, // Header дотор
},
body: JSON.stringify(өгөгдөл),
});
return хариу.json();
}
// Ашиглах
аюулгүй_хүсэлт('/api/нийтлэл', { гарчиг: 'Шинэ нийтлэл' })
.then(хариу => console.log(хариу));
</script>
php
<?php
// API route дотор header-ийн token шалгана
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// $_SERVER['HTTP_X_CSRF_TOKEN'] нь X-CSRF-Token header-ийг агуулна
CsrfGuard::шалга();
// ...
}
?>
Дараагийн хичээлд:
Тогтмол илэрхийлэл буюу Regular Expression (RegExp)-г судална. Имэйл, утасны дугаар, огноо зэрэг форматыг баталгаажуулах, мөр дотроос хэв загвар хайх, солих зэрэг хүчирхэг боловсруулалтыг үзнэ.