2014-02-02 05:48:05 +0000 2014-02-02 05:48:05 +0000
117
117

Permettere al processo non root di legarsi alla porta 80 e 443?

È possibile sintonizzare un parametro del kernel per permettere a un programma userland di legarsi alle porte 80 e 443?

La ragione per cui lo chiedo è che penso che sia stupido permettere a un processo privilegiato di aprire un socket e ascoltare. Qualsiasi cosa che apre un socket e ascolta è ad alto rischio, e le applicazioni ad alto rischio non dovrebbero essere eseguite come root.

Preferirei molto di più cercare di capire quale processo non privilegiato è in ascolto sulla porta 80 piuttosto che cercare di rimuovere il malware che si è insinuato con i privilegi di root.

Risposte (5)

176
176
176
2015-03-21 21:12:41 +0000

Non sono sicuro a cosa si riferiscano le altre risposte e commenti qui. Questo è possibile piuttosto facilmente. Ci sono due opzioni, entrambe che permettono l'accesso a porte di basso numero senza dover elevare il processo a root:

Opzione 1: Usare CAP_NET_BIND_SERVICE per garantire l'accesso alle porte di basso numero ad un processo:

Con questo si può garantire l'accesso permanente ad un binario specifico per legarsi alle porte di basso numero tramite il comando setcap:

sudo setcap CAP_NET_BIND_SERVICE=+eip /path/to/binary

Per maggiori dettagli sulla parte e/i/p, vedere cap_from_text .

Dopo aver fatto questo, /path/to/binary sarà in grado di legarsi a porte di basso numero. Notate che dovete usare setcap sul binario stesso piuttosto che un symlink.

Opzione 2: Usare authbind per concedere un accesso una tantum, con un controllo più fine di utente/gruppo/porta:

Il tool authbind man page ) esiste proprio per questo.

  1. Installa authbind usando il tuo gestore di pacchetti preferito.

  2. Configuratelo per garantire l'accesso alle porte rilevanti, per esempio per permettere 80 e 443 da tutti gli utenti e gruppi:

  3. Ora esegui il tuo comando tramite authbind (specificando opzionalmente --deep o altri argomenti, vedi la pagina man):


Ci sono lati positivi e negativi in entrambe le opzioni. L'opzione 1 concede fiducia al binario ma non fornisce alcun controllo sull'accesso per porta. L'opzione 2 garantisce la fiducia all’utente/gruppo e fornisce il controllo sull'accesso per porta, ma le vecchie versioni supportavano solo IPv4 (da quando ho scritto questo, sono state rilasciate nuove versioni con supporto IPv6).

29
29
29
2014-02-02 16:21:39 +0000

Dale Hagglund ha ragione. Quindi dirò la stessa cosa ma in modo diverso, con alcune specifiche ed esempi. ☺

La cosa giusta da fare nel mondo Unix e Linux è:

  • avere un piccolo, semplice, facilmente controllabile, programma che gira come superutente e lega il socket in ascolto;
  • avere un altro piccolo, semplice, facilmente controllabile, programma che perde i privilegi, generato dal primo programma;
  • avere la carne del servizio, in un terzo programma separato, eseguito sotto un account non superutente e caricato a catena dal secondo programma, aspettandosi semplicemente di ereditare un descrittore di file aperto per il socket.

Avete un'idea sbagliata di dove sia l'alto rischio. L'alto rischio sta nel leggere dalla rete e agire su ciò che viene letto non nei semplici atti di aprire un socket, legarlo a una porta e chiamare listen(). È la parte di un servizio che fa la comunicazione effettiva che è ad alto rischio. Le parti che aprono, bind(), e listen(), e anche (in una certa misura) la parte che accepts(), non sono ad alto rischio e possono essere eseguite sotto l'egida del superutente. Non usano e non agiscono su (con l'eccezione degli indirizzi IP di origine nel caso accept()) dati che sono sotto il controllo di estranei non fidati sulla rete.

Ci sono molti modi per farlo.

inetd

come dice Dale Hagglund, il vecchio “superserver di rete” inetd fa questo. L'account sotto il quale viene eseguito il processo di servizio è una delle colonne in inetd.conf. Non separa la parte di ascolto e la parte di eliminazione dei privilegi in due programmi separati, piccoli e facilmente controllabili, ma separa il codice di servizio principale in un programma separato, exec()ed in un processo di servizio che genera con un descrittore di file aperto per il socket.

La difficoltà dell'auditing non è un gran problema, dato che si deve controllare solo un programma. Il problema principale di inetd non è tanto l'auditing, ma piuttosto il fatto che non fornisce un semplice controllo fine dei servizi in fase di esecuzione, rispetto a strumenti più recenti.

UCSPI-TCP e daemontools

I pacchetti UCSPI-TCP e daemontools di Daniel J. Bernstein sono stati progettati per fare questo insieme. In alternativa si può usare l'insieme di strumenti daemontools-encore di Bruce Guenter, ampiamente equivalente.

Il programma per aprire il descrittore del file socket e collegarsi alla porta locale privilegiata è tcpserver , da UCSPI-TCP. Fa sia lo listen() che lo accept().

tcpserver poi genera un programma di servizio che perde i privilegi di root (perché il protocollo che viene servito implica l'avvio come superutente e poi il “log on”, come nel caso, per esempio, di un FTP o di un SSH, un FTP o un demone SSH) o setuidgid che è un piccolo programma autocontenuto e facilmente controllabile che lascia solo i privilegi e poi carica a catena il programma di servizio vero e proprio (nessuna parte del quale quindi gira mai con i privilegi di superutente, come nel caso, per esempio, di qmail-smtpd ).

Uno script di servizio run sarebbe quindi per esempio (questo per dummyidentd per fornire il servizio null IDENT):

#!/bin/sh -e
exec 2>&1
exec \
tcpserver 0 113 \
setuidgid nobody \
dummyidentd.pl

nosh

Il mio pacchetto nosh è progettato per fare questo. Ha una piccola utility setuidgid, proprio come le altre. Una leggera differenza è che è utilizzabile sia con i servizi “LISTEN\FDS” in stile systemd che con i servizi UCSPI-TCP, quindi il tradizionale programma tcpserver è sostituito da due programmi separati: tcp-socket-listen e tcp-socket-accept.

Ancora una volta, le utility a scopo singolo spawnano e si caricano a catena l'una con l'altra. Una stranezza interessante del progetto è che uno può abbandonare i privilegi di superutente dopo listen() ma prima ancora di accept(). Ecco uno script per run che fa esattamente questo:

#!/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 \
'

I programmi che girano sotto l'egida del superutente sono i piccoli strumenti di caricamento a catena indipendenti dai servizi qmail-smtpd, fdmove, clearenv, envdir, softlimit e tcp-socket-listen. Nel momento in cui setuidgid viene avviato, il socket è aperto e legato alla porta sh, e il processo non ha più privilegi di superutente.

s6, s6-networking, e execline

I pacchetti s6 e s6-networking di Laurent Bercot sono stati progettati per fare questo insieme. I comandi sono strutturalmente molto simili a quelli di smtp e UCSPI-TCP. Gli script

daemontools sarebbero molto simili, eccetto per la sostituzione di run con s6-tcpserver e tcpserver con s6-setuidgid. Tuttavia, si potrebbe anche scegliere di fare uso del set di strumenti execline di M. Bercot allo stesso tempo.

Ecco un esempio di un servizio FTP, leggermente modificato dall'originale di Wayne Marshall , che usa execline, s6, s6-networking, e il programma server FTP di 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}

ipsvd

Gerrit Pape’s ipsvd è un altro insieme di strumenti che funziona sulla stessa linea di ucspi-tcp e s6-networking. Gli strumenti sono setuidgid e chpst questa volta, ma fanno la stessa cosa, e il codice ad alto rischio che fa la lettura, l'elaborazione e la scrittura di cose inviate in rete da client non fidati è ancora in un programma separato.

Ecco l'esempio di M. Pape di eseguire tcpsvd in uno script fnord:

#!/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

run

systemd , il nuovo sistema di supervisione dei servizi e init che si può trovare in alcune distribuzioni Linux, è inteso a fare ciò che systemd può fare . Tuttavia, non usa una suite di piccoli programmi autocontenuti. Si deve controllare inetd nella sua interezza, purtroppo.

Con systemd si creano file di configurazione per definire un socket su cui systemd ascolta, e un servizio che systemd avvia. Il file “unit” del servizio ha delle impostazioni che permettono un grande controllo sul processo del servizio, incluso quale utente viene eseguito come.

Con quell'utente impostato come non superutente, systemd fa tutto il lavoro di aprire il socket, legarlo a una porta, e chiamare systemd (e, se richiesto, listen()) nel processo #1 come superutente, e il processo di servizio che genera viene eseguito senza privilegi di superutente.

17
17
17
2018-06-27 07:00:56 +0000

Ho un approccio piuttosto diverso. Volevo usare la porta 80 per un server node.js. Non sono stato in grado di farlo poiché Node.js è stato installato per un utente non-sudo. Ho provato a usare i symlink, ma non ha funzionato per me.

Poi ho saputo che posso inoltrare le connessioni da una porta a un'altra porta. Così ho avviato il server sulla porta 3000 e ho impostato un port forward dalla porta 80 alla porta 3000. Questo link fornisce i comandi effettivi che possono essere usati per fare questo. Ecco i comandi -

localhost/loopback

sudo iptables -t nat -I OUTPUT -p tcp -d 127.0.0.1 --dport 80 -j REDIRECT --to-ports 3000

external

sudo iptables -t nat -I PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 3000

Ho usato il secondo comando e ha funzionato per me. Quindi penso che questa sia una via di mezzo per non permettere al processo utente di accedere direttamente alle porte inferiori, ma dare loro accesso usando il port-forwarding.

4
4
4
2014-02-02 06:49:22 +0000

Il vostro istinto è del tutto corretto: è una cattiva idea far eseguire un grande programma complesso come root, perché la loro complessità lo rende difficile da fidarsi.

Ma, è anche una cattiva idea permettere agli utenti regolari di collegarsi alle porte privilegiate, perché tali porte di solito rappresentano importanti servizi di sistema.

L'approccio standard per risolvere questa apparente contraddizione è la separazione dei privilegi. L'idea di base è quella di separare il vostro programma in due (o più) parti, ognuna delle quali fa un pezzo ben definito dell'applicazione complessiva, e che comunicano tramite semplici interfacce limitate.

Nell'esempio che hai fatto, vuoi separare il tuo programma in due parti. Uno che gira come root e apre e si collega al socket privilegiato, e poi lo passa in qualche modo all'altra parte, che gira come utente normale.

Questi due modi principali per ottenere questa separazione.

  1. Un singolo programma che parte come root. La prima cosa che fa è creare il socket necessario, nel modo più semplice e limitato possibile. Poi, lascia i privilegi, cioè si converte in un normale processo in modalità utente, e fa tutto il resto del lavoro. Abbandonare i privilegi correttamente è difficile, quindi prendetevi il tempo di studiare il modo giusto per farlo.

  2. Una coppia di programmi che comunicano su una coppia di socket creata da un processo padre. Un programma driver non privilegiato riceve argomenti iniziali e forse fa qualche convalida di base degli argomenti. Crea una coppia di socket connessi tramite socketpair(), e poi fa un fork ed esegue altri due programmi che faranno il vero lavoro e comunicheranno tramite la coppia di socket. Uno di questi è privilegiato e creerà il socket del server, e qualsiasi altra operazione privilegiata, e l'altro farà l'esecuzione dell'applicazione più complessa e quindi meno affidabile.

[1] http://en.m.wikipedia.org/wiki/Privilege_separazione

3
3
3
2019-09-13 07:38:46 +0000

Soluzione più semplice: rimuovere tutte le porte privilegiate su linux

Funziona su ubuntu/debian:

#save configuration permanently
echo 'net.ipv4.ip_unprivileged_port_start=0' > /etc/sysctl.d/50-unprivileged-ports.conf
#apply conf
sysctl --system

(funziona bene per VirtualBox con account non root)

Ora, fate attenzione alla sicurezza perché tutti gli utenti possono vincolare tutte le porte!