Bildiginiz gibi web sitenizde xss sql injetcion gibi saldırılar olabiliyor ve bu web sitenizi kullanan kullanıcılar ve sizin icin cok büyük bir sıkıntı.
Bende TOP 10 GÜVENLİK ACIGI + 6 açık en büyük ve tehlikeli bir web sitesinde ilk bakılan 16 acıgı kapatan bir script yapıp burada paylasmak istedim.
Bu script sitenizi daha güvenli hagle getiricektir dostalarım.
SADECE 3 ADIMDA KURULUM:
1-İLK olarak bu kodu .htacces e ekleyin.
#Bu # Bu koddaki kullanıcıadi yerine kendi hostinginizde gözüken kullanıcı adını yazınız. php_value auto_prepend_file "/home/hostingkullaniciadi/public_html/init.php"Not: Eğer sunucunuzda PHP FastCGI olarak çalışıyorsa .htaccess içine php_value yazmanız 500 hatası verir. Bu durumda .htaccess yerine, sitenizin her sayfasının başına (veya ayar dosyanıza) direkt require_once 'init.php'; yazarak sistemi başlatabilirsiniz.
2-Sqlniz yoksa olusturun ve sorgu olarak bu kodu calıstırın.
CREATE TABLE IF NOT EXISTS `my_login_attempts` ( `id` int(11) NOT NULL AUTO_INCREMENT, `identifier` varchar(255) NOT NULL, `ip_address` varchar(45) NOT NULL, `user_agent` text DEFAULT NULL, `successful` tinyint(1) NOT NULL DEFAULT 0, `attempt_time` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3-Sonra bu init.php yi olusturun icine bunu yazın.
(Veri tabanı baglantısı kısmına kendi sql bilgilerinizi yazın. Ve site bilgilerine site urlnizi.)
<?php
define('CMS_ROOT', dirname(__FILE__));
// Sabitleri tanımlayın
define('SITE_URL', 'https://siteniz.com');
define('DEBUG_MODE', false); // Canlı sitede kesinlikle false olmalı
define('ENCRYPTION_KEY', 'buraya_çok_karışık_bir_şifre_yazın');
define('SESSION_LIFETIME', 1800); // 30 dakika
define('DB_PREFIX', 'my_'); // Veritabanı tablo ön eki
// Veritabanı bağlantısı
try {
$pdo = new PDO("mysql:host=localhost;dbname=veritabani_adiniz;charset=utf8mb4", "db_kullanici", "db_sifre", [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
} catch (PDOException $e) {
die("Veritabanı bağlantı hatası.");
}
// Güvenlik Sınıfını Dahil Et ve Ayarla
require_once CMS_ROOT . '/Security.php';
Security::setPdo($pdo); // SQL koruması için PDO'yu ver
Security::hardenPhp(); // PHP ayarlarını sertleştir
Security::secureSessionStart(); // Oturumu güvenli başlat
?> ardından security.php dosyanızı ana dizinde olusturun. Ve bunu icine yazın.
<?php
/**
* Gelişmiş Güvenlik Sınıfı (v2.0)
* OWASP Top 10 + Ek 6 Açık = Toplam 16 Açık Koruması
* SQL Injection Koruması: Dahili PDO Prepared Statements
*/
// Doğrudan erişimi engelle
if (!defined('CMS_ROOT')) {
die('Direct access denied');
}
class Security {
/**
* @var PDO|null Veritabanı bağlantı nesnesi
*/
private static $pdoInstance = null;
// ==========================================
// 1. SQL INJECTION KORUMASI (PDO WRAPPER)
// ==========================================
/**
* Sınıfın SQL işlemleri yapabilmesi için PDO nesnesini tanıtır.
* Kullanımı: Security::setPdo($pdo);
*/
public static function setPdo($pdo) {
if ($pdo instanceof PDO) {
self::$pdoInstance = $pdo;
} else {
self::logSecurityEvent('PDO_ERROR', 'critical', 'Geçersiz PDO nesnesi verildi.');
}
}
/**
* GÜVENLİ SQL SORGUSU
* Sadece bu metodu kullanarak veritabanına veri gönderin. Asla string birleştirme yapmayın.
*
* Örnek Kullanım:
* $kullanici = Security::safeQuery("SELECT * FROM users WHERE id = ? AND status = ?", [$id, 1])->fetch();
*/
public static function safeQuery($sql, $params = []) {
if (self::$pdoInstance === null) {
self::logSecurityEvent('PDO_MISSING', 'critical', 'PDO bağlantısı ayarlanmadan SQL sorgusu çalıştırılmaya çalışıldı.');
die('Sistem Hatası: Veritabanı bağlantısı güvenlik katmanına tanımlanmamış.');
}
try {
$stmt = self::$pdoInstance->prepare($sql);
$stmt->execute($params);
return $stmt;
} catch (PDOException $e) {
self::logSecurityEvent('SQL_ERROR', 'critical', $e->getMessage() . ' | SQL: ' . $sql);
// Canlı sitede detaylı hata gösterme
die('Veritabanı işlemi sırasında bir hata oluştu.');
}
}
// ==========================================
// 2. XSS KORUMASI (Çıktı Encoding)
// ==========================================
public static function escapeOutput($str) {
if (is_array($str)) {
return array_map([self::class, 'escapeOutput'], $str);
}
return htmlspecialchars((string)$str, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
// ==========================================
// 3. CSRF TOKEN YÖNETİMİ
// ==========================================
public static function generateCsrfToken() {
$lifetime = defined('CSRF_TOKEN_LIFETIME') ? CSRF_TOKEN_LIFETIME : 3600;
if (empty($_SESSION['csrf_token']) || empty($_SESSION['csrf_token_time']) || time() - $_SESSION['csrf_token_time'] > $lifetime) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
$_SESSION['csrf_token_time'] = time();
}
return $_SESSION['csrf_token'];
}
public static function verifyCsrfToken($token) {
if (empty($_SESSION['csrf_token']) || empty($token)) {
return false;
}
$lifetime = defined('CSRF_TOKEN_LIFETIME') ? CSRF_TOKEN_LIFETIME : 3600;
if (time() - $_SESSION['csrf_token_time'] > $lifetime) {
unset($_SESSION['csrf_token'], $_SESSION['csrf_token_time']);
return false;
}
return hash_equals($_SESSION['csrf_token'], $token);
}
public static function csrfField() {
$token = self::generateCsrfToken();
return '<input type="hidden" name="csrf_token" value="' . self::escapeOutput($token) . '">';
}
// ==========================================
// 4. SESSION FIXATION & HIJACKING KORUMASI
// ==========================================
public static function secureSessionStart() {
if (session_status() === PHP_SESSION_NONE) {
$fingerprint = self::getFingerprint();
session_start();
// Oturum ID'sini yenile (Fixation engelle)
if (!isset($_SESSION['initiated'])) {
session_regenerate_id(true);
$_SESSION['initiated'] = true;
$_SESSION['fingerprint'] = $fingerprint;
}
// Tarayıcı parmak izi kontrolü (Hijacking engelle)
if ($_SESSION['fingerprint'] !== $fingerprint) {
self::destroySession();
self::forceLogout('Oturum doğrulaması başarısız (Fingerprint mismatch).');
}
// Oturum süresi kontrolü
$lifetime = defined('SESSION_LIFETIME') ? SESSION_LIFETIME : 1800;
if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity']) > $lifetime) {
self::destroySession();
self::forceLogout('Oturum süresi doldu.');
}
$_SESSION['last_activity'] = time();
}
}
private static function getFingerprint() {
$parts = [
$_SERVER['HTTP_USER_AGENT'] ?? '',
substr($_SERVER['REMOTE_ADDR'] ?? '', 0, 7),
$_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? ''
];
return hash('sha256', implode('|', $parts));
}
public static function destroySession() {
$_SESSION = [];
if (ini_get('session.use_cookies')) {
$p = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000, $p['path'], $p['domain'], $p['secure'], $p['httponly']);
}
session_destroy();
}
public static function forceLogout($message) {
self::destroySession();
$siteUrl = defined('SITE_URL') ? SITE_URL : '/';
header('Location: ' . $siteUrl . '/admin/?hata=' . urlencode($message));
exit;
}
// ==========================================
// 5. BRUTE FORCE KORUMASI
// ==========================================
public static function checkBruteForce($identifier, $pdo = null) {
$db = $pdo ?? self::$pdoInstance;
if (!$db) return false; // DB yoksa kontrolü atla
$duration = defined('LOCKOUT_DURATION') ? LOCKOUT_DURATION : 900;
$max = defined('MAX_LOGIN_ATTEMPTS') ? MAX_LOGIN_ATTEMPTS : 5;
$prefix = defined('DB_PREFIX') ? DB_PREFIX : '';
$stmt = $db->prepare(
"SELECT COUNT(*) FROM " . $prefix . "login_attempts
WHERE identifier = ? AND attempt_time > (NOW() - INTERVAL " . intval($duration) . " SECOND)
AND successful = 0"
);
$stmt->execute([$identifier]);
return (int)$stmt->fetchColumn() >= $max;
}
public static function logLoginAttempt($identifier, $successful, $pdo = null) {
$db = $pdo ?? self::$pdoInstance;
if (!$db) return;
$ip = self::getClientIp();
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
$prefix = defined('DB_PREFIX') ? DB_PREFIX : '';
$stmt = $db->prepare(
"INSERT INTO " . $prefix . "login_attempts (identifier, ip_address, user_agent, successful, attempt_time)
VALUES (?, ?, ?, ?, NOW())"
);
$stmt->execute([$identifier, $ip, $ua, $successful ? 1 : 0]);
}
public static function clearLoginAttempts($identifier, $pdo = null) {
$db = $pdo ?? self::$pdoInstance;
if (!$db) return;
$prefix = defined('DB_PREFIX') ? DB_PREFIX : '';
$stmt = $db->prepare("DELETE FROM " . $prefix . "login_attempts WHERE identifier = ?");
$stmt->execute([$identifier]);
}
// ==========================================
// 6. DIRECTORY TRAVERSAL KORUMASI
// ==========================================
public static function sanitizePath($path) {
$path = str_replace(['../', '..\\', "\0"], '', $path);
$real = realpath($path);
if ($real === false || !str_starts_with($real, CMS_ROOT)) {
return false;
}
return $real;
}
// ==========================================
// 7. DOSYA YÜKLEME GÜVENLİĞİ
// ==========================================
public static function validateUpload($file) {
if (!isset($file) || $file['error'] !== UPLOAD_ERR_OK) {
return ['valid' => false, 'error' => 'Dosya yükleme hatası.'];
}
$maxSize = defined('UPLOAD_MAX_SIZE') ? UPLOAD_MAX_SIZE : 2097152;
if ($file['size'] > $maxSize) {
return ['valid' => false, 'error' => 'Dosya boyutu çok büyük (Maks ' . round($maxSize/1048576) . 'MB).'];
}
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$allowedExt = defined('ALLOWED_EXTENSIONS') ? ALLOWED_EXTENSIONS : ['jpg', 'jpeg', 'png', 'gif', 'pdf'];
if (!in_array($ext, $allowedExt)) {
return ['valid' => false, 'error' => 'Bu dosya türüne izin verilmiyor.'];
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
$allowedMime = defined('ALLOWED_MIME_TYPES') ? ALLOWED_MIME_TYPES : ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
if (!in_array($mime, $allowedMime)) {
return ['valid' => false, 'error' => 'Geçersiz dosya içeriği algılandı.'];
}
if (strpos($mime, 'image/') !== false) {
if (@getimagesize($file['tmp_name']) === false) {
return ['valid' => false, 'error' => 'Geçersiz görsel dosyası.'];
}
}
if (count(explode('.', $file['name'])) > 2) {
return ['valid' => false, 'error' => 'Çift uzantılı dosyalara izin verilmiyor.'];
}
return ['valid' => true, 'ext' => $ext, 'mime' => $mime];
}
public static function safeFileName($originalName) {
$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
return bin2hex(random_bytes(8)) . '_' . time() . '.' . $ext;
}
// ==========================================
// 8. COMMAND INJECTION KORUMASI
// ==========================================
public static function sanitizeCommand($input) {
$dangerous = ['&', '|', ';', '`', '$', '(', ')', '{', '}', '[', ']', '<', '>', '!', "\n", "\r"];
return str_replace($dangerous, '', $input);
}
// ==========================================
// 9. LDAP INJECTION KORUMASI
// ==========================================
public static function sanitizeLdap($input) {
$dangerous = ['\\', '*', '(', ')', "\x00", '&', '|', '!', '=', '<', '>'];
return str_replace($dangerous, '', $input);
}
// ==========================================
// 10. XML/XPATH INJECTION KORUMASI
// ==========================================
public static function sanitizeXml($input) {
// PHP 8.0+ libxml_disable_entity_loader kaldırıldı, ama eski sürümler için
if (PHP_VERSION_ID < 80000) {
libxml_disable_entity_loader(true);
}
libxml_use_internal_errors(true);
return htmlspecialchars($input, ENT_XML1 | ENT_QUOTES, 'UTF-8');
}
// ==========================================
// 11. SSRF KORUMASI
// ==========================================
public static function validateUrl($url) {
if (!filter_var($url, FILTER_VALIDATE_URL)) {
return false;
}
$parsed = parse_url($url);
$host = $parsed['host'] ?? '';
$ip = gethostbyname($host);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
return false;
}
if (!in_array(strtolower($parsed['scheme'] ?? ''), ['http', 'https'])) {
return false;
}
return true;
}
// ==========================================
// 12. OPEN REDIRECT KORUMASI
// ==========================================
public static function sanitizeRedirect($url) {
$siteUrl = defined('SITE_URL') ? SITE_URL : '/';
if (empty($url)) return $siteUrl;
if (str_starts_with($url, '/')) return $siteUrl . $url;
if (str_starts_with($url, $siteUrl)) return $url;
return $siteUrl;
}
// ==========================================
// 13. HTTP RESPONSE SPLITTING KORUMASI
// ==========================================
public static function sanitizeHeader($value) {
return str_replace(["\r", "\n", "%0a", "%0d", "\0"], '', $value);
}
// ==========================================
// 14. MASS ASSIGNMENT KORUMASI (DÜZELTİLDİ)
// ==========================================
/**
* Dizi içindeki verilerden SADECE izin verilen anahtarları dışarı çıkarır.
* Örnek: Security::filterFields($_POST, ['username', 'email']);
*/
public static function filterFields($data, $allowedFields) {
if (!is_array($data) || !is_array($allowedFields)) {
return [];
}
return array_filter(
$data,
fn($key) => in_array($key, $allowedFields),
ARRAY_FILTER_USE_KEY
);
}
// ==========================================
// 15. CLICKJACKING (Açıklama)
// ==========================================
// Clickjacking koruması .htaccess içinde şu şekilde yapılmalıdır:
// Header always append X-Frame-Options SAMEORIGIN
// Header always append X-Content-Type-Options nosniff
// ==========================================
// 16. SECURITY MISCONFIGURATION (PHP HARDENING)
// ==========================================
public static function hardenPhp() {
ini_set('expose_php', 0);
ini_set('allow_url_include', 0);
// NOT: allow_url_fopen'ı kapatmak API isteklerini (file_get_contents vs.) bozabilir.
// Eğer kesinlikle dışarıdan URL çağırmıyorsanız açabilirsiniz: ini_set('allow_url_fopen', 0);
ini_set('display_errors', (defined('DEBUG_MODE') && DEBUG_MODE) ? 1 : 0);
ini_set('log_errors', 1);
ini_set('session.cookie_httponly', 1);
ini_set('session.use_only_cookies', 1);
ini_set('session.use_strict_mode', 1);
// Sitede SSL varsa çerezleri de şifrele
if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
ini_set('session.cookie_secure', 1);
}
}
// ==========================================
// YARDIMCI FONKSİYONLAR
// ==========================================
/**
* Güvenli IP Alma (Proxy / Cloudflare desteği ile)
*/
public static function getClientIp() {
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP']) && filter_var($_SERVER['HTTP_CF_CONNECTING_IP'], FILTER_VALIDATE_IP)) {
return $_SERVER['HTTP_CF_CONNECTING_IP']; // Cloudflare
}
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$ip = trim($ips[0]);
if (filter_var($ip, FILTER_VALIDATE_IP)) return $ip; // Load Balancer
}
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
/**
* Genel Girdi Temizliği (XSS ve Null Byte için ön filtre)
* Not: Bu sadece ön temizliktir. Veritabanına eklerken safeQuery, ekrana basarken escapeOutput KULLANIN.
*/
public static function cleanInput($input) {
if (is_array($input)) {
return array_map([self::class, 'cleanInput'], $input);
}
$input = trim($input);
$input = stripslashes($input);
$input = str_replace("\0", '', $input); // Null byte temizle
return $input;
}
/**
* Parola Hashleme (Argon2id)
*/
public static function hashPassword($password) {
return password_hash($password, PASSWORD_ARGON2ID, [
'memory_cost' => 65536,
'time_cost' => 4,
'threads' => 3
]);
}
public static function verifyPassword($password, $hash) {
return password_verify($password, $hash);
}
/**
* AES-256-CBC Şifreleme / Çözme
*/
public static function encrypt($data) {
$key = defined('ENCRYPTION_KEY') ? hash('sha256', ENCRYPTION_KEY, true) : hash('sha256', 'varsayilan-gizli-anahtar', true);
$iv = random_bytes(16);
$encrypted = openssl_encrypt($data, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($iv . $encrypted);
}
public static function decrypt($data) {
$key = defined('ENCRYPTION_KEY') ? hash('sha256', ENCRYPTION_KEY, true) : hash('sha256', 'varsayilan-gizli-anahtar', true);
$data = base64_decode($data);
$iv = substr($data, 0, 16);
$decrypted = openssl_decrypt(substr($data, 16), 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
return $decrypted !== false ? $decrypted : null;
}
/**
* Rate Limiting (Dosya tabanlı)
*/
public static function rateLimit($key, $maxRequests = 60, $windowSeconds = 60) {
$file = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'rl_' . md5($key);
$now = time();
$data = @json_decode(@file_get_contents($file), true) ?: ['count' => 0, 'start' => $now];
if ($now - $data['start'] > $windowSeconds) {
$data = ['count' => 1, 'start' => $now];
} else {
$data['count']++;
}
@file_put_contents($file, json_encode($data), LOCK_EX);
if ($data['count'] > $maxRequests) {
http_response_code(429);
die('Çok fazla istek. Lütfen ' . $windowSeconds . ' saniye bekleyin.');
}
}
/**
* Güvenlik Loglama
*/
public static function logSecurityEvent($event, $level = 'info', $details = '') {
$logDir = CMS_ROOT . DIRECTORY_SEPARATOR . 'logs';
if (!is_dir($logDir)) {
@mkdir($logDir, 0700, true);
}
$logFile = $logDir . DIRECTORY_SEPARATOR . 'security_' . date('Y-m-d') . '.log';
$entry = sprintf(
"[%s] [%s] IP: %s | Event: %s | Details: %s\n",
date('Y-m-d H:i:s'),
strtoupper($level),
self::getClientIp(),
$event,
self::escapeOutput($details)
);
@file_put_contents($logFile, $entry, FILE_APPEND | LOCK_EX);
}
}
?> Bu sistem söyle calısır:
1. dosyanız login.php olsun php istegi alır.
2.htacces istegi init.php ye aktarır.
3. init.php 1 2 düzeltme ile sql incetion vs koruması yapıp security.php ye atar.
4.security.php de alınan istegi vs inceler ve daha güvenilir hagle getirir.
Bu kod sitenizin php istegine cevap vermesi yavaslatır biraz ama sitenizi kayda deger sekilde ölcüde güvenli yapar.
Bu sistem /100 hacklenmezsiniz vaadi vermez ama hackelenmesi önemli ölcüde zorlastırır.
Konuyu begendiyseniz konuya olumlu yorum yazmayı ve konuyu begenmeyi unnutmayın baskalarıda faydalansın.
