• 07-09-2025, 02:55:20
    #1
    Herkese merhaba,
    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.php’yi 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, zip’ler, eski projeler derken tam bir “çöp ev”e 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?)

    1. Görünürlüğü düşür
      Cache/CDN varsa temizle. Çok kritikse kısa süreli “bakım” sayfası bırak.
    2. Yedek al
      Dosya + veritabanını mevcut haliyle arşivle (kanıt da elinde kalır).
    3. Deface’i etkisizleştir
      Kötü index.html → *.bak. Varsa index.php’yi öne al (DirectoryIndex index.php index.html).
      Bazı klasörlerde gerçekten index yoksa 404 normal; oraya orijinal giriş dosyanı geri koyman gerekir.
    4. Backdoor taraması
      Klasik imzalar (eval(, base64_decode(), tek satır obfuscation vs.) ve resim içine gömülü PHP kontrolü.
    5. Parolalar & erişim
      cPanel/FTP/DB/Admin tüm şifreleri yenile. Mümkünse her siteye ayrı FTP ve home’u o klasöre kilitle. ModSecurity açık.
    6. 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 PHP’yi kapatan .htaccess
    • .user.ini ile auto_prepend_file / auto_append_file reset + temel php.ini sıkılaştırması
    • (Opsiyonel) Admin dizinlerini IP’ye 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
    Avantajı: cPanel Cron’a koyuyorsun, tek dosya tüm hesabı dolaşıp işini görüyor.


    🧩 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 HOME’u dolaş (skip’ler hariç) ve olası docroot’ları 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.
    • Uploads’ta PHP kapalı: En sık vektör—“yazı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 lock—disiplin meselesi.
    • Audit: “Bu gece ne değişmiş?” sorusunun cevabı tek komut.

    Dediğim gibi tam bir çözüm değil sadece biraz daha kontrolü sağlar yapacağınız en iyi korumalar ise başkalarından aldığınız scriptleri, kodları olabildiğince kontrol etmek, sunucunuza ek korumalar sağlamak, w*rez yazılımlar kullanmamak, kullanmadığınız dosyaları silmek, yazılımlarınızı güncel tutmak, *size bayilik adı altında kendi scriptlerini kurdurtmak isteyenlere dikkat etmek!


    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.
  • 07-09-2025, 02:56:24
    #2
    Güzel paylaşım elinize sağlık.
  • 07-09-2025, 02:59:07
    #3
    Teşekkürler hocam.
  • 07-09-2025, 02:59:44
    #4
    Geçmiş olsun, tekrardan dosyalarınızı kontrol etmenizi öneririz.
  • 07-09-2025, 04:56:08
    #5
    Elinize sağlık ama yapay zeka içeriğinde verilen herşeyi yapmanız da son kullanıcıyı kurtarmıyor maalesef. Temiz kodlama, güncel yazılım kullanımı ve gizliliğe dayalı erişim kısıtlaması yapan herkes rahat ve problemsiz kullanıyor sunucu ve hostinglerini.
  • 07-09-2025, 05:29:09
    #6
    Imufy360 + cloud linux cf bitti gitti hiç bişey yapmanıza gerek yok shell upload edemez hiç bir şekilde rootlanamaz sunucu. paylaşımlı hostsa ztn genelde ücretsiz bunlar oluyor artık shell atma olayı bitti.