İçeriğe geç
KURUMSAL Kurumsal teknoloji · İşletmenize özel mühendislik Kurumsal Yazılım · Yönetilen Sunucular · Kurumsal E-posta · LiteSpeed + CloudLinux · Imunify360 Güvenlik · cPanel Yönetim · Uygun pakette 3 Gbps'ye kadar ağ profili
AIOR

DirectAdmin ve CloudLinux'ta disable_functions Listesi Nasıl Düzenlenir

  • Konu başlatıcısı Konu başlatıcısı Aior
  • Başlangıç tarihi Başlangıç tarihi

Aior

Administrator

Yetkili üye
Katıldı
2 Nis 2023
Mesajlar
929
Tepki puanı
2
Puan
18
Yaş
41
Konum
Turkey
Web sitesi
aior.com
Konu sahibi

PHP'nin disable_functions direktifi exec, shell_exec ve proc_open gibi fonksiyonları PHP'den kaldırır; böylece ele geçirilmiş bir eklenti sunucuda doğrudan kabuk komutu (shell command) çalıştıramaz. CloudLinux kurulu bir DirectAdmin sunucusunda ise PHP aynı anda birkaç yerde çalışır: /usr/local/phpXX altındaki DirectAdmin derlemeleri, /opt/alt/phpXX altındaki CloudLinux alt-php sürümleri, PHP-FPM havuzları (pool), LiteSpeed'in lsphp süreçleri ve komut satırı (CLI). Bunların her biri farklı bir listeyle çalışıyor olabilir. Yeniden yazdığımız bu rehber, önceki sürümümüzde de bulunan yaygın bir hatayı düzeltiyor: liste, tek bir hesap için .user.ini dosyası, ini_set() ya da alan adı bazındaki bir PHP ayar ekranı üzerinden gevşetilemez. Rehber ayrıca PHP 8 ile neyin değiştiğini, gerçekte geçerli olan yapılandırmanın nasıl bulunacağını ve exec isteyen tek bir hesabın, fonksiyonu herkese açmadan nasıl ele alınacağını anlatıyor. Örneklerde PHP 8.3 kullanıldı; 83 yerine kendi sürümünüzü yazın.

Kısaca:
  • Genel listeyi php.ini'de tanımlayın; DirectAdmin'de bu işi CustomBuild'in secure_php seçeneğine bırakın. .user.ini, .htaccess ve ini_set() bu direktifi değiştiremez.
  • Bir PHP-FPM havuzu php.ini listesine yalnızca fonksiyon ekleyebilir, listeden fonksiyon çıkaramaz.
  • PHP 8.0'dan beri kapatılmış bir fonksiyon hiç yokmuş gibi davranır; çağrı "Call to undefined function" hatasıyla biter.
  • CLI, PHP-FPM, lsphp ve alt-php farklı dosyalar okuyabilir; siteye gerçekten hizmet veren işleyiciyi (handler) kontrol edin.
  • Tek bir hesap exec veya proc_open istiyorsa listeyi herkes için kısaltmak yerine o hesaba ayrı bir liste ya da ayrı bir sunucu verin.

disable_functions ne yapar, nerede ayarlanabilir?​

Direktif, PHP'nin yerleşik fonksiyonlarından (internal functions) oluşan, virgülle ayrılmış bir liste alır. PHP bu listeyi süreç başlarken okur ve o andan itibaren bu fonksiyonlar ortadan kalkar; PHP koduyla tanımlanmış fonksiyonlar etkilenmez. PHP kılavuzu direktifi INI_SYSTEM olarak işaretler ve php.ini içinde tanımlanması gerektiğini belirtir. Başarısız denemelerin çoğu buradan kaynaklanır: .user.ini dosyası, .htaccess satırı ya da ini_set() çağrısı işlendiğinde fonksiyon tablosu çoktan oluşmuştur ve ini_set('disable_functions', ...) yalnızca false döndürür.

Nerede tanımlanır?Sonuç
php.iniAsıl yer. Aynı derlemenin CLI, FPM ve lsphp ikilileri normalde aynı dosyayı okur.
Tarama dizinindeki ek .ini dosyaları (DirectAdmin'de php.conf.d)php.ini'den sonra alfabetik sırayla okunur. Buradaki bir disable_functions satırı php.ini listesine eklenmez, onun yerine geçer.
DirectAdmin'in alan adı bazlı ayarlar için belgelediği [HOST=…] ve [PATH=…] bölümleriPHP-FPM gibi CGI/FastCGI işleyicileri bunları her istekte, fonksiyon listesi oluştuktan sonra okur; olsa olsa phpinfo()'nun gösterdiği değer değişir.
PHP-FPM havuzu (php_admin_value veya php_value)php.ini listesine eklenir. Havuz listeyi uzatabilir, asla kısaltamaz.
CloudLinux PHP Selector'da kullanıcıya özel seçeneklerKullanıcının CageFS içindeki alt_php.ini dosyasına yazılır; alt-php bu dosyayı php.ini'den sonra okuduğu için değer o kullanıcının listesinin tamamı olur.
.user.ini, .htaccess, ini_set()Listeyi değiştiremez. .user.ini yalnızca INI_ALL, INI_PERDIR ve INI_USER direktiflerini kabul eder, ini_set() false döndürür; .htaccess'teki bir php_value satırı ya yok sayılır ya da php_value desteklemeyen işleyicilerde siteyi 500 hatasıyla durdurur.
Komut satırında php -d veya php -nListeyi yalnızca o komut için değiştirir ya da tamamen devre dışı bırakır; bu yüzden CLI listesi bir güvenlik sınırı değildir.

PHP 8 ile ne değişti: kapalı fonksiyon artık "tanımsız"​

PHP 7.4'e kadar kapatılan fonksiyon kayıtlı kalırdı: çağrı bir uyarı (warning) basar, null döndürür, betik de çalışmaya devam ederdi. PHP 8.0'dan itibaren fonksiyon tamamen kaldırılıyor; geçiş kılavuzu (migration guide), kapatılmış fonksiyonların artık hiç var olmayan fonksiyonlarla birebir aynı şekilde ele alındığını söylüyor.

Kod:
PHP 7.x: PHP Warning:  exec() has been disabled for security reasons in /home/USER/domains/example.com/public_html/backup.php on line 12
PHP 8.x: PHP Fatal error:  Uncaught Error: Call to undefined function exec() in /home/USER/domains/example.com/public_html/backup.php:12

DavranışPHP 7.xPHP 8.x
Fonksiyonu çağırmakUyarı verir, null döner, betik devam ederError fırlatır; yakalanmazsa ölümcül hata (fatal error)
function_exists()falsefalse
is_callable()true (yanıltıcı)false
Aynı adla fonksiyon tanımlamakMümkün değilMümkün

Ziyaretçi açısından, eskiden sessizce bozulan bir özellik artık açıkça çöker: HTTP 500, boş sayfa ya da WordPress'in "There has been a critical error on this website." mesajı. Asıl hata, alan adının hata günlüğündedir (error log). DirectAdmin'de Apache ve PHP-FPM kullanıyorsanız bu dosya /var/log/httpd/domains/example.com.error.log olur. PHP-FPM hataları burada AH01071: Got error 'PHP message: ...' önekiyle görünür; ilgili satırı bulmanın en kolay yolu grep -n 'Call to undefined function' /var/log/httpd/domains/example.com.error.log komutudur.

Bu mesaj, kurulu olmayan bir uzantının (extension) fonksiyonu çağrıldığında görülenle aynı olduğu için birçok kişi uzantıları yeniden kurmaya girişir. Pratik kural: exec, shell_exec, system, passthru, proc_open, popen, putenv, mail ya da posix_ ile başlayan bir fonksiyon söz konusuysa önce disable_functions listesine bakın; imagewebp veya mb_strlen gibi bir fonksiyonsa ilgili uzantının yüklü olup olmadığını kontrol edin. Çağırmadan önce function_exists() ile kontrol yapan kod iki sürümde de çalışmaya devam eder. Bir ayrıntı daha: PHP 8'de kapatılmış bir fonksiyonun adı boşta kaldığı için bir polyfill ya da zararlı bir dosya kendi exec() fonksiyonunu tanımlayabilir; davranış tuhafsa kodda "function exec(" araması yapın.

Gerçekte geçerli olan yapılandırmayı bulun​

DirectAdmin dokuz PHP sürümüne kadar çalıştırabilir (options.conf içinde php1_release'ten php9_release'e kadar), CloudLinux kendi alt-php sürümlerini ekler ve kullanıcılar bunlar arasında kendileri seçim yapar. İşe komut satırından başlayın:

Bash:
/usr/local/bin/php --ini
/usr/local/bin/php -i | grep -i disable_functions
ls -d /usr/local/php*/ /opt/alt/php*/ 2>/dev/null
grep -n '^ *disable_functions' /usr/local/php83/lib/php.ini /usr/local/php83/lib/php.conf.d/*.ini
/opt/alt/php83/usr/bin/php --ini

DirectAdmin derlemeleri önce /usr/local/phpXX/lib/php.ini dosyasını, ardından /usr/local/phpXX/lib/php.conf.d içindeki dosyaları okur (CustomBuild burada 10-directadmin.ini ve 50-webapps.ini dosyalarını oluşturur). grep çıktısı direktifi tanımlayan her satırı gösterir; php.ini listesini sessizce geçersiz kılan, unutulmuş bir conf.d dosyası da buna dahildir. alt-php sürümleri /opt/alt/phpXX/etc/php.ini dosyasını okur ve /opt/alt/phpXX/etc/php.d dizinini tarar. DirectAdmin dosyalarının ayrıntıları için DirectAdmin'de php.ini düzenleme rehberimize göz atabilirsiniz.

CLI çıktısı web sitesi hakkında kesin bir bilgi vermez: site başka bir sürümü, bir alt-php derlemesini ya da listeye fonksiyon ekleyen bir FPM havuzunu kullanıyor olabilir. Doğrulama bölümündeki test betiğini kullanın ve üç şeye bakın: SAPI (PHP-FPM için fpm-fcgi, lsphp için litespeed), yüklenen php.ini ve fonksiyon sonuçları. FPM'de bir tuzak var: havuz kendi disable_functions değerini tanımladığında phpinfo() ve ini_get() yalnızca havuzun listesini gösterir, oysa php.ini'deki fonksiyonlar da kapalıdır (DirectAdmin belgeleri de bunu not eder). Ekranda görünen metne değil, function_exists() sonucuna güvenin.

Sürüm seçimi birkaç katmanda yapılır. DirectAdmin'de alan adı düzeyindeki seçim (Domain Setup, PHP Version Selector) önceliklidir. CloudLinux PHP Selector kullanıcı bazında çalışır, php1_mode=lsphp gerektirir (CloudLinux'un uyumluluk tablosu PHP-FPM ile desteklenmediğini belirtir) ve yalnızca birinci DirectAdmin PHP sürümünü kullanan alan adlarını etkiler. LiteSpeed'de kullanıcı .htaccess üzerinden bir alt-php işleyicisini bile zorlayabilir. Kısacası listeye yalnızca varsayılan sürümde değil, sunucudaki her PHP derlemesinde ihtiyaç var. alt-php sürümlerinin sunucuya nasıl eklendiğini cPanel ve CloudLinux'ta yeni PHP sürümü ekleme konusunda anlattık.

DirectAdmin'de genel listeyi CustomBuild ile değiştirme​

CustomBuild'in secure_php seçeneği (varsayılan olarak kapalı), yönettiği her PHP sürümünün php.ini dosyasına bir disable_functions listesi yazar; expose_php ve mysqli.allow_local_infile açıksa onları da kapatır. Varsayılan liste exec, system, passthru, shell_exec, proc_close, proc_open, dl, popen, show_source ve on posix_ fonksiyonundan (posix_kill, posix_mkfifo, posix_getpwuid, posix_setpgid, posix_setsid, posix_setuid, posix_setgid, posix_seteuid, posix_setegid, posix_uname) oluşur. DirectAdmin, php.ini'deki disable_functions satırına elle yapılan değişikliklerin büyük olasılıkla bir sonraki PHP derlemesinde ezileceği konusunda uyarıyor; bu yüzden listenizi CustomBuild'in özel (custom) dosyasında tutun. Aşağıdaki örnek DirectAdmin'in varsayılan listesini yazar; çalıştırmadan önce fonksiyon adlarını kendi sunucunuza göre düzenleyin:

Bash:
cp -a /usr/local/php83/lib/php.ini /root/php83-php.ini.$(date +%F)
grep -E '^(php[1-9]_(release|mode)|secure_php|php_ini)=' /usr/local/directadmin/custombuild/options.conf
cd /usr/local/directadmin/custombuild
mkdir -p custom
echo "exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname" > custom/php_disable_functions
da build secure_php
grep disable_functions /usr/local/php*/lib/php.ini

CloudLinux sunucularda aynı komut listeyi /etc/cl.selector/global_php.ini dosyasına da yazar ve cagefsctl --setup-cl-selector çalıştırır; CloudLinux bu dosyayı kurulu her alt-php sürümünün ve sonradan kurulacak sürümlerin php.ini dosyasına uygular. alt-php'yi elle yönetiyorsanız tek tek /opt/alt/phpXX/etc/php.ini dosyalarını değil, global_php.ini dosyasını düzenleyin ve cagefsctl --setup-cl-selector ile uygulayın. global_php.ini bu direktifi içerdiği sürece (secure_php çalıştıktan sonra içerir) tek bir alt-php php.ini dosyasında yapılan elle düzenleme kalıcı olmaz: genel dosya bir sonraki uygulanışında kendi disable_functions değerini geri yazar. Listeyi geri almak isterseniz dikkat edin: custom/php_disable_functions dosyasına boş bir değer yazıp da build secure_php komutunu yeniden çalıştırırsanız liste tamamen temizlenir ve bu ayar artık hiçbir fonksiyonu kapatmaz. DirectAdmin'in varsayılan listesine dönmek istiyorsanız yukarıdaki listeyi dosyaya geri yazın. Eski kurulumlarda aynı iş custombuild dizininde ./build secure_php ile yapılır. Ayrıntılar DirectAdmin'in secure_php belgesinde.

Ardından değiştirdiğiniz her sürüm için PHP'yi yeniden başlatın:
İşleyiciDeğişikliği uygulama
PHP-FPMsystemctl restart php-fpm83 (her sürüm için ayrı servis)
LiteSpeed (lsphp)LSWS 5.3'ten beri PHP bağımsız (detached) modda çalışır ve web sunucusuyla birlikte yeniden başlamaz: touch /usr/local/lsws/admin/tmp/.lsphp_restart.txt (kademeli) veya pkill lsphp (anında); tek bir hesap için touch /home/USER/.lsphp_restart.txt
Apache + mod_lsapisystemctl restart httpd; varsayılan lsapi_terminate_backends_on_exit On ayarıyla lsphp süreçleri de durdurulur
CLI ve cronYeniden başlatma gerekmez; bir sonraki çalıştırma dosyayı okur

Hesap bazlı strateji: genel liste sıkı, istisnalar kontrol altında​

PHP-FPM'de bir hesabı daha sıkı hâle getirmek kolaydır. Admin Level'da Custom HTTPD Configurations bölümünde alan adının php-fpm83 kaydını açın ve CUSTOM2 alanına bir satır ekleyin. Bu satıra genel listeyi de yazın; phpinfo() yalnızca bu satırı göstereceği için çıktı böylece yanıltıcı olmaz:

INI:
php_admin_value[disable_functions] = exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname,mail

Sürüme özel alan yalnızca o PHP sürümünde geçerlidir. Kullanıcı sürüm değiştirebiliyorsa kuralın onunla birlikte taşınması için aynı sayfadaki "global php-fpm CUSTOM2" alanını kullanın (/usr/local/directadmin/data/users/USER/php/php-fpm.conf.custom2 dosyasında saklanır). Sonra php-fpm83 servisini yeniden başlatın; bu dosyaları elle düzenlediyseniz önce da build rewrite_confs çalıştırın.

Tek bir hesap exec veya proc_open istiyorsa seçenekleri şu sırayla değerlendirin:
  1. İhtiyacı ve bağlamı doğrulayın. Pek çok uygulama bu fonksiyonlara web isteklerinde değil, yalnızca cron veya CLI görevlerinde (Composer, zamanlayıcılar, kuyruk işçileri) ihtiyaç duyar. Bunu CLI tarafında çözün (sonraki bölüm), web listesine dokunmayın.
  2. CloudLinux, LSPHP ve PHP Selector: yönetici selectorctl ile tek bir kullanıcıya daha kısa bir liste tanımlayabilir (aşağıdaki örnek). Bu değer kullanıcının listesinin tamamı olur; bu yüzden tam listeyi yazıp yalnızca açacağınız fonksiyonları çıkarın. Hesap CageFS içinde kalır, ancak ayar yalnızca alan adı selector'da seçilmiş bir alt-php sürümüyle çalıştığı sürece geçerlidir.
  3. DirectAdmin ve PHP-FPM: DirectAdmin kullanıcı bazlı bir yöntemi belgeliyor. php.ini'deki disable_functions satırını yorum satırına çevirir, varsayılan listeyi genel şablonlara DF değişkeni olarak koyar (/usr/local/directadmin/data/templates/custom/php-fpm.conf.custom1 içine |?DF=php_admin_value[disable_functions] = ...|, php-fpm.conf.custom2 içine |DF|), da build rewrite_confs çalıştırır ve tek kullanıcı için DF değerini değiştirirsiniz. DirectAdmin'in adımları php_ini=no ayarını da yapar; böylece CustomBuild php.ini dosyasını artık yeniden yazmaz. Bedeli şu: CLI ve cron artık hiçbir liste almaz; ayrıca her CustomBuild PHP güncellemesinden sonra php.ini'deki satırın hâlâ yorum satırı olarak durduğunu grep ile kontrol etmeniz gerekir. Adımlar DirectAdmin'in PHP-FPM belgesinde.
  4. Ayrı bir sunucu veya VPS: ffmpeg kullanan medya işleme hatları, headless tarayıcılar ve derleme araçları gibi, işi program çalıştırmak olan uygulamalar için çoğu zaman en temiz çözüm budur.

Bash:
selectorctl --print-options --user=USER --version=8.3
OPTS=$(echo "disable_functions:exec,system,passthru,shell_exec,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname" | base64 -w 0)
selectorctl --user=USER --version=8.3 --replace-options="$OPTS" --base64

Bu örnek proc_open ve proc_close fonksiyonlarını yalnızca USER için açar. Adından da anlaşılacağı gibi --replace-options kullanıcının seçeneklerini bir bütün olarak belirler; bu yüzden önce --print-options çıktısını inceleyin ve kullanıcının değiştirdiği ayarları da yeni değere taşıyın. Kullanıcının PHP süreçlerini yeniden başlatın, ardından test betiğiyle fonksiyonların bu kullanıcıda açık, diğerlerinde kapalı olduğunu doğrulayın. PHP Selector'da kullanıcılar yalnızca /etc/cl.selector/php.conf dosyasında izin verilen (whitelist) direktifleri değiştirebilir; disable_functions direktifinin bunlar arasında olmadığından emin olun. selectorctl sizin sürümünüzde bu seçeneği kabul etmezse, komutu çalıştırabilmek için direktifi php.conf dosyasına eklemeyin; bu, ayarı bütün kullanıcılara açmak demektir.

Gevşek ayarlı ayrı bir PHP sürümü, istisnayı ancak diğer kullanıcılar o sürümü seçemiyorsa yalıtır. DirectAdmin'de php_version_selector açıkken (varsayılan durum) her kullanıcı kurulu sürümlerden istediğini Domain Setup'ta seçebilir; LiteSpeed'de .htaccess dosyasına yazılan bir AddHandler satırı da sürüm seçebilir. İkisini birden engelleyemiyorsanız gevşek sürüm, onu fark eden herkes için bir istisnadır. exec'i kime olursa olsun açmadan önce bir şeyi daha hesaba katın: open_basedir yalnızca PHP'nin kendi dosya fonksiyonlarını sınırlar; exec veya proc_open ile başlatılan bir program bu sınırı tanımaz ve sistem kullanıcısının okuyabildiği her şeyi okuyabilir. Diğer hesapları ve sistemin büyük bölümünü o programın göremeyeceği yerde tutan şey CageFS'tir. CloudLinux ve DirectAdmin'de exec() etkinleştirme notumuz dosya düzenlemelerini gösteriyor; o notu buradaki uyarılarla birlikte okuyun.

Hangi uygulama hangi fonksiyonlara ihtiyaç duyar?​

Uygulama veya görevTipik fonksiyonlarBağlam
Composer (git, unzip gibi araçları Symfony Process ile çalıştırır)proc_openCLI
Laravel zamanlayıcısı (schedule:run)proc_opencron
İş zaman aşımı (job timeout) kullanan Laravel kuyruk işçileripcntl uzantısının fonksiyonlarıCLI
mysqldump, tar veya zip çağıran yedekleme araçlarıexec, shell_exec veya proc_open; bazıları bu fonksiyonlar yoksa daha yavaş, saf PHP ile yazılmış bir yönteme geçerweb veya cron
GD veya Imagick uzantısıyla görsel işlemeListedeki fonksiyonların hiçbiriweb
convert/magick veya ffmpeg ikililerini çağırmakexec veya proc_openweb veya CLI
Guzzle gibi HTTP istemcilericurl_exec, curl_multi_execweb ve CLI
mail() kullanan iletişim formlarımailweb

Bu tabloyu kesin bir şartname değil, bir başlangıç noktası olarak görün: her uygulamanın gereksinim (requirements) sayfasını ve hata günlüğünü kontrol edin. Composer ve Laravel'in kullandığı Symfony Process, proc_open yoksa "The Process class relies on proc_open, which is not available on your PHP installation." hatasıyla durur. İnternette dolaşan bazı hazır listelerde curl_multi_exec ve parse_ini_file da bulunur. İkisi de kabuk komutu çalıştırmaz: curl_multi_exec olmadığında Guzzle istekleri paralel çalıştıramayan bir işleyiciye geri döner; parse_ini_file ise uygulamaların kendi ayar dosyalarını okumak için kullandığı bir fonksiyondur. Özel bir gerekçeniz yoksa ikisini de listeye almayın.

Cron görevleri CLI ikilisini (binary) çalıştırır ve DirectAdmin'de bu ikili, aynı sürümdeki web sitesiyle aynı php.ini dosyasını okur; dolayısıyla sıkı bir web listesi Composer'ı ve zamanlanmış görevleri de etkiler. /usr/local/bin/php varsayılan sürümdür; diğer sürümlerin ikilileri /usr/local/phpXX/bin/ altındadır (adlarını görmek için dizini listeleyin). CageFS içinde /usr/local/bin/php, PHP Selector'da seçilen sürüme giden bir bağlantıdır; bu yüzden kullanıcı selector'da bir alt-php sürümü seçtiyse cron görevleri o sürümün php.ini dosyasını ve kullanıcının alt_php.ini dosyasını okur. Güvendiğiniz bir görev için listeyi yalnızca o komutta devre dışı bırakın:

Bash:
/usr/local/bin/php -d disable_functions= /home/USER/domains/example.com/app/artisan schedule:run

Alternatif olarak php.ini'nin yanına koyacağınız bir php-cli.ini dosyası, CLI tarafından php.ini yerine okunur (PHP önce php-SAPI.ini adlı dosyayı arar); php.ini'yi kopyalayın, listeyi değiştirin ve güncellemelerden sonra kopyayı eşitlemeyi unutmayın. Tersi de mümkün: yalnızca web siteleri için kapatmak istediğiniz fonksiyonlar, örneğin putenv (Composer putenv() fonksiyonunu doğrudan çağırır), en iyi /usr/local/directadmin/data/templates/custom/php-fpm.conf.custom2 genel FPM şablonunda durur (ardından da build rewrite_confs); bu şablon her havuza ulaşır ama komut satırına hiç ulaşmaz. Bu yöntemlerin hiçbiri web tarafını zayıflatmaz; zaten cron görevi oluşturabilen bir kullanıcı istediği komutu çalıştırabilir.

Her işleyiciyi test betiğiyle doğrulayın, sonra silin​

PHP:
<?php
header('Content-Type: text/plain; charset=utf-8');
echo 'PHP ', PHP_VERSION, ' | SAPI: ', PHP_SAPI, "\n";
echo 'php.ini: ', php_ini_loaded_file() ?: 'yok', "\n";
echo 'görünen liste: ', ini_get('disable_functions'), "\n";
foreach (['exec', 'shell_exec', 'system', 'passthru', 'proc_open', 'popen', 'putenv', 'mail', 'curl_multi_exec'] as $f) {
    echo str_pad($f, 16), function_exists($f) ? 'açık' : 'kapalı ya da kurulu değil', "\n";
}

  1. Dosyayı df-7hq2.php gibi rastgele bir adla, sahibi kullanıcı olacak şekilde alan adının public_html dizinine kaydedin.
  2. Web üzerinden çağırın: curl -s https://example.com/df-7hq2.php
  3. Aynı dosyayı kullanıcı olarak komut satırında çalıştırın: su -s /bin/sh USER -c "/usr/local/bin/php /home/USER/domains/example.com/public_html/df-7hq2.php". CloudLinux'ta komutu kullanıcının CageFS ortamında çalıştırın (cagefsctl -e USER orada bir kabuk açar); kullanıcının cron görevleri de bu ortamı görür.
  4. SAPI, php.ini ve fonksiyon satırlarını her işleyici için hedeflediğiniz durumla karşılaştırın. "kapalı ya da kurulu değil" sonucu, hiç kurulmamış uzantıları da kapsar; örneğin curl uzantısı yüklü değilse curl_multi_exec satırı böyle görünür.
  5. Dosyayı silin: rm -f /home/USER/domains/example.com/public_html/df-7hq2.php

Güvenlik dengesi: disable_functions neyi yapmaz?​

PHP kılavuzu bu direktifin atlatılabileceğini ve paylaşımlı hosting için yeterli bir güvenlik önlemi sayılmaması gerektiğini açıkça yazıyor. Sunucuya web shell yükleyen bir saldırganı yavaşlatır, ama onu bir kafese kapatmaz.
  • Her listenin boşlukları vardır. mail(), mb_send_mail() ve e-posta modundaki error_log() sendmail ikilisini başlatır; putenv() ile birlikte kullanıldığında bu, listeyi atlatmanın bilinen bir yoludur. Pek çok yönetici bu yüzden uygulamalarını test ettikten sonra putenv'i de listeye ekler. DirectAdmin'in varsayılan listesinde pcntl_exec de yoktur; kuyruk işçileri pcntl_signal ve pcntl_alarm gibi başka pcntl fonksiyonları kullandığı için onu eklemek nadiren bir şeyi bozar. Uzantılar da kendi kütüphaneleri içinden program başlatabilir.
  • PHP motorundaki hatalar. PHP 7.0'dan 8.1'e kadar uzanan sürümler için kamuya açık istismar kodları (exploit) var; bunlar bellek bozulması hatalarını kullanarak listeyi tamamen atlatır. PHP'nin kendi güvenlik politikası da disable_functions ve open_basedir atlatmalarını güvenlik açığı olarak ele almadığı konular arasında listeler. Her PHP derlemesini destekli ve yamaları uygulanmış bir sürümde tutun.
  • FFI: ffi.enable değerini varsayılan preload ayarında bırakın ya da false yapın; böylece sıradan web betikleri C kütüphanelerini doğrudan çağıramaz.
  • CageFS, etkinleştirildiği her hesaba dosya sisteminin kendine ait bir görünümünü verir ve başarılı bir saldırganın görebileceği ve çalıştırabileceği şeyleri, exec ile başlatılan programlar dahil, sınırlar.
  • open_basedir yalnızca PHP'nin kendi dosya fonksiyonlarını kapsar, harici programları kapsamaz.
  • Imunify360 Proactive Defense kuruluysa PHP betiklerini çalışırken izler ve moduna göre şüpheli davranışı kaydeder ya da engeller; yani disable_functions'ın izin verdiği bir çağrı yine de durdurulabilir. Bir fonksiyonu açtıktan sonra meşru bir görev yine başarısız oluyorsa önce Imunify360'ın olay kayıtlarına bakın ve yalnızca incelediğiniz belirli dosyayı istisna olarak ekleyin.
  • ModSecurity HTTP isteklerini PHP'ye ulaşmadan süzer ve açığı olan eklentilere yönelik pek çok saldırıyı daha hiçbir fonksiyon çağrılmadan durdurur.

Sorun giderme tablosu​

BelirtiOlası nedenNe yapmalı?
PHP 8'e geçtikten sonra "Call to undefined function exec()"exec listede; PHP 8 onu tanımsız olarak bildirirTest betiğiyle doğrulayın, sonra hesap bazında karar verin
"exec() has been disabled for security reasons"Aynı durum, eski bir alt-php gibi bir PHP 7.x derlemesindeÇözüm aynı; destekli bir sürüme geçişi planlayın
php.ini değişti ama sonuç değişmediYanlış derleme veya dosya, sonradan okunan bir conf.d dosyası ya da yeniden başlatılmamış süreç (detached lsphp)php --ini ve test betiğiyle kontrol edin, php.conf.d'de grep yapın, doğru servisi yeniden başlatın
Havuz ayarından çıkarılan fonksiyon hâlâ kapalıHavuzlar yalnızca ekleme yapabilirGenel listeyi değiştirin ya da hesap bazlı bir yöntem kullanın
.user.ini veya paneldeki PHP ayarları işe yaramıyorDirektif INI_SYSTEM düzeyindephp.ini, havuz veya selector seçeneklerini kullanın
phpinfo() kısa bir liste gösteriyor ama exec kapalıFPM yalnızca havuzun değerini gösterirfunction_exists() sonucuna güvenin; havuz satırında tam listeyi tekrarlayın
Site çalışıyor, Composer veya cron proc_open hatası veriyorCLI aynı php.ini dosyasını okuyorO görev için -d kullanın ya da php-cli.ini oluşturun
CustomBuild güncellemesinden sonra liste kaybolduphp.ini'deki elle düzenlemeler derlemede ezilirListeyi custom/php_disable_functions dosyasında tutun ve da build secure_php çalıştırın
Fonksiyon açık ama çağrı yine başarısızImunify360 engelliyor ya da program CageFS içinde yokImunify360 olaylarına ve kullanıcının CageFS ortamına bakın

Sıkça sorulan sorular​

Kullanıcı disable_functions'ı .user.ini veya ini_set() ile değiştirebilir mi?
Hayır. Direktif INI_SYSTEM düzeyindedir; onu yalnızca php.ini düzeyindeki dosyalar, FPM havuzları (sadece ekleme yönünde) ve yöneticinin selector seçenekleri etkiler. Komut satırındaki php -d ise yalnızca o komutu etkiler.

Bir fonksiyonu yalnızca tek bir PHP-FPM havuzu için açabilir miyim?
Yalnızca havuz ayarıyla hayır; havuz php.ini listesine sadece ekleme yapabilir. DirectAdmin'in şablon yöntemi listeyi php.ini'den havuzlara taşıdığı için bunu mümkün kılar; CloudLinux'ta selector seçeneklerini, diğer durumlarda ayrı bir sunucuyu kullanın.

phpinfo() neden php.ini'den farklı bir liste gösteriyor?
Site başka bir PHP derlemesi veya dosyası kullanıyor, bir conf.d dosyası listeyi değiştiriyor ya da birleşik liste yerine FPM havuzunun satırı görüntüleniyor olabilir.

mail() ve putenv() kapatılmalı mı?
Ancak uygulamalarınızı test ettikten sonra. mail() kapatılırsa SMTP kullanmayan formlar e-posta gönderemez. putenv'i komut satırında değil, yalnızca web tarafında kapatın; Composer bu fonksiyonu doğrudan çağırır.

CageFS ve Imunify360 varken disable_functions'a hâlâ gerek var mı?
Evet, katmanlardan biri olarak. Web shell'den işletim sistemine giden en kolay yolu kapatır; onun kaçırdıklarının bir kısmını CageFS, Imunify360 ve ModSecurity yakalar.

disable_classes hâlâ çalışıyor mu?
PHP 8.4'e kadar evet. PHP 8.5 bu direktifi kaldırdı; 8.5'te böyle bir satır artık hiçbir sınıfı kapatmaz. 8.5 derlemelerinin okuduğu şablonlardan ve php.ini dosyalarından çıkarın.

Sonuç ve sonraki adımlar​

disable_functions doğru yerde yönetildiğinde işini görür: genel liste için CustomBuild üzerinden php.ini (CloudLinux'ta ayrıca global_php.ini), daha sıkı hesaplar için FPM havuzları, gerçek istisnalar için selector, DirectAdmin'in şablon yöntemi ya da ayrı bir sunucu. Sonraki adımlar:
  1. Sunucudaki her PHP derlemesini listeleyin ve hangi sitenin hangisini kullandığını not edin.
  2. Listenizi custom/php_disable_functions dosyasına yazıp da build secure_php çalıştırın.
  3. Test betiğini her işleyici üzerinden ve komut satırında çalıştırın, sonra silin.
  4. Her hesap bazlı istisnayı gerekçesiyle birlikte kayda geçirin.
  5. Her CustomBuild veya alt-php güncellemesinden sonra kontrolleri tekrarlayın.
Bu katmanların bakımını başka birine bırakmak isterseniz AIOR'un Linux sunucu yönetimi hizmeti seçeneklerden biridir.

PHP's disable_functions directive removes functions such as exec, shell_exec and proc_open from PHP, so a hacked plugin cannot simply run shell commands on the server. On a DirectAdmin server with CloudLinux, PHP runs in several places at once: DirectAdmin's own builds under /usr/local/phpXX, CloudLinux alt-php builds under /opt/alt/phpXX, PHP-FPM pools, LiteSpeed's lsphp processes and the command line. Each of them can end up with a different list. This rewritten guide corrects a common mistake, one our earlier version also made: the list cannot be loosened for one account through .user.ini, ini_set() or a per-domain PHP settings screen. It also covers what changed in PHP 8, how to find the configuration that is really in effect, and how to handle the one account that needs exec without opening it for everyone. The examples use PHP 8.3; replace 83 with your version.

Short version:
  • Set the global list in php.ini; on DirectAdmin, let CustomBuild's secure_php option write it. .user.ini, .htaccess and ini_set() cannot change it.
  • A PHP-FPM pool can only add functions to the php.ini list, never remove them.
  • Since PHP 8.0 a disabled function behaves as if it did not exist: calls end in "Call to undefined function".
  • The CLI, PHP-FPM, lsphp and alt-php may each read a different file; check the handler that actually serves the site.
  • When one account needs exec or proc_open, give it its own list or its own server instead of shortening the list for everyone.

What disable_functions does and where it can be set​

The directive takes a comma-separated list of internal PHP functions. PHP reads it when the process starts, and from then on those functions are gone; functions defined in PHP code are not affected. The PHP manual marks it INI_SYSTEM and says it must be set in php.ini. That explains most failed attempts: by the time a .user.ini file, an .htaccess line or an ini_set() call is processed, the function table is already built, and ini_set('disable_functions', ...) simply returns false.

Where you set itResult
php.iniThe main place. The CLI, FPM and lsphp binaries of one build normally read the same file.
Extra .ini files in the scan directory (php.conf.d on DirectAdmin)Read after php.ini in alphabetical order. A disable_functions line there replaces the php.ini list instead of extending it.
[HOST=…] and [PATH=…] sections, which DirectAdmin documents for per-domain settingsCGI/FastCGI handlers such as PHP-FPM read them per request, after the function list is built; at most, the value phpinfo() displays changes.
PHP-FPM pool (php_admin_value or php_value)Appended to the php.ini list. A pool can make the list longer, never shorter.
CloudLinux PHP Selector options for one userWritten to the user's alt_php.ini inside CageFS, which alt-php reads after php.ini, so the value becomes that user's whole list.
.user.ini, .htaccess, ini_set()Cannot change it. .user.ini only accepts INI_ALL, INI_PERDIR and INI_USER directives, ini_set() returns false, and a php_value line in .htaccess is either ignored or, on handlers that do not support php_value, breaks the site with a 500 error.
php -d or php -n on the command lineChanges or drops the list for that one command, so a CLI list is not a security boundary.

What changed in PHP 8: disabled now means undefined​

Up to PHP 7.4 a disabled function stayed registered: a call printed a warning, returned null and the script carried on. Since PHP 8.0 the function is removed; the migration guide says disabled functions are now treated exactly like non-existent ones.

Kod:
PHP 7.x: PHP Warning:  exec() has been disabled for security reasons in /home/USER/domains/example.com/public_html/backup.php on line 12
PHP 8.x: PHP Fatal error:  Uncaught Error: Call to undefined function exec() in /home/USER/domains/example.com/public_html/backup.php:12

BehaviourPHP 7.xPHP 8.x
Calling the functionWarning, returns null, script continuesThrows Error; fatal unless caught
function_exists()falsefalse
is_callable()true (misleading)false
Defining a function with that nameNot possiblePossible

For visitors, a feature that used to fail quietly now fails loudly: HTTP 500, a blank page or WordPress's "There has been a critical error on this website." The real message is in the domain's error log. On DirectAdmin with Apache and PHP-FPM that is /var/log/httpd/domains/example.com.error.log, where PHP-FPM errors appear with the prefix AH01071: Got error 'PHP message: ...'. The quickest way to find the line is grep -n 'Call to undefined function' /var/log/httpd/domains/example.com.error.log.

The wording is the same as for a function from a missing extension, so people often start reinstalling extensions. Rule of thumb: for exec, shell_exec, system, passthru, proc_open, popen, putenv, mail or a posix_ function, check disable_functions first; for something like imagewebp or mb_strlen, check whether the extension is loaded. Code that tests function_exists() before calling keeps working on both versions. And because a disabled name is free in PHP 8, a polyfill or a malicious include can define its own exec(); if behaviour looks odd, search the code for "function exec(".

Find the configuration that is actually in effect​

DirectAdmin can run up to nine PHP versions (php1_release to php9_release in options.conf), CloudLinux adds its alt-php versions, and users choose between them. Start on the command line:

Bash:
/usr/local/bin/php --ini
/usr/local/bin/php -i | grep -i disable_functions
ls -d /usr/local/php*/ /opt/alt/php*/ 2>/dev/null
grep -n '^ *disable_functions' /usr/local/php83/lib/php.ini /usr/local/php83/lib/php.conf.d/*.ini
/opt/alt/php83/usr/bin/php --ini

DirectAdmin builds read /usr/local/phpXX/lib/php.ini and then the files in /usr/local/phpXX/lib/php.conf.d (CustomBuild creates 10-directadmin.ini and 50-webapps.ini there). The grep shows every line that sets the directive, including a forgotten conf.d file that silently replaces the php.ini list. alt-php builds read /opt/alt/phpXX/etc/php.ini and scan /opt/alt/phpXX/etc/php.d. For more on the DirectAdmin files, see our guide to editing php.ini on DirectAdmin.

The CLI result says nothing certain about the website: the site may use another version, an alt-php build or an FPM pool that adds functions. Use the test script from the verification section and read three things: the SAPI (fpm-fcgi for PHP-FPM, litespeed for lsphp), the loaded php.ini and the function results. One FPM trap: when a pool sets its own disable_functions, phpinfo() and ini_get() show only the pool's list, although the php.ini functions are still disabled (DirectAdmin's documentation notes this too). Trust function_exists(), not the displayed string.

Version selection is layered. A domain-level choice in DirectAdmin (Domain Setup, PHP Version Selector) wins. The CloudLinux PHP Selector works per user, needs php1_mode=lsphp (CloudLinux's compatibility matrix lists it as unsupported with PHP-FPM) and only affects domains that use the first DirectAdmin PHP version. On LiteSpeed, a user can even force an alt-php handler from .htaccess. In practice every PHP build on the server needs the list, not only the default one. How alt-php versions get onto a server is covered in adding a PHP version on cPanel and CloudLinux.

DirectAdmin: change the global list the CustomBuild way​

CustomBuild's secure_php option (off by default) writes a disable_functions list into the php.ini of every PHP version it manages and switches expose_php and mysqli.allow_local_infile off where they are enabled. Its default list is exec, system, passthru, shell_exec, proc_close, proc_open, dl, popen, show_source and ten posix_ functions (posix_kill, posix_mkfifo, posix_getpwuid, posix_setpgid, posix_setsid, posix_setuid, posix_setgid, posix_seteuid, posix_setegid, posix_uname). DirectAdmin warns that hand edits of disable_functions in php.ini will likely be overwritten the next time PHP is built, so keep your list in CustomBuild's custom file. The example below writes DirectAdmin's default list; edit the function names for your server before you run it:

Bash:
cp -a /usr/local/php83/lib/php.ini /root/php83-php.ini.$(date +%F)
grep -E '^(php[1-9]_(release|mode)|secure_php|php_ini)=' /usr/local/directadmin/custombuild/options.conf
cd /usr/local/directadmin/custombuild
mkdir -p custom
echo "exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname" > custom/php_disable_functions
da build secure_php
grep disable_functions /usr/local/php*/lib/php.ini

On CloudLinux, the same command also writes the list to /etc/cl.selector/global_php.ini and runs cagefsctl --setup-cl-selector; CloudLinux applies that file to the php.ini of every installed alt-php version, and to versions installed later. If you maintain alt-php by hand, edit global_php.ini rather than each /opt/alt/phpXX/etc/php.ini, and apply it with cagefsctl --setup-cl-selector. As long as global_php.ini sets disable_functions (it does once secure_php has run), a hand edit in a single alt-php php.ini does not last: the next time the global file is applied, its value is written back. Take care when reverting: writing an empty value to custom/php_disable_functions and running da build secure_php again clears the list completely, so this setting then disables no functions at all. To return to DirectAdmin's default, write the default list above back into the file. Older installations use ./build secure_php inside the custombuild directory. Details are in DirectAdmin's secure_php documentation.

Then reload PHP for every version you changed:
HandlerHow to apply the change
PHP-FPMsystemctl restart php-fpm83 (one service per version)
LiteSpeed (lsphp)Since LSWS 5.3, PHP runs detached and does not restart with the web server: touch /usr/local/lsws/admin/tmp/.lsphp_restart.txt (gradual) or pkill lsphp (immediate); for one account, touch /home/USER/.lsphp_restart.txt
Apache with mod_lsapisystemctl restart httpd; with the default lsapi_terminate_backends_on_exit On, this also stops the lsphp backends
CLI and cronNothing to restart; the next run reads the file

Per-account strategy: strict globally, exceptions contained​

Making one account stricter is easy with PHP-FPM. In Admin Level, Custom HTTPD Configurations, open the domain's php-fpm83 entry and add a line to the CUSTOM2 field. Repeat the global list in this line as well: phpinfo() will show only this line, so the output then stays accurate:

INI:
php_admin_value[disable_functions] = exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname,mail

The per-version field applies to that PHP version only. If the user can switch versions, use the "global php-fpm CUSTOM2" field on the same page (stored in /usr/local/directadmin/data/users/USER/php/php-fpm.conf.custom2) so the rule follows them. Restart php-fpm83 afterwards; if you edit such files by hand, run da build rewrite_confs first.

When one account needs exec or proc_open, work through these options in order:
  1. Check the need and the context. Many applications need these functions only in cron or CLI tasks (Composer, schedulers, queue workers), not in web requests. Solve that on the CLI side (next section) and leave the web list alone.
  2. CloudLinux with LSPHP and the PHP Selector: an administrator can give one user a shorter list with selectorctl (example below). The value becomes the user's complete list, so write the full list minus what you open. The account stays inside CageFS, but this only applies while its domain runs an alt-php version chosen in the selector.
  3. DirectAdmin with PHP-FPM: DirectAdmin documents a per-user method. You comment out disable_functions in php.ini, put the default list into the global templates as a DF variable (/usr/local/directadmin/data/templates/custom/php-fpm.conf.custom1 with |?DF=php_admin_value[disable_functions] = ...|, and php-fpm.conf.custom2 with |DF|), run da build rewrite_confs and override DF for the one user. DirectAdmin's steps also set php_ini=no, so CustomBuild stops replacing php.ini. The price: the CLI and cron get no list at all, and after every CustomBuild PHP update you should use grep to confirm that the php.ini line is still commented out. The steps are in DirectAdmin's PHP-FPM documentation.
  4. A separate server or VPS for applications built around running programs (media pipelines with ffmpeg, headless browsers, build tools). Often the cleanest answer.

Bash:
selectorctl --print-options --user=USER --version=8.3
OPTS=$(echo "disable_functions:exec,system,passthru,shell_exec,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname" | base64 -w 0)
selectorctl --user=USER --version=8.3 --replace-options="$OPTS" --base64

This example opens proc_open and proc_close for USER only. As the name suggests, --replace-options sets the user's options as a whole, so review the --print-options output first and carry over anything the user changed. Restart that user's PHP processes, then confirm with the test script that the functions are open for this user and still closed for others. In the PHP Selector, users can change only the directives whitelisted in /etc/cl.selector/php.conf, so make sure disable_functions is not among them. If selectorctl refuses the option on your version, do not add it to php.conf to make the command work; that would hand the setting to every user.

A separate, relaxed PHP version isolates an exception only if other users cannot select it. On DirectAdmin every user can pick any installed version in Domain Setup while php_version_selector is on (the default), and on LiteSpeed an AddHandler line in .htaccess can select a version as well. Unless you can block both, a relaxed version is an exception for everyone who finds it. Weigh one more fact before opening exec for anyone: open_basedir limits PHP's own file functions, but a program started through exec or proc_open ignores it and can read whatever the system user can read. CageFS is what keeps other accounts and most of the system out of that program's view. Our note on enabling exec on CloudLinux and DirectAdmin shows the file edits; read it together with these cautions.

Which applications need which functions​

Application or taskTypical functionsContext
Composer (runs git, unzip and other tools through Symfony Process)proc_openCLI
Laravel scheduler (schedule:run)proc_opencron
Laravel queue workers with job timeoutsFunctions of the pcntl extensionCLI
Backup tools that call mysqldump, tar or zipexec, shell_exec or proc_open; some have a slower pure-PHP fallbackweb or cron
Image work through the GD or Imagick extensionsNone of the listed functionsweb
Calling the convert/magick or ffmpeg binariesexec or proc_openweb or CLI
HTTP clients such as Guzzlecurl_exec, curl_multi_execweb and CLI
Contact forms that use mail()mailweb

Treat this as a map, not a specification: check each application's requirements page and its error log. Symfony Process, which Composer and Laravel use, stops with "The Process class relies on proc_open, which is not available on your PHP installation." Some copied lists also contain curl_multi_exec and parse_ini_file. Neither runs shell commands: without curl_multi_exec, Guzzle falls back to a handler that cannot run requests in parallel, and applications use parse_ini_file to read their own settings. Leave both out unless you have a specific reason.

Cron jobs run the CLI binary, which on DirectAdmin reads the same php.ini as the website for that version, so a strict web list also hits Composer and scheduled tasks. /usr/local/bin/php is the default version; the other versions have their own binaries under /usr/local/phpXX/bin/ (list the directory to see the names). Inside CageFS, /usr/local/bin/php is a link to the version chosen in the PHP Selector, so when a caged user has picked an alt-php version there, their cron jobs read that version's php.ini plus the user's alt_php.ini. For a job you trust, override the list for that command only:

Bash:
/usr/local/bin/php -d disable_functions= /home/USER/domains/example.com/app/artisan schedule:run

Alternatively, a php-cli.ini next to php.ini is read by the CLI instead of php.ini (PHP looks for php-SAPI.ini first); copy php.ini, change the list and keep the copy in sync after updates. The reverse also works: functions you want blocked for websites only, such as putenv (Composer calls putenv() directly), fit best in the global FPM template /usr/local/directadmin/data/templates/custom/php-fpm.conf.custom2 (then da build rewrite_confs), which reaches every pool but never the CLI. None of this weakens the web side, and a user who can create cron jobs can run commands anyway.

Verify each handler with a test script, then delete it​

PHP:
<?php
header('Content-Type: text/plain');
echo 'PHP ', PHP_VERSION, ' | SAPI: ', PHP_SAPI, "\n";
echo 'php.ini: ', php_ini_loaded_file() ?: 'none', "\n";
echo 'shown list: ', ini_get('disable_functions'), "\n";
foreach (['exec', 'shell_exec', 'system', 'passthru', 'proc_open', 'popen', 'putenv', 'mail', 'curl_multi_exec'] as $f) {
    echo str_pad($f, 16), function_exists($f) ? 'available' : 'disabled or missing', "\n";
}

  1. Save it under a random name such as df-7hq2.php in the domain's public_html, owned by the user.
  2. Request it: curl -s https://example.com/df-7hq2.php
  3. Run the same file as the user: su -s /bin/sh USER -c "/usr/local/bin/php /home/USER/domains/example.com/public_html/df-7hq2.php". On CloudLinux, run it inside the user's CageFS instead (cagefsctl -e USER opens a shell there), which is what their cron jobs see.
  4. Compare SAPI, php.ini and the function lines with what you intended for each handler. "disabled or missing" also covers extensions that are simply not installed; curl_multi_exec shows up that way when the curl extension is not loaded.
  5. Delete the file: rm -f /home/USER/domains/example.com/public_html/df-7hq2.php

Security trade-offs: what disable_functions does not do​

The PHP manual itself warns that the directive can be circumvented and is not a sufficient security measure for shared hosting. It slows down an attacker who has uploaded a web shell; it does not lock one in.
  • Every list has gaps. mail(), mb_send_mail() and error_log() in e-mail mode all start the sendmail binary, and together with putenv() that is a well-known way around the list, which is why many admins add putenv once their applications are tested. DirectAdmin's default list also leaves out pcntl_exec; adding it rarely breaks anything, because queue workers use other pcntl functions such as pcntl_signal and pcntl_alarm. Extensions can also start programs from inside their own libraries.
  • Engine bugs. Public exploits covering PHP 7.0 through 8.1 use memory-corruption bugs to bypass the list completely, and PHP's own security policy lists disable_functions and open_basedir bypasses among the issues it does not treat as security vulnerabilities. Keep every PHP build on a supported, patched release.
  • FFI: leave ffi.enable at its default, preload, or set it to false, so ordinary web scripts cannot call C libraries.
  • CageFS, when it is enabled for an account, gives that account its own view of the file system and limits what a successful attacker can see and run, including programs started through exec.
  • open_basedir covers PHP's own file functions only, not external programs.
  • Imunify360 Proactive Defense, where installed, watches PHP scripts as they run and, depending on its mode, logs or blocks suspicious behaviour, so a call that disable_functions allows can still be stopped. If a legitimate task still fails after you opened a function, check the Imunify360 event log first, and exempt only the specific file you reviewed.
  • ModSecurity filters HTTP requests before PHP sees them and stops many attacks on vulnerable plugins before any function is called.

Troubleshooting​

SymptomLikely causeWhat to do
"Call to undefined function exec()" after moving to PHP 8exec is on the list; PHP 8 reports it as undefinedConfirm with the test script, then decide per account
"exec() has been disabled for security reasons"The same, on a PHP 7.x build such as an old alt-phpSame fix; plan the move to a supported version
php.ini edited, nothing changedWrong build or file, a later conf.d file, or no restart (detached lsphp)Check php --ini and the test script, grep php.conf.d, restart the right service
A function removed in the pool config is still disabledPools can only appendChange the global list or use a per-account method
.user.ini or panel PHP settings have no effectINI_SYSTEM directiveUse php.ini, the pool or selector options
phpinfo() shows a short list, yet exec is disabledFPM displays only the pool's valueTrust function_exists(); repeat the full list in the pool line
Site works, Composer or cron fails with the proc_open messageThe CLI reads the same php.iniUse -d for that job or a php-cli.ini
List gone after a CustomBuild updateHand edits in php.ini are overwritten on rebuildKeep it in custom/php_disable_functions and run da build secure_php
Function available, but the call still failsImunify360 blocked it, or the program does not exist inside CageFSCheck the Imunify360 events and the user's CageFS

Frequently asked questions​

Can a user change disable_functions with .user.ini or ini_set()?
No. It is an INI_SYSTEM directive; only php.ini-level files, FPM pools (append only) and the administrator's selector options affect it. On the command line, php -d changes it for that one command only.

Can I enable a function for a single PHP-FPM pool?
Not with a pool setting alone, because a pool can only add to the php.ini list. DirectAdmin's template method makes it possible by moving the list from php.ini into the pools; otherwise use the CloudLinux selector or a separate server.

Why does phpinfo() show a different list than php.ini?
The site may use another PHP build or file, a conf.d file may replace the list, or FPM may be displaying the pool's line instead of the combined list.

Should I disable mail() and putenv()?
Only after testing your applications. Without mail(), forms that do not use SMTP stop sending. Block putenv for websites only, not on the CLI, because Composer calls it directly.

Is disable_functions still worth it with CageFS and Imunify360?
Yes, as one layer. It closes the easiest path from a web shell to the operating system; CageFS, Imunify360 and ModSecurity catch some of what it misses.

Does disable_classes still work?
Up to PHP 8.4, yes. PHP 8.5 removed the directive, so on 8.5 such a line no longer disables any class. Remove it from the templates and php.ini files that 8.5 builds read.

Conclusion and next steps​

disable_functions does its job when it is managed in the right place: php.ini through CustomBuild (plus global_php.ini on CloudLinux) for the global list, FPM pools for stricter accounts, and the selector, DirectAdmin's template method or a separate server for real exceptions. Next steps:
  1. List every PHP build on the server and note which one each site uses.
  2. Put your list into custom/php_disable_functions and run da build secure_php.
  3. Run the test script through each handler and on the CLI, then delete it.
  4. Record every per-account exception and the reason for it.
  5. Repeat the checks after every CustomBuild or alt-php update.
If you would rather have someone else look after these layers, AIOR offers a Linux server management service.
 
Son düzenleyen:

Forum istatistikleri

Konular
925
Mesajlar
932
Üyeler
26
En son üye
Jessicawatson

Çevrimiçi üyeler

Şu anda çevrimiçi üye yok.

Öne çıkan içerik

AIOR
AIOR TEKNOLOJİ

Tüm ihtiyaçlarınız için Teklif alın

Hosting · Domain · Sunucu · Tasarım · Yazılım · Mühendislik · Sektörel Çözümler

Teklif al

7/24 Destek · Anında yanıt

Geri
Üst