• 08-10-2024, 11:15:16
    #1
    Selamlar,

    Bir intranet sistemim var ve bu intranet sistemine kullanıcılar AD ile senkron olarak giriş yapıyor.

    Giriş yaptıktan sonra bazı bölümlere ait yönetim panelleri var ve bu panellere menü üzerinden ulaşılıyor, şuan local bir db üzerinde tutulan kullanıcı bilgileri ile giriş yapıyorlar ve güvenilir olduğunu düşünmüyorum.

    Tasarladığım yapıda AD üzerinde gruplar oluşturdum ve eğer intranet sistemine giren kullanıcı bu gruba üye ise bu ekrana girebilecek fakat değilse geri yönlenecek. Bu alanlar önemli alanlar (duyuru paneli gibi) olduğu için güvenliği üst düzeyde tutmak istiyorum.

    Yapay zekadan yardım alarak yapmış olduğum ve tasarladığım giriş sistemi aşağıdaki gibidir, önerileriniz var mıdır?

    Güvenlik açısından girmiş olduğu panel üzerindeki her sayfada bu kontrolü yine yaptıracağım.

    <?php
        ini_set('session.use_strict_mode', 1);
        
        ini_set('session.cookie_httponly', 1);
        ini_set('session.cookie_secure', 1);
        ini_set('session.cookie_samesite', 'Strict');
            
        error_reporting(0);
        ini_set('display_errors', 0);
        function logError($message) {
            error_log("[" . date("Y-m-d H:i:s") . "] " . $message . "\n", 3, "/var/www/cloud.domain.com.tr/islemler/testpanel/process.log");
        }
        function secureExit($message = '') {
            session_destroy();
            if (!empty($message)) {
                logError($message);
            }
            header('Location: ../../login.php');
            exit();
        }
        try {
            if (session_status() == PHP_SESSION_NONE) {
                session_start();
            }
            
            if (!isset($_SESSION['initialized']) || !$_SESSION['initialized']) {
                session_regenerate_id(true);
                $_SESSION['initialized'] = true;
                $_SESSION['client_ip'] = $_SERVER['REMOTE_ADDR'];
                $_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
                $_SESSION['last_activity'] = time();
            }
            
            if ($_SESSION['client_ip'] !== $_SERVER['REMOTE_ADDR'] || 
                $_SESSION['user_agent'] !== $_SERVER['HTTP_USER_AGENT']) {
                logError("Olası session hijacking denemesi tespit edildi.");
                secureExit('Güvenlik ihlali tespit edildi.');
            }
            $ldap_config = [
                'host' => 'IP',
                'base_dn' => 'DN',
                'username' => 'USER',
                'password' => 'PW',
                'required_group' => 'CN'
            ];
            if (!isset($_SESSION['username']) || empty($_SESSION['username'])) {
                logError("Username session bulunamadı.");
                secureExit('Oturum bulunamadı.');
            }
            if ($_SERVER['REQUEST_METHOD'] === 'POST') {
                if (!isset($_SESSION['csrf_token']) || !isset($_POST['csrf_token']) || 
                    $_SESSION['csrf_token'] !== $_POST['csrf_token']) {
                    logError("CSRF token doğrulaması başarısız: " . $_SESSION['username']);
                    secureExit('CSRF token doğrulaması başarısız.');
                }
                $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
            }
            function verifyGroupMembership($username, $config) {
                $ldap_conn = ldap_connect($config['host']);
                if (!$ldap_conn) {
                    throw new Exception('LDAP sunucusuna bağlanılamadı.');
                }
                ldap_set_option($ldap_conn, LDAP_OPT_PROTOCOL_VERSION, 3);
                ldap_set_option($ldap_conn, LDAP_OPT_REFERRALS, 0);
                ldap_set_option($ldap_conn, LDAP_OPT_NETWORK_TIMEOUT, 5);
                if (!ldap_bind($ldap_conn, $config['username'], $config['password'])) {
                    throw new Exception('LDAP kimlik doğrulaması başarısız.');
                }
                $filter = "(sAMAccountName=" . ldap_escape($username, "", LDAP_ESCAPE_FILTER) . ")";
                $search = ldap_search($ldap_conn, $config['base_dn'], $filter, ['dn']);
                
                if (!$search || ldap_count_entries($ldap_conn, $search) !== 1) {
                    throw new Exception('Kullanıcı bulunamadı veya birden fazla sonuç döndü.');
                }
                $user_entry = ldap_first_entry($ldap_conn, $search);
                $user_dn = ldap_get_dn($ldap_conn, $user_entry);
                $group_filter = "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:=$user_dn))";
                $group_search = ldap_search($ldap_conn, $config['base_dn'], $group_filter, ['dn']);
                
                $entries = ldap_get_entries($ldap_conn, $group_search);
                $has_access = false;
                for ($i = 0; $i < $entries["count"]; $i++) {
                    if (strcasecmp($entries[$i]["dn"], $config['required_group']) === 0) {
                        $has_access = true;
                        break;
                    }
                }
                ldap_close($ldap_conn);
                return $has_access;
            }
            if (!verifyGroupMembership($_SESSION['username'], $ldap_config)) {
                logError("Yetkisiz erişim denemesi: " . $_SESSION['username']);
                header("Location: https://cloud.domain.com.tr");
                exit();
            } else {
                logError("Kullanıcı girişi başarılı: " . $_SESSION['username']);
            }
            $allowed_session_keys = [
                'name', 'surname', 'unvan', 'username', 'mail', 
                'telephonenumber', 'comment', 'company', 'department', 
                'mobile', 'physicaldeliveryofficename'
            ];
            foreach ($allowed_session_keys as $key) {
                $$key = isset($_SESSION[$key]) ? htmlspecialchars($_SESSION[$key], ENT_QUOTES, 'UTF-8') : '';
            }
            if (!isset($_SESSION['csrf_token'])) {
                $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
            }
        } catch (Exception $e) {
            logError($e->getMessage());
            secureExit();
        }
    ?>
  • 08-10-2024, 11:28:27
    #2
    Kodlarda genel olarak bi amatorlük ve karışıklık var ama bunlar çalışmasına engel şeyler değil. Burda da verebileceğim tek tavsiye session verilerini encrypt/decrypt ederek kullanman. Atarken encrypt, okurken decrypt edersen bi tık dhaa güvenli olur. Her sayfa da bu kontrolleri yapmak sayfa performansını düşürebilir o durumda örnek olarak 5dk da bir kontrol yapacak şekilde güncelleyebilirsin.
  • 08-10-2024, 14:22:01
    #3
    Büyük bela bu Keycloak falan kullan zaten çoğu full-fledged auth çözümünün upstream IdP desteği oluyor, AD'yi oraya bağlarsın. AuthN kolay da, AuthZ çok daha karmaşık bir konu. Orada policy engine gibi mevzular devreye giriyor, onu da muhtemelen Intranet'teki uygulamaların yanına Envoy ve OPA'yı sidecar olarak yerleştirip falan çözebilirsin. Olmadı uygulama kendisine paslanan JWT'ı validate ederken claim'leri kendi içinde çözümleyip ona göre yanıt döner, ama haliyle bu durumda her uygulamanın kodunda değişiklik yapılması gerekiyor.

    Benim sizin koddan anladığım her uygulama PHP ile yazılma, session'lar da ortak. Her şeyi merkezileştirmeniz sizin yararınıza olur, bu pek akıl işi gelmedi bana. Bir de auth server'dan geçtikten sonra bir daha niye uygulamalara ayrı ayrı auth olunması gerekiyor onu anlamadım, bu zaman AD ve SSO kullanmanın mantığına aykırı bu şekilde.
  • 08-10-2024, 15:02:03
    #4
    kazimolmez adlı üyeden alıntı: mesajı görüntüle
    Kodlarda genel olarak bi amatorlük ve karışıklık var ama bunlar çalışmasına engel şeyler değil. Burda da verebileceğim tek tavsiye session verilerini encrypt/decrypt ederek kullanman. Atarken encrypt, okurken decrypt edersen bi tık dhaa güvenli olur. Her sayfa da bu kontrolleri yapmak sayfa performansını düşürebilir o durumda örnek olarak 5dk da bir kontrol yapacak şekilde güncelleyebilirsin.
    berkantipek adlı üyeden alıntı: mesajı görüntüle
    Büyük bela bu Keycloak falan kullan zaten çoğu full-fledged auth çözümünün upstream IdP desteği oluyor, AD'yi oraya bağlarsın. AuthN kolay da, AuthZ çok daha karmaşık bir konu. Orada policy engine gibi mevzular devreye giriyor, onu da muhtemelen Intranet'teki uygulamaların yanına Envoy ve OPA'yı sidecar olarak yerleştirip falan çözebilirsin. Olmadı uygulama kendisine paslanan JWT'ı validate ederken claim'leri kendi içinde çözümleyip ona göre yanıt döner, ama haliyle bu durumda her uygulamanın kodunda değişiklik yapılması gerekiyor.

    Benim sizin koddan anladığım her uygulama PHP ile yazılma, session'lar da ortak. Her şeyi merkezileştirmeniz sizin yararınıza olur, bu pek akıl işi gelmedi bana. Bir de auth server'dan geçtikten sonra bir daha niye uygulamalara ayrı ayrı auth olunması gerekiyor onu anlamadım, bu zaman AD ve SSO kullanmanın mantığına aykırı bu şekilde.
    Öneriler için teşekkürler.

    @berkantipek; Uygulama tamamen generic PHP ile yazılmış durumda. Keycloak ile bir identify server oluşturup bu sunucu üzerinden mi doğrulama yaptırmamızı öneriyorsunuz?
  • 08-10-2024, 18:28:21
    #5
    turansonkaya adlı üyeden alıntı: mesajı görüntüle
    Öneriler için teşekkürler.

    @berkantipek; Uygulama tamamen generic PHP ile yazılmış durumda. Keycloak ile bir identify server oluşturup bu sunucu üzerinden mi doğrulama yaptırmamızı öneriyorsunuz?
    Ben sandim ki agda birden fazla uygulama olacak. Eger tek uygulama varsa gerek yok, kendi auth mantiginizi orada yazabilirsiniz ama tavsiyem bir router ve dispatcher yapisi kullanin Symfony'den falan PSR standartina uygun. Grup tabanli RBAC kolay olur kendiniz cozersiniz, eger ki authZ mantigi karmasiklasirsa o asamada belki Zanzibar vs. kullanan bir auth server'a gecebilirsiniz.