domingo, 24 de enero de 2021

Optimizar módulo PHP FPM y opciones de seguridad en PHP

FPM (FastCGI Process Manager) es una implementación alternativa al PHP FastCGI con algunas características adicionales (la mayoría) útiles para sitios web con mucho tráfico. Deshabilitar exponer la versión de PHP, la subida de ficheros, la llamada de URL's externas, las inclusiones fuera del directorio del trabajo,  Veremos también cómo deshabilitar funciones potencialmente peligrosas en PHP. Y no sólo las más habituales (exec ,passthru, shell_exec, system, proc_open, popen, curl_exec, curl_multi_exec, parse_ini_file, show_source). Funciones utilizadas en web shells de php muy conocidas como: PHP-Reverse-Shell Pentestmonkey , Weevely o phpsploit (webshell).

 



Qué es PHP-FPM y características


PHP-FPM es la implementación alternativa más popular de PHP FastCGI, que cuenta con características adicionales realmente útiles para sitios web de alto tráfico. Estas son algunas de ellas:

  • Gestión avanzada que permite detener/arrancar procesos fácilmente.
  • Posibilidad de iniciar hilos de procesos con diferentes uid/gid/chroot/environment y distintos php.ini; sustituye a safe_mode.
  • Registro stdout y stderr.
  • Reinicio de emergencia en caso de destrucción accidental del caché opcode.
  • Soporte acelerado de subidas.
  • Configuración de variable slowlog; para detectar qué funciones tardan en ejecutarse más de lo habitual.
  • Basado en archivos de configuración php.ini.
  • Mejora de FastCGI, como fastcgi_finish_request(); una función especial para detener y descargar todos los datos mientras se continúa haciendo un proceso más largo como la conversión de vídeos o el procesamiento de estadísticas.
  • Estadísticas básicas (similar al módulo mod_status de Apache)

PHP-FPM puede utilizar uno de los tres tipos de gestión de procesos:

Entonces, vemos que hay tres valores posibles:

  1. Estático: Se mantendrá un número fijo de procesos PHP pase lo que pase.
  2. Dinámico: Conseguimos especificar el número mínimo y máximo de procesos que php-fpm se mantendrá vivo en cualquier momento.
  3. Bajo demanda: Los procesos se crean y destruyen, bueno, bajo demanda.
En inglés:
  1.     static
  2.     dynamic
  3.     ondemand




Variables importantes:

  • pm: Establece cómo el administrador de procesos de PHP controlará el número de subprocesos. Valores posibles: "static", "ondemand", "dynamic".
  • pm.max_children - Establece el límite de peticiones simultáneas que pueden servir PHP para este servicio.
  • pm.start_servers - Número de subprocesos o hilos creados en el inicio. Solo se usa cuando "pm" está definido a "dynamic".
  • pm.min_spare_servers - Número mínimo de procesos en estado "idle". Solo se usa cuando "pm" está definido en "dynamic".
  • pm.max_spare_servers - Número máximo de procesos en estado "idle". Solo se usa cuando "pm" está definido en "dynamic".
  • pm.process_idle_timeout  - Número de segundos tras los cuáles un proceso en estado "idle" será cancelado. Solo se usa cuando "pm" está definido a "ondemand". Unidades disponibles: s(egundos)(por defecto), m(inutos), h(oras), o d(ías).
Configurar el Pool:

/etc/php-fpm.d/web.conf

Ejemplo:

[web]
user = httpd
group = httpd

;antes
;listen = localhost:8001


listen.owner = httpd
listen.group = httpd
listen.mode = 0660
listen = /var/php-fpm/web.sock

;pm = static
;pm.max_children = 100

; virtualmin defecto
pm = dynamic
pm.max_children = 500
pm.start_servers = 20
pm.min_spare_servers = 15
pm.max_spare_servers = 40
;pm.max_spare_servers = 30
;pm.max_requests = 500


slowlog = /var/log/php-fpm/slow.log
request_slowlog_timeout = 1s

php_admin_value[upload_tmp_dir] = /home/httpd/tmp
php_admin_value[session.save_path] = /home/httpd/tmp

;status activado phpfpm
pm.status_path = /fpm-statusweb


php_value[file_uploads] = off
php_value[expose_php] = off
; memory_limit por defecto es 128M
php_value[memory_limit] = 512M
php_value[register_argc_argv] = On
php_value[date.timezone] = Europe/Madrid


Llamarlo en Apache:

    <FilesMatch \.php$>
        # php
        SetHandler proxy:unix:/var/php-fpm/web.sock|fcgi://127.0.0.1
    </FilesMatch>


Setting Value

Calcular CPU Cores

echo Total CPU Cores = $(( $(lscpu | awk '/^Socket/{ print $2 }') * $(lscpu | awk '/^Core/{ print $4 }') ))


Calcular consumo medio de memoria del proceso PHP


 ps --no-headers -o "rss,cmd" -C php-fpm | awk '{ sum+=$1 } END { printf ("Average memory per PHP process: " "%d%s\n", sum/NR/1024,"M") }'


Setting Value
max_children (Total RAM – Memory used for Linux, DB, etc.) / process size
start_servers Number of CPU cores x 4
min_spare_servers Number of CPU cores x 2
max_spare_servers Same as start_servers


max_children (Total RAM - Memory used for Linux, DB, etc.) / process size
start_servers Number of CPU cores x 4
min_spare_servers Number of CPU cores x 2
max_spare_servers Same as start_servers


Ejemplo Tunning
pm = dynamic
pm.max_children = 100
pm.start_servers = 32
pm.min_spare_servers = 16
pm.max_spare_servers = 32
pm.max_requests = 200


; Optimized for php-fpm request size of 55MB on AWS EC2 m4.xlarge (4CPU cores, 16GB RAM)
pm = dynamic
pm.max_children = 256
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 20
pm.max_requests = 1000
Una excelente manera para saber si la configuración en nuestro servidor es correcta, es monitorizar el ftpm "status"

Con el fichero de configuración del "pool"

# CentOS default pool
/etc/php-fpm.d/www.conf
# Ubuntu default pool
/etc/php/7.0/fpm/pool.d/www.conf

Debemos activar ftpm status

; Uncomment the following to allow FPM status
pm.status_path = /status



En servidor ngninx debemos activar y proteger el "directorio" status

# Enable php-fpm status page
location ~ ^/(status|ping)$ {
## disable access logging for request if you prefer
access_log off;

## Only allow trusted IPs for security, deny everyone else
# allow 127.0.0.1;
# allow 1.2.3.4; # your IP here
# deny all;

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_index index.php;
include fastcgi_params;
## Now the port or socket of the php-fpm pool we want the status of
fastcgi_pass 127.0.0.1:9000;
# fastcgi_pass unix:/run/php-fpm/your_socket.sock;
}

En Apache:


<LocationMatch "/status">
 Order deny,allow
 Deny from all
 Allow from localhost
ProxyPass fcgi://localhost:8001/status
</LocationMatch>
Ahora sólo hay que llamar la url

curl -L 127.0.0.1/status

Y el resultado:

pool:                 web
process manager:      dynamic
start time:           23/Dec/2020:11:45:16 +0100
start since:          108329
accepted conn:        4577
listen queue:         0
max listen queue:     1
listen queue len:     128
idle processes:       3
active processes:     1
total processes:      4
max active processes: 4
max children reached: 0
slow requests:        0
Vigilar especialmente los valores

  • max children reached
Para un seguimiento básico, debe estar atento al "max children reached". El valor "max children reached" aumentará cuando se alcance el límite del proceso y php-fpm no pueda crear más niños para manejar las solicitudes. (Este valor solo importa cuando php-fpm está operando en modo dinámico y / o bajo demanda, siendo dinámico el modo predeterminado).

Si "max children reached" es algo por encima de 0, eso significa que tuvo solicitudes para la cola php-fpm y el usuario tuvo que esperar a que un proceso estuviera libre para ser manejado. Esto podría indicar un pico de tráfico que está sobrecargando su servidor, o quizás un problema de la base de datos que está ralentizando la respuesta a las solicitudes y afectando a los usuarios. Moraleja de este valor, es que desea saber cuándo está por encima de 0.

Puede crear un monitor de URL que compruebe ese valor y active una alerta o un correo electrónico si el valor es superior a 0. Si esta alerta se activa, sabrá que actualmente se encuentra con un problema.

  • idle processes
¿Qué pasa si quiere saber antes de que ocurra un problema? Esto requiere que sepa un poco más sobre su configuración php-fpm para hacerlo bien. Php-fpm se puede ejecutar en varios modos diferentes: estático, dinámico o bajo demanda. Dinámico es el predeterminado, así que hablemos de lo que significa este valor cuando está configurado como dinámico.

En su configuración de php-fpm, tiene una serie de valores establecidos para controlar la cantidad de procesos que pueden generar y manejar solicitudes

Advertencias (Warnings) que podemos ver en el log del pool (por defecto en /var/log/php-fpm/error.log)  para incrementar valores:

WARNING: [pool www] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning x children, there are x idle, and x total children


WARNING: [pool web ] server reached pm.max_children setting (50), consider raising it 

Opciones de Seguridad en PHP


Exponer versión PHP


Es importante esconder a versión PHP

X-Powered-By: PHP/7.4.x

Ocultar la versión PHP que se ejecuta en el servidor añadir directiva en todos los phpfpm

expose_php 0

expose_php = off

No muestres errores por pantalla

display_errors = Off

Pero guarda los errores en fichero

log_errors = on

error_log=/var/log/httpd/php_scripts_error.log

Si está habilitado, allow_url_fopen permite que las funciones de archivo de PHP, como file_get_contents () y las declaraciones include y require, puedan recuperar datos de ubicaciones remotas, como un sitio web o FTP.

  • include(), require(),  o file_get_contents()


Ejemplo

file_get_contents('https://www.example.com');

La opción allow_url_fopen permite que las funciones de archivo de PHP, como file_get_contents () y las declaraciones include y require, puedan recuperar datos de ubicaciones remotas utilizando protocolos ftp o http. Los programadores con frecuencia olvidan esto y no filtran adecuadamente las entradas cuando pasan datos proporcionados por el usuario a estas funciones, lo que las abre a vulnerabilidades de inyección de código. Una gran cantidad de vulnerabilidades de inyección de código reportadas en aplicaciones web basadas en PHP son causadas por la combinación de habilitar allow_url_fopen y un filtrado de entrada incorrecto. Edite /etc/php.d/security.ini y configura la siguiente directiva:

allow_url_fopen = Off

Ten en cuenta que algunos CMS, como PrestaShop necesitan allor_url_fopen on (activado)  para funcionar correctamente. Ya que realizan llamadas a terceros.

También recomiendo deshabilitar allow_url_include por razones de seguridad:

allow_url_include = Off

Cookies seguras

session.cookie_httponly = On

session.cookie_secure = On 

Recuerda 

php_value o php_flag según corresponda

phpfpm (la sintaxis es algo diferente, la variable va entre [corchetes]

php_value[upload_max_filesize] = 64M
php_value[post_max_size] = 64M
php_flag[display_errors] = Off
Es importante usar php_admin_value para impedir que los usuarios sobrescriban la configuración de las directivas que no queremos que se cambien:
It’s also possible to prevent config values from being overridden by ini_set by using php_admin_value and php_admin_flag.
Funciones deshabilitadas para que no hagan "override" config original (con el php admin de arriba ya no hará falta)

  • ini_set
  • error_reporting
  • set_include_path
Un usuario podría cambiar la configuración en un fichero php o usando un .htaccess

O bien modificando el fichero de configuración apache httpd.conf 

Ejemplo de configuración de Apache

<IfModule mod_php5.c>
  php_value include_path ".:/usr/local/lib/php"
  php_admin_flag engine on
</IfModule>
Por eso es mejor utilizar admin:

php_admin_value[upload_max_filesize] = 64M
php_admin_value[post_max_size] = 64M
php_admin_flag[display_errors] = Off


  • php_flag nombre on|off

Usado para establecer una directiva de configuración de tipo boolean. Sólo puede usarse con las directivas de tipo PHP_INI_ALL y PHP_INI_PERDIR.

  • php_admin_value nombre valor

Establece el valor de la directiva especificada. Esto no se puede usar en ficheros .htaccess. Ninguna directiva establecida con php_admin_value podrá ser sobrescrita por .htaccess o por ini_set(). Para borrar un valor establecido previamente use none como valor.


  • php_admin_flag nombre on|off

Usado para establecer una directiva de configuración de tipo boolean. Esto no se puede usar en ficheros .htaccess. Ninguna directiva establecida con php_admin_flag podrá ser sobrescrita por .htaccess o por ini_set().


Rutas (path)

include_path = /home/user/public_html

open_basedir = /home/user/public_html

Recuerda /home/user/public_html incluye /home/user/public_html/* y /home/user/public_html/ (acabado con la barra) sólo ese directorio

Si no utilizas la función de subir ficheros, la puedes, debes deshabilitar:

php_value[file_uploads] = off

Deshabilitar Funciones PHP

Funciones potencialmente peligrosas en PHP.

  • Cómo deshabilitar funciones peligrosas de PHP.

Aquí está el resumen rápido de lo que puede hacer con las funciones php.

Ejemplos básicos.

  • system: muestra inmediatamente toda la salida y se usa para mostrar texto
  • passthru: devuelve la salida inmediatamente, pero se usa para datos binarios y se usa para devolver datos binarios en lugar de ascii.
  • shell_exec devuelve la salida completa del comando, cuando el comando terminó de ejecutarse.
  • exec: solo devuelve la última línea de la salida generada.
  • proc_open y popen


Imagina que encontraste una carga de archivo o una vulnerabilidad de inclusión remota de archivos donde puedes cargar / solicitar tu php-reverse-shell y obtener un shell rápido. Sin embargo, es posible que el administrador haya desactivado todas las funciones php anteriores, así que veamos estas 4 funciones php peligrosas en acción antes de pasar a las otras funciones.

root@kali:~# cat system.php
system("whoami");
?>
root@kali:~#
root@kali:~# php system.php
root
root@kali:~#
¡Fantástico! Podemos ejecutar comandos del sistema operativo y obtener el resultado del comando. Entonces, ¿qué sucede si deshabilitamos todas estas 4 funciones? Deshabilite las funciones peligrosas de php del archivo php.ini en

 /etc/php/7.2/cli/php.ini

y configura

display_errors = On

Si estás realizando este ejercicio a través de su apache, es posible que deba deshabilitarlos en /etc/php/7.2/apache2/php.ini. Ejecutemos los mismos archivos php nuevamente.

root@kali:~# php pasthru.php
PHP Warning:  passthru() has been disabled for security reasons in /root/pasthru.php on line 2
Warning: passthru() has been disabled for security reasons in /root/pasthru.php on line 2
root@kali:~#


Ten en cuenta también que estamos recibiendo este error debido a la función Display_errors = On. Puede habilitar esta función en el entorno de producción, pero debes deshabilitarla en el entorno en vivo. 

PHP-Reverse-Shell Pentestmonkey , Weevely, phpsploit

Todos sabemos el famoso php-reverse-shell para linux de pentest-monkey: 


Podemos intentar obtener un shell con el shell inverso anterior, ya que usa la función proc_open.

  • Disable proc_open function


Msfvenom PHP Shell

root@kali:/usr/share/webshells/php# msfvenom -p php/reverse_php LHOST=127.0.0.1 LPORT=443 -f raw > shell.php

Utiliza otras funciones:

if($rRMZDi('shell_exec')and!$WzjhDqn('shell_exec',$dis)){

  [..]
      }else
      if($rRMZDi('passthru')and!$WzjhDqn('passthru',$dis)){

 [..]

      }else
      if($rRMZDi('popen')and!$WzjhDqn('popen',$dis)){
   
 [..]

      }else
      if($rRMZDi('exec')and!$WzjhDqn('exec',$dis)){

     [..]

      }else
      if($rRMZDi('system')and!$WzjhDqn('system',$dis)){

 [..]

      }else
      if($rRMZDi('proc_open')and!$WzjhDqn('proc_open',$dis)){
        $handle=proc_open($c,array(array('pipe','r'),array('pipe','w'),array('pipe','w')),$pipes);

 [..]

Utiliza las funciones

  • shell_exec, passthru, popen, exec, system and proc_open
  • fsockopen and socket_create

Mail () y Putenv ()

Aquí hay un método ordenado que abusa de la funcionalidad mail () y putenv (): https://www.tarlogic.com/en/blog/how-to-bypass-disable_functions-and-open_basedir/

Puede descargar la herramienta Chankro desde github y probarla también. Funcionará sin problemas para omitir las funciones disable_functions. Para bloquear este método, también necesita deshabilitar la función putenv ().

Mod_cgi


Imap_open ()

Una de las últimas funciones de omisión de disable_functions es usar la función imap_open. Es muy similar al bypass de mail () y putenv (). Aquí puede encontrar una gran explicación aquí: https://lab.wallarm.com/rce-in-php-or-how-to-bypass-disable-functions-in-php-installations-6ccdbf4f52bb

Para evitar la omisión de imap_open, debe establecer imap.enable_insecure_rsh en 0. Es 0 por defecto, pero si está en el entorno de prueba y desea jugar con la vulnerabilidad, puede establecerlo en 1 y ver el comportamiento con strace herramienta en Linux.
Así pues tu fichero de configuración php.ini debería contener al menos:

php_admin_value[disable_functions] = system, passthru, shell_exec, exec, proc_open, popen, fsockopen, socket_create, imap_open, mail, putenv


Pero esto no es suficiente, hay muchas más funciones potencialmente peligrosas:

allow_url_fopen, allow_url_include, apache_child_terminate, apache_get_modules, apache_note, apache_setenv, curl_exec, curl_multi_exec, define_syslog_variables, diskfreespace, disk_free_space, disk_total_space, dl, escapeshellarg, escapeshellcmd, exec, passthru, shell_exec, system, popen, curl_exec, curl_multi_exec, pcntl_exec, pcntl_exec, putenv, proc_close, proc_get_status, proc_nice, proc_terminate, popen, pclose, ini_alter, virtual, openlog, escapeshellcmd, escapeshellarg, parse_ini_file, show_source, imap_open, ftp_connect, posix_uname, posix_getuid, posix_getgid, apache_setenv, define_syslog_variables, eval, ftp_connect, ftp_exec, ftp_get, ftp_login, ftp_nb_fput, ftp_put, ftp_raw, ftp_rawlist, highlight_file, ini_alter, ini_restore, inject_code, openlog, phpAds_remoteInfo, phpAds_XmlRpc, phpAds_xmlrpcDecode, phpAds_xmlrpcEncode, popen, posix_getpwuid, posix_kill, posix_mkfifo, posix_setpgid, posix_setsid, posix_setuid, posix_setuid, posix_uname, proc_close, proc_get_status, proc_nice, proc_open, proc_terminate, syslog, xmlrpc_entity_decode, pipe, parse_ini_file, show_source, dl, ini_alter, virtual, openlog, apc_add, apc_bin_dump, apc_bin_dumpfile, apc_bin_loadfile, apc_cache_info, apc_cas, apc_clear_cache, apc_compile_file, apc_dec, apc_define_constants, apc_delete_file, apc_delete, apc_exists, apc_fetch, apc_inc, apc_load_constants, apc_store, symlink, eval, extract, fsockopen, getcwd, getenv, getlastmo, getmygid, getmyinode, getmypid, getmyuid, get_cfg_var, get_current_user, ini_alter, ini_restore, dl, exec, shell, proc_close, ini_restore, ini_set, mail, parse_ini_file, pcntl_alarm, pcntl_exec, pcntl_fork, pcntl_getpriority, pcntl_get_last_error, pcntl_setpriority, pcntl_signal, pcntl_signal_dispatch, pcntl_sigprocmask, pcntl_sigtimedwait, pcntl_sigwaitinfo, pcntl_strerrorp, pcntl_wait, pcntl_waitpid, pcntl_wexitstatus, pcntl_wifexited, pcntl_wifsignaled, pcntl_wifstopped, pcntl_wstopsig, pcntl_wtermsig, phpinfo, php_uname, popen, posixc, posix_getlogin, posix_getpwuid, posix_kill, posix_mkfifo, posix_setpgid, posix_setsid, posix_setuid, posix_ttyname, posix_uname, proc_close, proc_get_status, proc_nice, proc_terminate, ps_aux, readlink, runkit_function_rename, show_source, socket_create, stream_select, symlink, syslog


Aunque algunas de estas funciones son utilizadas por WordPress por ejemplo:

  • getenv
  • extract
  • php_uname
  • ini_set
Deberás mirar el error_log del PHP

error_log = /var/log/php-fpm/php_errors.log
; Log errors to syslog.
;error_log = syslog

Para buscar errores:

Got error 'PHP message: PHP Warning:  extract() has been disabled for security reasons in  /home/dominio/public_html
 

 

No hay comentarios:

Publicar un comentario