Est-il possible d'ajuster un paramètre de noyau pour permettre à un programme utilisateur de se lier au port 80 et 443?
La raison pour laquelle je pose cette question est qu’il est idiot de permettre à un processus privilégié d’ouvrir un socket et d’écouter. Tout ce qui ouvre un socket et écoute présente un risque élevé, et les applications à haut risque ne doivent pas être exécutées en tant que root.
Je préférerais de beaucoup essayer de déterminer quel processus non privilégié écoute sur le port 80 plutôt que d'essayer de supprimer les logiciels malveillants qui s'y enfoncent avec des privilèges root.
Je ne suis pas sûr de ce que les autres réponses et commentaires ici font référence. C'est possible assez facilement. Il existe deux options, toutes deux permettant l’accès à des ports peu nombreux sans avoir à élever le processus à la racine:
Option 1: utilisez CAP_NET_BIND_SERVICE
pour accorder un accès au port avec un numéro bas à un processus:
Avec cela, vous pouvez accorder un accès permanent à un binaire spécifique pour vous lier à des ports de faible numéro via la commande setcap
:
Sudo setcap CAP_NET_BIND_SERVICE=+eip /path/to/binary
Pour plus de détails sur la partie e/i/p, voir cap_from_text
.
Cela fait, /path/to/binary
pourra se lier aux ports dont le numéro est bas. Notez que vous devez utiliser setcap
sur le binaire lui-même plutôt qu'un lien symbolique.
Option 2: Utilisez authbind
pour accorder un accès unique, avec un contrôle utilisateur/groupe/port plus précis:
L'outil authbind
( page de manuel ) existe précisément pour cela.
Installez authbind
en utilisant votre gestionnaire de paquets préféré.
Configurez-le pour accorder l'accès aux ports appropriés, par exemple. pour autoriser 80 et 443 de tous les utilisateurs et groupes:
Sudo touch /etc/authbind/byport/80
Sudo touch /etc/authbind/byport/443
Sudo chmod 777 /etc/authbind/byport/80
Sudo chmod 777 /etc/authbind/byport/443
Maintenant exécutez votre commande via authbind
(en spécifiant éventuellement --deep
ou d’autres arguments, voir la page de manuel):
authbind --deep /path/to/binary command line args
Par exemple.
authbind --deep Java -jar SomeServer.jar
Il y a des avantages et des inconvénients à la fois de ce qui précède. L'option 1 accorde la confiance au binaire mais ne fournit aucun contrôle sur l'accès par port. L'option 2 accorde la confiance à utilisateur/groupe et permet de contrôler l'accès par port, mais AFAIK ne prend en charge que IPv4.
Dale Hagglund est sur place. Je vais donc simplement dire la même chose, mais d'une manière différente, avec des détails et des exemples. ☺
La bonne chose à faire dans les mondes Unix et Linux est la suivante:
Vous avez une mauvaise idée de l'endroit où le risque est élevé. Le risque élevé réside dans lecture sur le réseau et en agissant sur ce qui est lu pas dans les simples actions d'ouvrir un socket, de le lier à un port et d'appeler listen()
. C'est la partie d'un service qui fait la communication réelle qui est le risque élevé. Les parties qui s'ouvrent, bind()
et listen()
, et même (dans une certaine mesure) la partie qui accepts()
ne constituent pas un risque élevé et peuvent être exécutées sous l'égide du superutilisateur. Ils n'utilisent pas et ne traitent pas (à l'exception des adresses IP source dans le cas accept()
) les données qui sont sous le contrôle d'inconnus inconnus sur le réseau.
Il y a plusieurs façons de le faire.
inetd
name__Comme le dit Dale Hagglund, l'ancien "réseau superserver" inetd
le fait. Le compte sous lequel le processus de service est exécuté est l’une des colonnes de inetd.conf
. Il ne sépare pas la partie écoute et la partie suppression des privilèges en deux programmes distincts, petits et faciles à contrôler, mais il sépare le code de service principal en un programme séparé, exec()
ed, dans un processus de service créé avec un descripteur de fichier ouvert. pour la prise.
La difficulté de l'audit n'est pas un problème, il suffit de vérifier le programme en question. inetd
name Le problème majeur de __ n'est pas tant l'audit, mais plutôt le fait qu'il ne fournit pas un contrôle de service d'exécution à granularité fine, comparé aux outils plus récents.
Les paquets UCSPI-TCP et daemontools de Daniel J. Bernstein ont été conçus pour le faire conjointement. On peut également utiliser le jeu d'outils daemontools-encore largement équivalent de Bruce Guenter.
Le programme permettant d'ouvrir le descripteur de fichier de socket et de se connecter au port local privilégié est tcpserver
NAME_ , à partir de UCSPI-TCP. Il effectue à la fois la listen()
et la accept()
.
tcpserver
génère ensuite un programme de service qui supprime lui-même les privilèges root (car le protocole utilisé implique de démarrer en tant que superutilisateur puis de se "connecter", comme c'est le cas par exemple avec un démon FTP ou SSH) ou setuidgid
NAME_ qui est un petit programme autonome et facilement vérifiable qui supprime uniquement les privilèges, puis enchaîne les charges vers le programme de service proprement dit (aucune partie de ce programme ne s'exécute donc avec des privilèges de superutilisateur, comme c'est le cas avec qmail-smtpd
).
Un script de service run
serait donc par exemple (celui-ci pour dummyidentd pour la fourniture d'un service null IDENT):
#!/bin/sh -e
exec 2>&1
exec \
tcpserver 0 113 \
setuidgid nobody \
dummyidentd.pl
My nosh package est conçu pour cela. Il a un petit utilitaire setuidgid
name__, comme les autres. Une légère différence réside dans le fait qu’il est utilisable avec les services "LISTEN_FDS" ainsi que les services UCSPI-TCP de la même manière que le programme systemd
name __- style. Le programme tcpserver
traditionnel est remplacé par deux programmes distincts: tcp-socket-listen
et tcp-socket-accept
.
Encore une fois, les utilitaires à but unique apparaissent et se chargent en chaîne. Une particularité intéressante de la conception est que l'on peut supprimer les privilèges de superutilisateur après listen()
mais avant même accept()
. Voici un script run
pour qmail-smtpd
qui fait exactement cela:
#!/bin/nosh
fdmove -c 2 1
clearenv --keep-path --keep-locale
envdir env/
softlimit -m 70000000
tcp-socket-listen --combine4and6 --backlog 2 ::0 smtp
setuidgid qmaild
sh -c 'exec \
tcp-socket-accept -v -l "${LOCAL:-0}" -c "${MAXSMTPD:-1}" \
ucspi-socket-rules-check \
qmail-smtpd \
'
Les programmes qui s'exécutent sous l'égide du superutilisateur sont les petits outils de chargement de chaîne indépendants du service fdmove
name__, clearenv
name__, envdir
name__, softlimit
name__, tcp-socket-listen
et setuidgid
name__. Au moment où sh
est démarré, le socket est ouvert et lié au port smtp
et le processus ne dispose plus des privilèges de superutilisateur.
Les packages s6 _ et s6-networking de Laurent Bercot ont été conçus pour le faire conjointement. Les commandes sont structurellement très similaires à celles de daemontools
et UCSPI-TCP.
Les scripts run
seraient sensiblement les mêmes, à l'exception de la substitution de s6-tcpserver
pour tcpserver
et s6-setuidgid
pour setuidgid
name__. Cependant, on pourrait aussi choisir d'utiliser le jeu d'outils execline de M. Bercot en même temps.
Voici un exemple de service FTP légèrement modifié par rapport à l'original de Wayne Marshall , qui utilise execline, s6, s6-networking et le programme du serveur FTP de publicfile :
#!/command/execlineb -PW
multisubstitute {
define CONLIMIT 41
define FTP_ARCHIVE "/var/public/ftp"
}
fdmove -c 2 1
s6-envuidgid pubftp
s6-softlimit -o25 -d250000
s6-tcpserver -vDRH -l0 -b50 -c ${CONLIMIT} -B '220 Features: a p .' 0 21
ftpd ${FTP_ARCHIVE}
Le ipsvd de Gerrit Pape est un autre ensemble d'outils qui va dans le même sens que ucspi-tcp et s6-networking. Les outils sont chpst
et tcpsvd
cette fois, mais ils font la même chose, et le code à haut risque qui lit, traite et écrit les éléments envoyés sur le réseau par des clients non fiables est toujours dans un programme séparé.
Voici exemple de M. Pape sur _ { fnord
NAME_ _ dans un script run
name__:
#!/bin/sh
exec 2>&1
cd /public/10.0.5.4
exec \
chpst -m300000 -Uwwwuser \
tcpsvd -v 10.0.5.4 443 sslio -v -unobody -//etc/fnord/jail -C./cert.pem \
fnord
systemd
name__systemd
NAME_ , le nouveau système de supervision du service et d'init disponible dans certaines distributions Linux, est destiné à faire ce que inetd
peut faire }. Cependant, il n’utilise pas une suite de petits programmes autonomes. Il faut malheureusement auditer systemd
dans son intégralité.
Avec systemd
name__, on crée des fichiers de configuration pour définir un socket que systemd
écoute et un service qui systemd
démarre. Le fichier "unité" de service contient des paramètres qui permettent de contrôler le processus de service, y compris son utilisateur.
systemd
effectue tout le travail d'ouverture du socket en le liant à un port et en appelant listen()
(et, si nécessaire, accept()
) dans le processus n ° 1 en tant que superutilisateur et le service. Le processus qu’il génère s’exécute sans les privilèges de superutilisateur.
J'ai une approche assez différente. Je voulais utiliser le port 80 pour un serveur node.js. Je n'ai pas pu le faire car Node.js a été installé pour un utilisateur autre que Sudo. J'ai essayé d'utiliser des liens symboliques, mais cela n'a pas fonctionné pour moi.
Ensuite, j'ai appris que je pouvais transférer les connexions d'un port à un autre. J'ai donc démarré le serveur sur le port 3000 et configuré un transfert de port du port 80 au port 3000.
Ce lien fournit les commandes qui peuvent être utilisées à cette fin. Voici les commandes -
localhost/loopback
Sudo iptables -t nat -I OUTPUT -p tcp -d 127.0.0.1 --dport 80 -j REDIRECT --to-ports 3000
externe
Sudo iptables -t nat -I PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 3000
J'ai utilisé la deuxième commande et cela a fonctionné pour moi. Je pense donc que c'est un moyen terme pour ne pas autoriser les processus utilisateur à accéder directement aux ports inférieurs, mais leur permettre d'accéder via le transfert de port.
Votre instinct est tout à fait correct: c’est une mauvaise idée d’avoir un grand programme complexe exécuté en tant que root, car leur complexité les rend difficiles à faire confiance.
Cependant, il est également déconseillé de permettre aux utilisateurs normaux de se connecter à des ports privilégiés, car ces ports représentent généralement des services système importants.
L’approche standard pour résoudre cette contradiction apparente est séparation des privilèges . L'idée de base est de séparer votre programme en deux parties (ou plus), chacune d'entre elles constituant une partie bien définie de l'application globale et communiquant par le biais d'interfaces limitées.
Dans l'exemple que vous donnez, vous voulez séparer votre programme en deux parties. Celui qui s'exécute en tant que root s'ouvre et se connecte au socket privilégié, puis le transfère d'une manière ou d'une autre à l'autre partie, qui s'exécute en tant qu'utilisateur normal.
Ces deux moyens principaux pour réaliser cette séparation.
Un seul programme qui commence en tant que root. La première chose à faire est de créer le socket nécessaire, de la manière la plus simple et la plus limitée possible. Ensuite, il supprime les privilèges, c’est-à-dire qu’il se convertit en un processus normal en mode utilisateur et effectue tout autre travail. Il est difficile de supprimer des privilèges correctement. Veuillez donc prendre le temps d’étudier la meilleure façon de le faire.
Une paire de programmes qui communiquent via une paire de sockets créée par un processus parent. Un programme de pilote non privilégié reçoit les arguments initiaux et effectue peut-être une validation de base des arguments. Il crée une paire de sockets connectés via socketpair (), puis lance et exécute deux autres programmes qui feront le vrai travail et communiqueront via la paire de sockets. L'une d'elles est privilégiée et créera le socket du serveur, ainsi que toute autre opération privilégiée, et l'autre effectuera l'exécution d'application la plus complexe et donc la moins digne de confiance.