SVX日記

2004|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|10|11|12|
2023|01|02|03|04|05|06|07|08|09|10|11|12|
2024|01|02|03|04|05|06|07|08|09|10|11|12|
2025|01|02|03|04|05|06|07|08|09|10|11|12|

2025-11-25(Tue) 叩けよhttpさらばssh開かれん

  ssh接続に使うポート番号は22が標準であるが「ブルートフォース攻撃の対象になりやすいため、この番号を変更することが効果的だ」とする記述をあちこちで見かける。

  しかし、そんなことをしなくても、公開鍵認証のみ許可としておけば、それを突破することは不可能だ。数学的に不可能と言える。なお、攻撃者は一般ユーザアカウント名を推測することすら難しいので、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

  穴が開くのは3分間だけだが、その間に接続に成功すれば、その接続は3分経過後も維持される。なんだか二段階認証っぽいな。

  問題は実装。そのためだけに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/, $\))

  呼び出されるCGIの中身は上記。そのまんまだ。

[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

  サービス化のためのsystemdスクリプトが上記。こっちもそのまんまやね。

  使うのは年に1回あるかないかだろうが、スマホに上記のURIをブックマークしておけば、ssh接続の直前にそれを開くだけで、以降はフツーにsshサービスが利用できる。なかなかいいアイデアだと思うが、どんなもんかね?

  関連ファイル一式をhttp://itline.jp/git/sshopenに置いておく。