SVX日記
2025-11-25(Tue) 叩けよhttpさらばssh開かれん
しかし、そんなことをしなくても、公開鍵認証のみ許可としておけば、それを突破することは不可能だ。数学的に不可能と言える。なお、攻撃者は一般ユーザアカウント名を推測することすら難しいので、rootのみパスワード認証を禁止(公開鍵認証のみ許可)としておけば十分。以前はwithout-passwordという設定だったが、最近は「PermitRootLogin prohibit-password」と書いておけばいい。そもそも、大概はデフォルトでそうなっていることだろう。root作業をしたければ、一般ユーザアカウントでパスワードログインしてからsuしてもいい。そうしておけば、1日に数千、数万のアタックがあろうが対策としては鉄壁なのである。
むしろ、ポート番号の変更には意味がない、と断言しよう。というのも、実際に一度、変更してみたのだが、あっという間に悟られて数日後にはアタックの数は22の時と変わらなくなってしまったからである。rootでのパスワードログインを許容している限り、十分すぎるほど長く複雑なパスワードは必須だ。
と、それだけならば、それで話は終わりなのだが、突破することが不可能だとしても、ログがウルサくなるという問題は残るのであった。一応、管理者の務めとして、毎日Logwatchからのメールに目を通しているのだが、非常にウルサい。見たくない。そこで思いついた。必要な時だけ、ポートが開けばよいのではないか?
問題は手段だ。22番ポートが閉じているのだから、ssh経由でポートを開くことはできない。そこでhttpを使う。認証付きのページへのアクセスに成功したら、一定時間だけsshポートが開くようにするのである。実際には、sshポートが開くのではなく、firewallに穴が開く。こんなコマンドで3分間のみ穴を開けることができるのだ。
firewall-cmd --add-service=ssh --timeout=3m
問題は実装。そのためだけにapacheを立ち上げるのはバカらしい。仕掛けたいサーバは、実質コンテナ母艦になっているので、80番と443番ではhaproxyが待っていて、表にはapacheが立ち上がっていないのだ。そのためだけにコンテナを立ち上げるのもバカらしいが、そうしたところでコンテナ内から母艦を操作することは困難だ。と、そこで先日MAGIシステムっぽいものを作った時に使ったWEBrickを思い出した。それで組めないものか。
require 'webrick'
server = WEBrick::HTTPServer.new({
:DocumentRoot => './',
:BindAddress => '0.0.0.0',
:Port => 8080,
:DocumentRootOptions => { :FancyIndexing => false },
})
class CGIHandlerWithAuth < WEBrick::HTTPServlet::CGIHandler
def initialize(server, script, auth)
@auth = auth
super(server, script)
end
def service(req, res)
@auth.authenticate(req, res)
super
end
end
# htpasswd -d -c .htpasswd username
#htpasswd = WEBrick::HTTPAuth::Htpasswd.new('.htpasswd')
#basic_auth = WEBrick::HTTPAuth::BasicAuth.new(Realm: 'SSH Open', UserDB: htpasswd)
#server.mount('/open12345.cgi', CGIHandlerWithAuth, 'open.rb', basic_auth)
# htdigest -c .htdigest 'SSH Open' username
htdigest = WEBrick::HTTPAuth::Htdigest.new('.htdigest')
digest_auth = WEBrick::HTTPAuth::DigestAuth.new(Realm: 'SSH Open', UserDB: htdigest)
server.mount('/open12345.cgi', CGIHandlerWithAuth, 'open.rb', digest_auth)
trap('INT') { server.shutdown }
server.start
できた。んが、WEBrickでCGIに認証を付けるためには一筋縄では済まず、ついAIの助けを借りてしまった。一応、味見のためのBASIC認証も書いたが、SSLを使わない場合はDIGEST認証を使わないと危険だろう。また、ポート番号やCGIのURIも変更しておくとよい。
result = `firewall-cmd --add-service=ssh --timeout=3m`
$\ = "\r\n"
print('Content-Type: text/plain')
print()
print(("--\n%s--" % result).gsub(/\r?\n/, $\))
[Unit]
Description=SSH Port Opener
[Service]
Type=simple
WorkingDirectory=/usr/local/bin/sshopen
ExecStart=/usr/local/bin/sshopen/sshopen
User=root
Group=root
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
