Bugün kendi sitemde bir kodu güncellerken, xxx.com yerine refleksle yyy.com adresine gittim. Ekranda bambaşka bir index. Yanlış site mi? diye bakmadım değil ama URL bana ait. Birkaç subdomaine daha baktım; bazılarında aynı sayfa, bazılarında yok. Kısaca biri dolaşmış, bazı klasörlere index.html basmış; index.phpyi de silmiş. Üstüne bir de WhatsApp numarası bırakmış Kibarca söyleyeyim: bu uyarı değil, hack.

Aslında iyi de oldu: hesap içinde testler, zipler, eski projeler derken tam bir çöp eve dönmüştü. Bu bahaneyle hepsini toparladım, temiz bir kurulum ve kalıcı sertleştirme yaptım. Bu yazıda baştan sona ne yaptığımı, paylaşımlı hosting ortamında nelerle önlem aldığımı ve isteyenin tek komutla tüm hesabı sertleştirebileceği aracı anlatıyorum.
%100 sihirli çözüm yok. Ama yazma yüzeyini küçültüp (izinler), panel girişlerini kısıtlayıp (WAF/IP), dosya değişimini izleyip (cron) ve eski çöpleri kaldırınca risk ciddi düşüyor.
Olayın kısa özeti (benim tarafta neler oldu?)
- Belirti: Birkaç yerde aynı korsan index. index.php yok.
- Tahmin: Bir yükleme ya da açık üzerinden yazma yetkisi alınmış; belirli klasörlere index basılmış, PHP giriş dosyası silinmiş.
- Not: Tamamında değil, seçmece. Bu da otomatik gezinen bir script ya da elle seçilmiş klasör ihtimalini güçlendiriyor.
İlk yardım (ben nasıl toparladım?)
- Görünürlüğü düşür
Cache/CDN varsa temizle. Çok kritikse kısa süreli bakım sayfası bırak. - Yedek al
Dosya + veritabanını mevcut haliyle arşivle (kanıt da elinde kalır). - Defacei etkisizleştir
Kötü index.html → *.bak. Varsa index.phpyi öne al (DirectoryIndex index.php index.html).Bazı klasörlerde gerçekten index yoksa 404 normal; oraya orijinal giriş dosyanı geri koyman gerekir.
- Backdoor taraması
Klasik imzalar (eval(, base64_decode(), tek satır obfuscation vs.) ve resim içine gömülü PHP kontrolü. - Parolalar & erişim
cPanel/FTP/DB/Admin tüm şifreleri yenile. Mümkünse her siteye ayrı FTP ve homeu o klasöre kilitle. ModSecurity açık. - Güncelle + çöpleri kaldır
CMS/tema/eklentiler güncel olsun. Kullanmadığın projenin web erişimini kes ya da arşive taşı.
Paylaşımlı hostingte tek komutla toplu sertleştirme
Ben bütün bu işleri sürekli elle yapmak yerine, ev dizinini baştan aşağı dolaşıp olası site köklerine (index bulunan klasörlere) aşağıdakileri otomatik uygulayan küçük bir CLI yazdım:
- .htaccess:
- DirectoryIndex index.php index.html
- Options -Indexes
- uploads / images / storage / cache / tmp gibi klasörlerde PHPyi kapatan .htaccess
- .user.ini ile auto_prepend_file / auto_append_file reset + temel php.ini sıkılaştırması
- (Opsiyonel) Admin dizinlerini IPye kilitleme (wp-admin, admin, panel, backend, administrator, cp)
- (Opsiyonel) Lock/Unlock: kritik dosyaları 444 yapar; güncellemeden önce unlock, sonra tekrar lock
- (Opsiyonel) Audit: Şu tarihten beri ne değişmiş? raporu
🧩 home_hardener_cli.php
<?php
/**
* home_hardener_cli.php Paylaşımlı hosting için tek komutluk güvenlik sertleştirici
* YAPABİLDİKLERİ:
* - DirectoryIndex + -Indexes ekler
* - uploads/images/storage/cache/tmp/assets/uploads gibi klasörlerde PHP'yi kapatır
* - .user.ini yazar (auto_prepend/append reset + temel ini)
* - Admin dizinlerini IP'ye kilitleyebilir (wp-admin, admin, panel, backend, administrator, cp)
* - Kilitleme: kritik dosyaları 444 yapar (unlock ile geri al)
* - Audit: yakın zamanda değişenleri raporlar
*
* KULLANIM:
* php home_hardener_cli.php --dry-run
* php home_hardener_cli.php --apply [--allow-ip=1.2.3.4,5.6.7.8] [--lock]
* php home_hardener_cli.php --unlock
* php home_hardener_cli.php --audit [--since=YYYY-MM-DD]
*/
ini_set('memory_limit','768M');
ini_set('max_execution_time','900');
$HOME = dirname(__DIR__); // /home/USERNAME
$NOW = date('Ymd_His');
$LOG = $HOME.'/tools/hardener_log_'.$NOW.'.txt';
$argv = $argv ?? [];
$ARGS = implode(' ', $argv);
// Flags
$DRY = in_array('--dry-run', $argv, true) || (!$argv || count($argv)===1);
$APPLY = in_array('--apply', $argv, true);
$LOCK = in_array('--lock', $argv, true);
$UNLOCK = in_array('--unlock', $argv, true);
$AUDIT = in_array('--audit', $argv, true);
// Params
$ALLOW_IPS = [];
$SINCE_TS = null;
foreach ($argv as $arg) {
if (strpos($arg,'--allow-ip=')===0) {
$list = substr($arg, 11);
$ALLOW_IPS = array_values(array_filter(array_map('trim', explode(',', $list))));
}
if (strpos($arg,'--since=')===0) {
$d = substr($arg, 8);
$t = strtotime($d.' 00:00:00');
if ($t) $SINCE_TS = $t; else { fwrite(STDERR,"HATA: --since tarihi hatalı\n"); exit(2); }
}
}
if ($UNLOCK && ($APPLY || $LOCK)) {
fwrite(STDERR,"HATA: --unlock ile --apply/--lock birlikte kullanılamaz.\n");
exit(2);
}
// Kalıplar (istenirse düzenle)
$skipDirs = [
'.cpanel','.trash','.cagefs','etc','mail','logs','ssl','tmp','.softaculous',
'softaculous_backups','composer','.config','.npm','.cache','.local',
'node_modules','vendor','storage','cache','.git','.well-known','tools','_baseline'
];
$uploadDirNames = [
'uploads','upload','images','img','assets','assets/uploads','assets/images','public/uploads',
'storage','cache','tmp','writable','var','var/cache'
];
$adminDirNames = ['wp-admin','admin','administrator','panel','backend','cp'];
// Yardımcılar
function inSkipped($path,$skipDirs){ foreach($skipDirs as $s){ if(stripos($path,"/$s/")!==false) return true; } return false; }
function ensureDir($p){ if(!is_dir($p)) @mkdir($p,0755,true); }
function hasLine($content,$needle){ return (strpos($content,$needle)!==false); }
function prependIfMissing($file,$line){
$c = is_file($file)? @file_get_contents($file):'';
if(strpos($c,$line)===false){
$c = $line."\n".$c;
return @file_put_contents($file,$c)!==false;
}
return true;
}
function appendIfMissing($file,$chunk){
$c = is_file($file)? @file_get_contents($file):'';
if(strpos($c,$chunk)===false){
$c = $c."\n".$chunk."\n";
return @file_put_contents($file,$c)!==false;
}
return true;
}
function chmodSafe($path,$mode){ @chmod($path,$mode); }
function fileList($dir){
$res=[];
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS));
foreach($it as $f){ if($f->isFile()) $res[]=$f->getPathname(); }
return $res;
}
// LOG
$fh = @fopen($LOG,'w'); if(!$fh){ fwrite(STDERR,"HATA: Log açılamadı: $LOG\n"); exit(1); }
$log = function($s) use($fh){ fwrite($fh,$s); };
// Tüm HOMEu dolaş (skipler hariç) ve olası docrootları bul
$docroots = [];
$rii = new RecursiveIteratorIterator(
new RecursiveCallbackFilterIterator(
new RecursiveDirectoryIterator($HOME, FilesystemIterator::SKIP_DOTS),
function($cur) use($skipDirs){ if($cur->isDir()){ if(in_array($cur->getFilename(),$skipDirs,true)) return false; } return true; }
),
RecursiveIteratorIterator::SELF_FIRST
);
foreach($rii as $file){
if($file->isDir()) continue;
$path = $file->getPathname();
if(inSkipped($path,$skipDirs)) continue;
$base = strtolower($file->getFilename());
if($base==='index.php' || $base==='index.html' || $base==='index.htm'){
$dir = $file->getPath();
$docroots[$dir] = 1;
}
}
$docroots = array_keys($docroots);
// Rapor başı
$log("=== HOME HARDENER $NOW ===\nHOME: $HOME\nARGS: $ARGS\n");
$log("Bulunan muhtemel docroot sayısı: ".count($docroots)."\n");
$log("Apply: ".($APPLY?'YES':'NO')." Lock: ".($LOCK?'YES':'NO')." Unlock: ".($UNLOCK?'YES':'NO')." Audit: ".($AUDIT?'YES':'NO')."\n\n");
if($AUDIT){
$sinceTxt = $SINCE_TS? date('Y-m-d H:i:s',$SINCE_TS) : 'YOK';
$log("== AUDIT MODE (since=$sinceTxt) ==\n");
foreach($docroots as $dir){
$log("[DIR] $dir\n");
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS));
foreach($it as $f){
if($f->isDir()) continue;
$p = $f->getPathname();
if($SINCE_TS && filemtime($p)<$SINCE_TS) continue;
$log(" ".date('Y-m-d H:i:s',filemtime($p))." | $p\n");
}
}
$log("== AUDIT END ==\n");
fclose($fh);
echo "Audit tamamlandı. Log: $LOG\n";
exit(0);
}
// İçerik şablonları
$htaccessHeader = "Options -Indexes\nDirectoryIndex index.php index.html\n";
$htaccessNoPHP = <<<HTA
# PHP yürütmeyi kapat
<FilesMatch "\\.(php|phar|phtml|php[0-9]?)$">
Require all denied
</FilesMatch>
HTA;
$htaccessAdminTpl = function(array $ips){
$ips = array_map('trim',$ips);
$authz = "";
if($ips){
$authz .= "<IfModule mod_authz_core.c>\n";
foreach($ips as $ip){ $authz .= " Require ip $ip\n"; }
$authz .= "</IfModule>\n";
$authz .= "<IfModule !mod_authz_core.c>\n Order deny,allow\n Deny from all\n";
foreach($ips as $ip){ $authz .= " Allow from $ip\n"; }
$authz .= " Satisfy any\n</IfModule>\n";
}
// Cloudflare header varsa (opsiyonel) - basit kontrol
$cf = "";
if($ips){
$pat = implode('|', array_map(function($ip){ return preg_quote($ip,'/'); }, $ips));
$cf = "RewriteEngine On\n";
$cf .= "RewriteCond %{HTTP:CF-Connecting-IP} !^($pat)\$\n";
$cf .= "RewriteRule ^ - [F]\n";
}
return trim($authz . $cf);
};
$userIni = <<<INI
; --- SECURITY BASELINE ---
auto_prepend_file=
auto_append_file=
display_errors=Off
expose_php=Off
session.cookie_httponly=1
session.use_strict_mode=1
; NOT: disable_functions bazı hostlarda override edilemez / uygulama kırabilir
; çok agresif olmadan bir set:
disable_functions=system,passthru,shell_exec,proc_open,popen,pcntl_exec,expect_popen
INI;
// İşlem döngüsü
$changedDirs = 0;
foreach($docroots as $dir){
$changed = false;
$hta = $dir.'/.htaccess';
$ui = $dir.'/.user.ini';
// 1) .htaccess header
if($APPLY){
ensureDir($dir);
if(prependIfMissing($hta, $htaccessHeader)){
$changed = true;
$log("[HTA] DirectoryIndex/-Indexes uygulandı: $hta\n");
}
} else {
$log("[DRY] .htaccess eklenecek: $hta\n");
}
// 2) .user.ini
if($APPLY){
if(!is_file($ui)) @file_put_contents($ui, $userIni);
else {
$c = @file_get_contents($ui) ?: '';
if(strpos($c,'auto_prepend_file=')===false || strpos($c,'auto_append_file=')===false){
$c = $userIni . "\n\n" . $c;
@file_put_contents($ui, $c);
}
}
$changed = true;
$log("[INI] .user.ini yazıldı/güncellendi: $ui\n");
} else {
$log("[DRY] .user.ini eklenecek: $ui\n");
}
// 3) uploads/images/... klasörlerinde PHP kapat
foreach($uploadDirNames as $name){
$cand = $dir.'/'.$name;
if(is_dir($cand) && !inSkipped($cand,$skipDirs)){
$htaU = $cand.'/.htaccess';
if($APPLY){
ensureDir($cand);
if(!is_file($htaU) || strpos(@file_get_contents($htaU)?:'','Require all denied')===false){
@file_put_contents($htaU, $htaccessNoPHP);
$log("[HTA] PHP kapatma: $htaU\n");
}
} else {
$log("[DRY] PHP kapatma .htaccess eklenecek: $htaU\n");
}
}
}
// 4) Admin dizinleri IP kilidi
if($APPLY && $ALLOW_IPS){
foreach($adminDirNames as $ad){
$adm = $dir.'/'.$ad;
if(is_dir($adm)){
$htaA = $adm.'/.htaccess';
$block = $htaccessAdminTpl($ALLOW_IPS);
if($block){
$ok = appendIfMissing($htaA, $block);
if($ok){ $log("[HTA] Admin IP kısıtı: $htaA\n"); }
}
}
}
} elseif(!$APPLY && $ALLOW_IPS){
$log("[DRY] Admin IP kısıtı (".$dir.") uygulanacak.\n");
}
// 5) LOCK / UNLOCK
if($LOCK || $UNLOCK){
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS));
foreach($it as $f){
if($f->isDir()) continue;
$p = $f->getPathname();
if(inSkipped($p,$skipDirs)) continue;
$ext = strtolower(pathinfo($p, PATHINFO_EXTENSION));
$base= strtolower(basename($p));
// Kilitlenecek tipler: php, phtml, phar, inc, htaccess, user.ini, ini
$lockable = in_array($ext, ['php','phtml','phar','inc','ini']) || $base==='.htaccess' || $base==='.user.ini';
// Yazılabilir olması gereken klasörleri atla:
$rel = strtolower(str_replace($dir.'/','',$p));
$isUpload = false;
foreach($uploadDirNames as $name){ if(strpos($rel, strtolower($name).'/')===0){ $isUpload=true; break; } }
if($isUpload) continue;
if($lockable){
if($LOCK){
chmodSafe($p, 0444);
} else { // unlock
// .htaccess ve .user.ini hariç 0644 yap
if($base==='.htaccess' || $base==='.user.ini') chmodSafe($p, 0444);
else chmodSafe($p, 0644);
}
}
}
$log(($LOCK?"[LOCK] ":"[UNLOCK] ").$dir." tamamlandı.\n");
}
if($changed) $changedDirs++;
}
// HOME köküne de bir .user.ini bırak (auto_prepend reset için)
$homeUserIni = $HOME.'/.user.ini';
if($APPLY){
if(!is_file($homeUserIni)) @file_put_contents($homeUserIni, $userIni);
else {
$c=@file_get_contents($homeUserIni)?:'';
if(strpos($c,'auto_prepend_file=')===false) @file_put_contents($homeUserIni, $userIni."\n".$c);
}
$log("[INI] HOME .user.ini güncellendi: $homeUserIni\n");
} else {
$log("[DRY] HOME .user.ini eklenecek: $homeUserIni\n");
}
$log("\n== DONE ==\n");
fclose($fh);
echo ($APPLY?'Uygulandı':'Dry-run tamam') . ". Log: $LOG\n";
if($LOCK) echo "Kilitler açılana kadar (--unlock) kritik dosyalar 444.\n";
if($UNLOCK) echo "Kilitler açıldı (kritikler hariç 644).\n";🧩 Kullanım örnekleri
# 1) Sadece rapor (neler yapacağını gösterir) php /home/mebulart/tools/home_hardener_cli.php --dry-run # 2) Tüm sertleştirmeleri uygula (IP kısıtı DAHİL) php /home/mebulart/tools/home_hardener_cli.php --apply --allow-ip=SENİN.IP.V4,ANOTHER.IP.V4 --lock # 3) Sadece IP kuralı olmadan uygula php /home/mebulart/tools/home_hardener_cli.php --apply # 4) Kilitleri aç (güncelleme/deploy için) php /home/mebulart/tools/home_hardener_cli.php --unlock # 5) Son 7 günde değişenleri raporla php /home/mebulart/tools/home_hardener_cli.php --audit --since=2025-08-30
Neden işe yarıyor?
- DirectoryIndex: Birisi index.html bıraksa bile index.php öncelikli.
- Uploadsta PHP kapalı: En sık vektöryazılabilir klasöre shell at, çalıştır. Çalışamazsa oyun bitiyor.
- .user.ini reset: auto_prepend_file ile her isteğin başına zararlı dosya ekleme numarası burada patlar.
- Admin IP kilidi: Brute force ve admin-ajax sömürülerini ciddi azaltır.
- Lock (444): Üstüne yazmayı zorlaştırır. Güncelleme/deploy öncesi unlock, sonra lockdisiplin meselesi.
- Audit: Bu gece ne değişmiş? sorusunun cevabı tek komut.
Benim yaşadığım sorun çok büyük ihtimalle bununla alakalıydı bir bayilik aldım ve xml yerine onların scriptlerini kurmamı istediler ve muhtemelen yazılım içerisinde ki şifreli kod aracılığıyla bu backdoor atıldı. Bundan emin olamasam da şüpheleniyorum. Umarım yazı birilerine yardımcı olur.
İyi forumlar.