Écouter sur le port 80 sans être root


Sous beaucoup d’OS, tous les ports d’un nombre inférieur à 1024 ne peuvent pas être utilisés par des processus sans avoir les privilèges administrateurs. Néanmoins, on a pas vraiment envie que son app bricolée un lendemain de cuite soit lancée en root, pour que la moindre faille de sécurité donne l’accès total à son système.

Beaucoup de logiciels se lancent en root, et relâchent leurs privilèges a posteriori. C’est ce que faisait Apache a une époque (peut être le fait-il toujours, j’ai pas cheché). Nginx lui, lance un processus racine en root, et spawn des enfants avec un utilisateur normal.

Mais nous, on a pas la foi de se faire chier à faire ça, donc généralement, on met nginx en front et il gère ça pour nous.

Sauf que, parfois, on a pas envie de mettre 40 couches devant notre app. Par exemple, si on utilise crossbar.io (ouai, j’ai encore réussi à le placer \o/), le logiciel est clairement capable d’être en front tout seul.

Bonne nouvelle, sur les Linux modernes, les exécutables peuvent avoir des permissions avancées, comme “pourvoir changer l’horloge système” ou “empêcher la mise en veille”. Ces permissions sont changeables avec l’outil setcap.

Sous Ubuntu, ça s’installe avec :

sudo apt-get install libcap2-bin

Puis on choisit l’exécutable à qui on veut donner nos nouvelles permissions. Dans mon cas, le Python du virtualenv de mon projet :

$ pew workon super_projet
$ which python
/home/sam/.local/share/virtualenvs/super_projet/bin/python

Je check si il a pas déjà des permissions (normalement non) :

$ sudo getcap `which python`

Nada. Good.

Un petit coup de man setcap nous liste les permissions utilisables, et on peut voire que CAP_NET_BIND_SERVICE est la permission qui permet d’autoriser un exécutable à binder n’importe quel port.

On rajoute les permissions :

sudo setcap cap_net_bind_service=+ep `which python`

On check que les permissions ont bien été ajoutées :

$ sudo getcap `which python`
/home/sam/.local/share/virtualenvs/super_projet/bin/python = cap_net_bind_service+eip

On a rajouté avec le + la permission cap_net_bind_service pour les cas e et p qui correspondent aux premières lettres de ces définitions :

Permitted (formerly known as forced):
    These capabilities are automatically permitted to the thread, regardless of the thread's inheritable capabilities. 

Inheritable (formerly known as allowed):
    This set is ANDed with the thread's inheritable set to determine which inheritable capabilities are enabled in the permitted set of the thread after the execve(2). 

Effective:
    This is not a set, but rather just a single bit. If this bit is set, then during an execve(2) all of the new permitted capabilities for the thread are also raised in the effective set. If this bit is not set, then after an execve(2), none of the new permitted capabilities is in the new effective set.

Et je n’ai absolument rien compris à celles-ci, je sais juste que ça marche.

Voilà, maintenant tout ce que vous lancez avec le Python de ce virtualenv peut se binder au port 80.

Si vous n’aimez pas l’idée de donner cette permission à tout un Python, il existe une alternative : rediriger tout ce qui rentre sur le port 80 vers un autre port.

Pour ça on peut utiliser un autre soft :

sudo apt-get install socat

Par exemple, balancer tout le trafic du port 80 vers le port 8080 :

socat tcp-listen:80,fork,reuseaddr tcp:localhost:8080

Et pouf, votre appli qui écoute sur le port 8080 reçoit le trafic du port 80.

C’est plus simple, mais il faut le faire pour chaque port, et s’assurer que la commande est bien lancée au démarrage du serveur.

16 thoughts on “Écouter sur le port 80 sans être root

  • bobol

    Et sinon, pour tester une app, on peut la la lancer sur un port utilisateut (genre 8081 par ex) et faire une redirection du port 80 sur le 8081 avec masquerade ?

  • Symen

    J’imagine qu’il proposait de faire la même chose qu’avec socat, mais en utilisant iptables (en faisant du PAT) avec une commande du genre:

    iptables -t nat -A PREROUTING -p tcp –dport 80 -j REDIRECT –to-port 8080

  • Sam Post author

    D’ailleurs, je pense que ça consomme moins de ram d’utiliser ipatables.

  • buffalo974

    Comment les développeurs choisissent ils le port ?

    Par exemple pour Flask , c’ est 5000. Superstition fétiche ou contrainte hardware ?

    Pourquoi ils n’ ont pas pris 80 ?

    Pourquoi rediriger les ports à écouter ?

    Une illustration utile pour une bidouille de débutant sur Flask , bottle ou cherrypy ?

    Conséquences pour la sécurité ?

  • Thomas M

    Du coup n’importe quel utilisateur peut lancer python et s’accaparer le port 80.

    Pas forcément grave mais à garder en tête.

    Ah oui, et pour un site à fort trafic mieux vaut utiliser tout de même Nginx, il sera plus à même de gérer les connexions réseaux entrantes.

  • cyp

    Pour le 5000 aucune idée, ils ont pris ce qui venait probablement, en fait c’est même un port déjà “réservé” https://www.grc.com/port_5000.htm mais c’est toujours plus facile à retenir 5317

    A la limite le 8080 pourrait être utilisé https://www.grc.com/port_8080.htm mais vu que c’est destiné avant tout à un usage “local” ça ne changerait pas grand chose à par la satisfaction d’être un peu plus en phase avec les standard.

    Si il n’utilisent pas le 80 je dirais:

    pour la raison évoqué dans l’article (problème d’autorisation dans l’OS)
    parce que c’est plus pratique pour le dev et les test si sur la même machine le 80 est utilisé par quelque chose de plus stable
    car il y a pas mal de chance que le 80 soit déjà utilisé (même le 8080 par exemple, typiquement une install par défaut d’Apache et de Tomcat sur bcp de distribution Linux)
    parce que souvent c’est des serveurs web peu adaptés à un accés public (sécurité, performance, multiple protocole et standard pas forcément supporté….) et qu’il sont bien mieux derrière un reverse proxy à ne gérer que le traffic applicatif sans que leur dev est besoin de se préocupper du reste

    Rediriger les port ça évite le “:5000” dans l’adresse, plus pratique et surtout ça permet de tester dans des conditions plus proche du fonctionnement final (adresse généré, plus facile pour les non techniciens d’y accéder….).

    Niveau sécurité un mix des truc précédents.

  • Sam Post author

    @buffalo974 :

    Par exemple pour Flask , c’ est 5000. Superstition fétiche ou contrainte hardware ?

    C’est juste le port par défaut, ça se configure. Il faut prendre un port qu’on pense ne pas être trop utilisé par d’autres software, mais facile à retenir puisqu’on va taper l’adresse dans son navigateur lors du dev. Après c’est freestyle.

    Pourquoi ils n’ ont pas pris 80 ?

    Parce que personne n’a envie de donner les droits root à flask ou se faire chier à utiliser les astuces de cet article en mode développement.

    Pourquoi rediriger les ports à écouter ?

    Parce que, comme écrit dans l’article, tout port sous 1024 ne peut être écouté que si on a les droits admin.

    Une illustration utile pour une bidouille de débutant sur Flask , bottle ou cherrypy ?

    C’est pareil, l’article marche pour flask, bottle ou cherrpy. Mais généralement on place ces derniers derrière nginx pour des raisons de perf, donc pas besoin.

    Conséquences pour la sécurité ?

    Si ton exécutable est compromis, le serveur pourra écouter sur ce port. C’est exploitable, par exemple pour faire croire à tes utilisateurs qu’ils tapent toujours sur ton serveur. Mais si tu en arrives là, tu as des problèmes plus grave que ça.

    @Thomas M :

    Du coup n’importe quel utilisateur peut lancer python et s’accaparer le port 80.

    Non, dans l’exemple on le fait sur un python d’un environnement virtuel, qui est dans le dossier d’un utilisateur. Donc les autres utilisateurs n’ont pas accès à cet exécutable.

    Ah oui, et pour un site à fort trafic mieux vaut utiliser tout de même Nginx, il sera plus à même de gérer les connexions réseaux entrantes.

    Tout dépend. Par exemple twisted en front tient parfaitement la charge pour un gros site et ne sera probablement jamais ton bottlenet. Cherrypy, pour un petit site, peut aussi être raisonnablement mis en front si on veut pas se faire chier.

  • Réchèr

    *bottleneck, pas bottlenet.

    À moins que ce soit un mix entre botnet et bottleneck. Une sorte d’IA malveillante qui force l’ensemble de l’humanité à boire à la bouteille.

  • joat

    Dans le même genre il y a authbind, qui est un peu moins prise de tête que setcap.

    Par exemple, pour autoriser l’utilisateur courant à utiliser le port 80 :

    touch /etc/authbind/byport/80

    chmod u+x /etc/authbind/byport/80

    authbind python server.py

  • Gring

    Merci pour cet article !

    Par contre, quel est l’intérêt d’interdire d’écouter les ports en dessous de 1024 ?

    C’est possible, sinon, d’avoir un second processus qui écoute ce qui passe sur un port (pour faire un analyseur de trafic, par exemple) ?

  • Sam Post author

    Les ports sous 1024 sont généralement des ports par défaut pour des protocoles courants : le port 80 pour http, le port 443 pour ssl, le port 22 pour ssh, etc. Ce sont donc des ports “à risque”, et on veut donc être certain que la personne qui les expose sait ce qu’elle fait.

    Et oui, tu peux regarder ce qui se passe sur un port puisque des outils comme wireshark le font.

  • Gring

    Ok, ça fait trop longtemps que je suis seul sur mes serveurs :). J’imagine qu’il y a des serveurs mutualisés avec accès ssh sur lesquels on veut interdire aux utilisateurs de squatter un des ports standard… (C’est juste qu’à priori, ça ne devrait concerner trop peu de systèmes pour être un comportement par défaut, c’est pour ça que ça m’étonne).

    Pour écouter un port comme Wireshark, j’imagine qu’il faut faire des appels système très spécifiques à l’os, on ne doit pas pouvoir utiliser simplement l’objet socket de Python ?

  • Sam Post author

    Non, c’est plus pour éviter qu’un pirate qui a gagné l’accès à ton serveur puisse détourner le trafic trop facilement.

    Pour le port, je sais pas, j’ai jamais eu à le faire.

  • Ludovic Gasc

    @Gring: Si tu veux faire comme Wireshark, tu peux utiliser une lib high level comme Scapy: http://www.secdev.org/projects/scapy/

    Tu peux capturer, mais surtout générer des paquets.

    Ça fonctionne très bien, j’ai ça en prod pour pouvoir injecter des messages SIP non supportés par le central téléphonique du client.

    Scapy utilise en dessous des raw sockets, qui te permettent de créer toi-même ton dialogue TCP ou UDP.

    Par contre, les perfs sont pourries par rapport à une ouverture de socket classique, et tu dois être root pour faire ça.

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.