カーネルパラメータを調整して、ユーザランドプログラムが80番ポートと443番ポートにバインドできるようにすることは可能ですか?
私が質問する理由は、特権プロセスがソケットを開いてリッスンすることを許可するのは愚かなことだと思うからです。ソケットを開いてリッスンするものはすべてリスクが高く、リスクの高いアプリケーションは root で実行すべきではありません
私は、ルート権限で潜り込んだマルウェアを除去しようとするよりも、80番ポートで何の非特権プロセスがリッスンしているのかを把握しようとしています
142 jww 2014-02-02
他の回答やここのコメントが何を参考にしているのかよくわかりません。これはわりと簡単に可能です。2つのオプションがあり、どちらもプロセスをrootに昇格させることなく、低番号のポートへのアクセスを可能にします
オプション1: CAP_NET_BIND_SERVICE
を使用して、プロセスに低番号のポートアクセスを許可する
これにより、setcap
コマンドを使って、特定のバイナリへの永続的なアクセスを許可して、番号の低いポートにバインドすることができます
sudo setcap CAP_NET_BIND_SERVICE=+eip /path/to/binary
e/i/pの部分については、cap_from_text
を参照してください
これを行うと、/path/to/binary
は低番号のポートにバインドできるようになります。シンボリックリンクではなく、バイナリ自体に setcap
を使わなければならないことに注意してください
オプション2: authbind
を使用して、より細かいユーザ/グループ/ポート制御でワンタイムアクセスを許可します
authbind
(man page) ツールは、まさにそのために存在しています
好きなパッケージマネージャを使って
authbind
をインストールしてください関連するポートへのアクセスを許可するように設定します
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
ここで、
authbind
(オプションで--deep
やその他の引数を指定することもできます。マニュアルページを参照してください) を使ってコマンドを実行しますauthbind --deep /path/to/binary command line args
E.g.
authbind --deep java -jar SomeServer.jar
上記の両方の長所と短所があります。オプション1はバイナリに信頼を与えますが、ポート毎のアクセスを制御することはできません。オプション2はユーザー/グループに信頼を付与し、ポートごとのアクセスを制御しますが、古いバージョンではIPv4しかサポートしていませんでした(私が最初にこれを書いて以来、IPv6をサポートした新しいバージョンがリリースされました)
209 Jason C 2015-03-21
Dale Hagglundの言うことは的を得ている。だから私も同じことを言うけど 違う言い方で 具体的に例を挙げて ☺
UnixやLinuxの世界で正しいのは
- スーパーユーザとして動作し、リスニングソケットをバインドする、小さくてシンプルで、簡単に聴き取れるプログラムを持つようにします
- 最初のプログラムで生成された特権を削除する、もう一つの小さくてシンプルで、簡単に監査可能なプログラムを持つようにします
- サービスの本質を、別の第3のプログラムで、スーパーユーザではないアカウントで実行し、第2のプログラムでチェインロードすることで、ソケットのオープンファイル記述子を継承することを期待しています
あなたは、ハイリスクがどこにあるかについて間違った考えを持っています。リスクが高いのは、ネットワークから読み込んで、読み込んだものに基づいて行動することであって、ソケットを開いてポートにバインドしてlisten()
を呼び出すという単純な行為ではありません。リスクが高いのは、実際の通信を行うサービスの部分です。開く部分、bind()
、listen()
、そして(ある程度は)accepts()
を呼び出す部分はリスクが高くなく、スーパーユーザの庇護の下で実行することができます。ネットワーク上で信頼できない他人の管理下にあるデータを(accept()
の場合はソースIPアドレスを除いて)利用して行動することはしない
いろいろな方法があります
inetd
Dale Hagglundが言うように、古い「ネットワークスーパーサーバ」inetd
はこのようなことをしています。サービスプロセスが実行されるアカウントは inetd.conf
のカラムの一つである。しかし、メインのサービスコードを別のプログラムに分離し、exec()
のサービスプロセスでソケット用のオープンファイル記述子を生成している
監査の難しさは、一つのプログラムを監査するだけなので、それほど問題ではありません。inetd
の大きな問題は、監査というよりも、最近のツールと比較して、単純な細かいランタイムサービス制御を提供していないことです
UCSPI-TCPとdaemontools
Daniel J. Bernstein の UCSPI-TCP と daemontools パッケージは、これを併用するように設計されています。代わりに Bruce Guenter のほぼ同等の daemontools-encore ツールセットを使うこともできます
ソケットファイル記述子を開き、特権ローカルポートにバインドするプログラムはUCSPI-TCPのtcpserver
である。これは listen()
と accept()
の両方を行う
tcpserver
は、ルート権限を削除するサービスプログラムを生成します (サービスを提供するプロトコルは、例えば FTP や SSH デーモンのように、スーパーユーザとして起動してから「ログオン」する必要があるため) あるいは、setuidgid
は、権限を削除して、適切なサービスプログラムにロードをチェーンするだけの、自己完結型の小さくて簡単に監査可能なプログラムです (qmail-smtpd
のように、スーパーユーザ権限で実行される部分はありません)
サービスrun
のスクリプトは、このようにして、例えば(dummyidentdのヌルIDENTサービスを提供するためのもの)となります
#!/bin/sh -e
exec 2>&1
exec \
tcpserver 0 113 \
setuidgid nobody \
dummyidentd.pl
nosh
My nosh パッケージはこのようなことをするために設計されています。他のものと同じように、小さな setuidgid
ユーティリティを持っています。1つのわずかな違いは、UCSPI-TCP サービスと同様に systemd
スタイルの “LISTEN_FDS” サービスでも使用できることで、従来の tcpserver
プログラムは 2つの別々のプログラムに置き換えられています。tcp-socket-listen
とtcp-socket-accept
です
ここでも、単一目的のユーティリティは、互いにスポーンして連鎖的にロードされます。この設計の面白いところは、スーパーユーザ権限を listen()
の後でも accept()
の前でも削除できることです。これを正確に実行する run
スクリプトが qmail-smtpd
にあります
#!/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 \
'
スーパーユーザの庇護の下で実行されるプログラムは、サービスに依存しないチェインロードツール fdmove
, clearenv
, envdir
, softlimit
, tcp-socket-listen
, setuidgid
である。sh
が起動された時点で、ソケットはオープンで smtp
ポートにバインドされており、プロセスはスーパーユーザ権限を持たなくなります
s6、s6-networking、execline
Laurent Bercot の s6 と s6-networking パッケージは、これを連動させて行うように設計されています。コマンドは構造的に daemontools
や UCSPI-TCP と非常に似ています
run
スクリプトは、s6-tcpserver
を tcpserver
に、s6-setuidgid
を setuidgid
に置き換える以外は、ほとんど同じです。しかし、M. Bercot の execline ツールセットを同時に利用することもできます
Wayne Marshall のオリジナルを軽く修正した FTP サービスの例です
#!/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氏のipsvdも、ucspi-tcpやs6-networkingと同じ路線で動くツールセットです。今回のツールはchpst
とtcpsvd
ですが、やっていることは同じで、信頼されていないクライアントがネットワーク経由で送ってくるものを読み取ったり、処理したり、書いたりするリスクの高いコードは、やはり別のプログラムの中にあります
ここではM. Pape’s example of running fnord
をrun
のスクリプトにしています
#!/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
は、は、がができることを行うことを意図しています。しかし、小さな自己完結型のプログラム群を使っているわけではありません。残念ながら、systemd
全体を監査しなければなりません
systemd
では、systemd
がリッスンするソケットと、systemd
が起動するサービスを定義するための設定ファイルを作成する。サービスの “unit “ファイルには、どのユーザで実行するかなど、サービスプロセスを大きく制御するための設定が含まれています
そのユーザを非スーパーユーザに設定すると、systemd
は、ソケットをオープンし、ポートにバインドし、プロセス#1のlisten()
(および必要に応じてaccept()
)をスーパーユーザとして呼び出し、生成するサービスプロセスをスーパーユーザ権限なしで実行します
33 JdeBP 2014-02-02
私はどちらかというと違うアプローチをしています。node.jsサーバーに80番ポートを使いたかったのです。Node.jsは非sudoユーザー用にインストールされていたので、できませんでした。シンボリックリンクを使ってみましたが、私の場合はうまくいきませんでした
そして、あるポートから別のポートに接続を転送できることを知りました。そこで、3000番ポートでサーバを起動し、80番ポートから3000番ポートへのポートフォワードを設定してみました
このリンクには、これを行うために使用できる実際のコマンドが記載されています。ここではコマンド –
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
2 番目のコマンドを使ってみましたが、私の場合はうまくいきました。なので、ユーザープロセスに直接下位ポートへのアクセスを許可せず、ポートフォワードを使ってアクセスを与えるというのが中間地点だと思います
26 noob 2018-06-27
最も簡単な解決策: linux上のすべての特権ポートを削除します
ubuntu/debian上で動作します
#save configuration permanently
echo 'net.ipv4.ip_unprivileged_port_start=0' > /etc/sysctl.d/50-unprivileged-ports.conf
#apply conf
sysctl --system
(rootではないアカウントでVirtualBoxを使用している場合は問題なく動作します)
全てのユーザが全てのポートをバインドすることができるので、セキュリティには十分注意してください
7 soleuu 2019-09-13
あなたの直感は完全に正しい: 大規模で複雑なプログラムをルートとして実行させるのは良くない考えです
しかし、通常のユーザが特権ポートにバインドすることを許可するのもよくありません
この明らかな矛盾を解決するための標準的なアプローチは、特権の分離です。基本的な考え方は、プログラムを 2 つの (あるいはそれ以上の) 部分に分割し、それぞれがアプリケーション全体の中で定義された部分を実行し、シンプルで限定されたインタフェースで通信することです
あなたの例では、プログラムを2つの部分に分けたいとします。1つはrootとして実行され、特権ソケットを開いてバインドし、それをどうにかして通常のユーザとして実行されているもう1つの部分に渡します
この2つの主な方法で分離を実現しています
root で起動する単一のプログラム。最初に行うことは、必要なソケットを作成することです。その後、権限を削除し、通常のユーザモードのプロセスに変換し、他のすべての作業を行います。正しい権限の落とし方は厄介なので、時間をかけて正しい方法を勉強してください
親プロセスによって作成されたソケットペアを介して通信するプログラムのペア。非特権ドライバプログラムは初期引数を受け取り、基本的な引数検証を行います。非特権のドライバプログラムは、初期引数を受け取り、基本的な引数の検証を行い、socketpair() で接続されたソケットのペアを作成し、実際の作業を行う他の 2 つのプログラムをフォークして実行します。これらのうちの1つは特権的な操作で、サーバソケットの作成やその他の特権的な操作を行い、もう1つはより複雑で信頼性の低いアプリケーションの実行を行います
[1] http://en.m.wikipedia.org/wiki/Privilege_separation
4 Dale Hagglund 2014-02-02