Ubuntu

Ubuntu 24.04 攻撃的なアクセスへの対応

Ubuntu

Webサーバーやメールサーバーを運営していると、様々なアクセスがある。

一切の広報・広告をしていない個人的なサーバーに対して、結構な数の攻撃的なアクセスがあったりする。
この攻撃的なアクセスに、できるだけサーバーのリソースを割かずに対処するにはどうしたらよいか、Geminiさんと相談してみた。



ここから広告
広告
広告ここまで


環境

Ubuntu 24.04+Apacheでサービスを提供している。
バックエンドにはWordPressやGiteaなどがある。

IPアドレスでのアクセス

とあるIPアドレスがどんなサービスを提供しているのか、というのを調べているセキュリティ企業や有志グループがあるようだ。
だが、そうした調査はごく稀で、多くはダイレクトな攻撃や、攻撃のためのサイト構造の把握だったりする。

事例

例えばこれ、過去にあった脆弱性を探していると思われる。

20.194.96.176 - - [27/Sep/2026:00:51:44 +0900] "GET /wp-content/plugins/hellopress/wp_filemanager.php HTTP/1.1" 410 422 "-" "-"
20.194.96.176 - - [27/Sep/2026:00:51:44 +0900] "GET /this_is_a_new_hello_world.php HTTP/1.1" 410 422 "-" "-"
20.194.96.176 - - [27/Sep/2026:00:51:44 +0900] "GET /f35.update.php HTTP/1.1" 410 422 "-" "-"
20.194.96.176 - - [27/Sep/2026:00:51:44 +0900] "GET //adminfuns.php HTTP/1.1" 410 422 "-" "-"
20.194.96.176 - - [27/Sep/2026:00:51:45 +0900] "GET /3PJcpMFsD8B.php HTTP/1.1" 410 422 "-" "-"
20.194.96.176 - - [27/Sep/2026:00:51:45 +0900] "GET /4PJcpMFsD8B.php HTTP/1.1" 410 422 "-" "-"
20.194.96.176 - - [27/Sep/2026:00:51:45 +0900] "GET //av.php HTTP/1.1" 410 422 "-" "-"
20.194.96.176 - - [27/Sep/2026:00:51:45 +0900] "GET /dex.php HTTP/1.1" 410 422 "-" "-"
20.194.96.176 - - [27/Sep/2026:00:51:45 +0900] "GET /chosen.php HTTP/1.1" 410 422 "-" "-"
20.194.96.176 - - [27/Sep/2026:00:51:45 +0900] "GET /1.php?p= HTTP/1.1" 410 422 "-" "-"

これは、shの起動で制御を奪取しようとする攻撃。

116.98.202.136 - - [27/Sep/2026:01:54:35 +0900] "POST /cgi-bin/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/bin/sh HTTP/1.1" 400 2019 "-" "libredtail-http"
116.98.202.136 - - [27/Sep/2026:01:54:37 +0900] "POST /cgi-bin/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/bin/sh HTTP/1.1" 400 2019 "-" "libredtail-http"
116.98.202.136 - - [27/Sep/2026:01:54:39 +0900] "POST /hello.world?%ADd+allow_url_include%3d1+%ADd+auto_prepend_file%3dphp://input HTTP/1.1" 410 2105 "-" "libredtail-http"

どちらもアクセス元はまっとうな企業のサービスのようだ。
アクセス元が悪いのではなく、利用者が悪い、あるいは、これを乗っ取った者が悪い。

対処

まともな情報を返す必要がないので、こちらの都合でテキトーな応答をしておけば良いと考えている。

ただし、うちはIPアドレスでアクセスしてきたという確証が取れない環境なので、以下に注意が必要だった。

  • 提供していないサービス名でのアクセスをテキトー応答サービスに入れる。
    • 優先度はファイル名で決まる。
  • たとえば、example.netでサービスを提供しているとき、正当なクローラーがwww.example.netでアクセスしてくることがある。
    • 名前解決ができないようにしておけば、このアクセスは発生しない。
    • 名前解決ができてしまう場合は、example.netにリダイレクトしてあげる必要がある。

テキトー応答

優先度はファイル名で決まるので、000とかから始まるファイル名でテキトー応答を定義し、サイトを有効化しておく。

$ sudo apachectl -S
VirtualHost configuration:
*:80 is a NameVirtualHost
default server default (/etc/apache2/sites-enabled/000-defence.conf:1)
port 80 namevhost default (/etc/apache2/sites-enabled/000-defence.conf:1)
port 80 namevhost example.net (/etc/apache2/sites-enabled/wordpress.conf:1)
port 80 namevhost www.example.net (/etc/apache2/sites-enabled/wordpress.conf:90)
*:443 is a NameVirtualHost
default server default (/etc/apache2/sites-enabled/000-defence.conf:12)
port 443 namevhost default (/etc/apache2/sites-enabled/000-defence.conf:12)
port 443 namevhost example.net (/etc/apache2/sites-enabled/wordpress.conf:12)
port 443 namevhost www.example.net (/etc/apache2/sites-enabled/wordpress.conf:97)
...

適切なテキトーを考えると403(Forbidden)が良さそうだが、ここでは410を返している。

/etc/apache2/sites-available/000-defence.conf

<VirtualHost *:80>
ServerName default
Redirect 410 /
ErrorLog ${APACHE_LOG_DIR}/defence-error.log
CustomLog ${APACHE_LOG_DIR}/defence-access.log combined
</VirtualHost>

<VirtualHost *:443>
ServerName default
Redirect 410 /
ErrorLog ${APACHE_LOG_DIR}/defence-error.log
CustomLog ${APACHE_LOG_DIR}/defence-access.log combined
SSLEngine on
SSLCertificateFile /etc/ssl/private/true-snakeoil.crt
SSLCertificateKeyFile /etc/ssl/private/true-snakeoil.key
</VirtualHost>

エラー表示でサイト名が見えちゃったりするのも残念なので、表示オフ。

/etc/apache2/conf-enabled/security.conf

ServerTokens Prod
ServerSignature Off

HTTPSで接続されたとき、証明書から情報が見えるのも残念。

$ openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-keyout ./true-snakeoil.key \
-out ./true-snakeoil.crt \
-subj "/C=XX/ST=Default/L=Default/O=Default/CN=default" \
-addext "basicConstraints = CA:FALSE"

できあがった証明書を適切に設置。

…テキトー応答のために、結構しっかりやることがある。

wwwを付けたアクセス

wwwでサービスを提供していないなら、DNSにあるwwwのレコードを削除する。

都合で消せないとか、アスタリスク指定をしている場合にはリダイレクトする。

/etc/apache2/sites-enabled/wordpress.conf

<VirtualHost *:80>
ServerName www.example.net
Redirect permanent / https://example.net/
ErrorLog ${APACHE_LOG_DIR}/wp-error.log
CustomLog ${APACHE_LOG_DIR}/wp-access.log combined
</VirtualHost>

<VirtualHost *:443>
ServerName www.example.net
Redirect permanent / https://example.net/
ErrorLog ${APACHE_LOG_DIR}/wp-error.log
CustomLog ${APACHE_LOG_DIR}/wp-access.log combined
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.net/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.net/privkey.pem
</VirtualHost>

対処(拡張)

410を返す設定によって、ちょっと応答してログ出力するだけになるし、ログも別ファイルに切り出せた。
これならApacheの負荷も少なく対処として十分だけど、更にFail2banでBANすることもできる。

/etc/fail2ban/filter.d/apache-410.local

[Definition]
failregex = ^<HOST> -.*"(GET|POST|HEAD).*HTTP.*" 410 .+$

datepattern = ^[^\[]*(\[{DATE}\s*\])
{^LN-BEG}

/etc/fail2ban/jail.local

[apache-410]
enabled = true
port = http,https
backend = pyinotify
logpath = /var/log/apache2/defence-access.log
findtime = <お好みで>
maxretry = <お好みで>
bantime = <お好みで>

IPアドレスでアクセスさせる理由がどこにもないなら、アクセス一発でBANしても良いように思う。

ログイン試行

WordPressのアカウントも狙われる。

以前使っていたプラグインでは、1日に数百回の攻撃を受けている、と警告されたりしていた。
このプラグインはIPv6の設定ができなかったので、ApacheとModSecurity、Fail2banで対処することにした。

事例

例えば、今日の11時頃までに当サイトで行われたログイン試行の抜粋。

86.46.35.0 - - [27/Sep/2026:01:23:03 +0900] "GET /wp-login.php HTTP/2.0" 200 5379 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36"
86.46.35.0 - - [27/Sep/2026:01:23:05 +0900] "POST /wp-login.php HTTP/2.0" 403 330 "https://rohhie.net/wp-login.php" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36"
62.60.130.247 - - [27/Sep/2026:01:34:59 +0900] "GET /wp-login.php HTTP/1.1" 200 9724 "" "Mozilla/5.0 (Windows NT 11.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0"
62.60.130.247 - - [27/Sep/2026:01:35:11 +0900] "POST /wp-login.php HTTP/1.1" 403 532 "https://rohhie.net/wp-login.php" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
192.185.2.64 - - [27/Sep/2026:01:40:10 +0900] "GET /wp-login.php HTTP/2.0" 200 5379 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36"
192.185.2.64 - - [27/Sep/2026:01:40:11 +0900] "POST /wp-login.php HTTP/2.0" 403 330 "https://rohhie.net/wp-login.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36"
192.185.2.64 - - [27/Sep/2026:01:40:11 +0900] "GET /wp-login.php?redirect_to=https%3A%2F%2Frohhie.net%2Fwp-admin%2F&reauth=1 HTTP/2.0" 200 6483 "https://rohhie.net/wp-admin/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36"
192.185.179.153 - - [27/Sep/2026:03:58:21 +0900] "GET /wp-login.php HTTP/2.0" 200 5379 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"
192.185.179.153 - - [27/Sep/2026:03:58:21 +0900] "POST /wp-login.php HTTP/2.0" 403 307 "https://rohhie.net/wp-login.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"
192.185.179.153 - - [27/Sep/2026:03:58:22 +0900] "GET /wp-login.php?redirect_to=https%3A%2F%2Frohhie.net%2Fwp-admin%2F&reauth=1 HTTP/2.0" 200 6485 "https://rohhie.net/wp-admin/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"
146.120.203.99 - - [27/Sep/2026:04:15:21 +0900] "GET /wp-login.php HTTP/1.1" 200 9709 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36"
82.225.26.71 - - [27/Sep/2026:05:41:18 +0900] "GET /wp-login.php HTTP/2.0" 200 5379 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36"
82.225.26.71 - - [27/Sep/2026:05:41:20 +0900] "POST /wp-login.php HTTP/2.0" 403 330 "https://rohhie.net/wp-login.php" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36"
161.118.224.149 - - [27/Sep/2026:06:40:53 +0900] "GET /wp-login.php HTTP/1.1" 200 9726 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0.1"
161.118.224.149 - - [27/Sep/2026:06:40:53 +0900] "POST /wp-login.php HTTP/1.1" 403 532 "https://rohhie.net/wp-login.php" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15"
161.118.224.149 - - [27/Sep/2026:06:40:53 +0900] "GET /wp-admin/index.php HTTP/1.1" 302 637 "https://rohhie.net/wp-login.php" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15"
83.5.170.127 - - [27/Sep/2026:11:04:29 +0900] "GET /wp-login.php HTTP/2.0" 200 5378 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"
83.5.170.127 - - [27/Sep/2026:11:04:30 +0900] "POST /wp-login.php HTTP/2.0" 403 330 "https://rohhie.net/wp-login.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"

Webの表記を人間が読める形式に置き換えてくれるコマンドをGeminiさんが教えてくれた。

$ python3 -c "import sys, urllib.parse; print(urllib.parse.unquote(sys.stdin.read()))"
<ペースト>[Enter]
[Ctrl]+[d]

このコマンドを利用しながら、どんなアクセスだったのかを確認してみると…

  • 1行目は、ログインページへのアクセス。
  • 2行目は、そこにuser:rohhie、password:rohhieと入力してエラーになっている。
  • 4行目は、user:site_admin、password:<何かどこかから流出した?パスワード>と入力してエラー。
  • 6行目ではログイン失敗しているが、7行目で管理ページにアクセスし、この後でログイン画面に戻されている。

※user, passwordはmodsec_audit.logで確認。

といった具合。過去には、辞書アタックが一通り行われていたことも観測している。

これも、アクセス元が悪いのではなく、利用者が悪い、あるいは、これを乗っ取った者が悪い。

対処

こうしたアクセスは、もしかしたら、新しく発見されて対処されていない脆弱性を突いてくるかもしれない。
プラグインの使い勝手も悪かったので、ModSecurityとFail2banで対処することにした。

ModSecurityのルールは、ログイン失敗で200(ログイン画面に戻す)を返そうとしたら、403に変更する、というもの。

REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf

SecRule REQUEST_METHOD "@streq POST" \
"id:1000204,\
phase:3,\
deny,\
status:403,\
log,\
auditlog,\
msg:'Wordpress Login Failed',\
severity:'CRITICAL',\
chain"
SecRule REQUEST_URI "@endswith /wp-login.php" \
"chain"
SecRule RESPONSE_STATUS "@eq 200"

後は、Fail2banを使ってお好みで403をBanするだけ。

リスクの高い国からのアクセス

リスクの高い国とは何か、というと3種類あるそう。

  • 国家コミコミで攻撃をしてくる国
  • クラウドやVPSが悪用されやすい国
  • マルウェア感染・ボットネットの発生率が高い国

どこ向けのサービスなのか、ということによって対処の仕方は違ってくるだろうけれど、

  • 国で遮断
  • 攻撃を受けたら遮断
  • 諦めて真正面から攻撃を受け止める

のいずれかの対応になるように思われる。

対処

国で遮断というのはGeminiさんに言わせれば簡単、RIR(Regional Internet Registry)から国別割り当て表の最新版をダウンロードしてきて、IPアドレスのリストを作り、nftablesのルールに仕立ててドロップするだけと。

管理団体設立割り当てリストのダウンロード先
RIPE(Réseaux IP Européens)1992https://ftp.ripe.net/ripe/stats/
APNIC(Asia Pacific Network Information Centre)1993https://ftp.apnic.net/stats/apnic
ARIN(American Registry for Internet Numbers)1997https://ftp.arin.net/pub/stats/arin/
LACNIC(Latin American and Caribbean Internet Addresses Registry)2002https://ftp.lacnic.net/pub/stats/lacnic/
AFRINIC(The African Network Information Centre)2005https://ftp.afrinic.net/pub/stats/afrinic/
元々はアメリカのInterNICが一括管理していたが、分散管理でRIRが設立されていった。

ブラックリスト管理で、決まった国を除外するなら、こんなルールを作成。

table inet blackhole {
set drop_ipv4 {
type ipv4_addr
flags interval
elements = {
# ... IPリスト ...
}
}
set drop_ipv6 {
type ipv6_addr
flags interval
elements = {
# ... IPリスト ...
}
}
chain prerouting {
type filter hook prerouting priority -10; policy accept;
ip saddr @drop_ipv4 drop
ip6 saddr @drop_ipv6 drop
}
}

アクセス元を国内に絞りたい、と思うこともあるかもしれない。
ホワイトリスト管理で、決まった国からのアクセスだけを許容するなら、こんなルールを。

table inet blackhole {
set accept_ipv4 {
type ipv4_addr
flags interval
elements = {
# ... IPリスト ...
}
}
set accept_ipv6 {
type ipv6_addr
flags interval
elements = {
# ... IPリスト ...
}
}
chain prerouting {
type filter hook prerouting priority +10; policy accept;

# 1. ループバックおよび確立済み通信を許可(必須)
iifname "lo" accept
ct state established,related accept

# 2. IPリストに含まれない新規通信をすべて破棄
meta nfproto ipv4 ip saddr != @accept_ipv4 drop
meta nfproto ipv6 ip6 saddr != @accept_ipv6 drop

# 3. ここに到達したものは「許可された国からの新規通信」のみ
tcp dport { 80, 443 } accept
}
}

ただし、WebサーバーとSMTPサーバーを運営している、といった場合は注意が必要。
メールだけはどこから来ても受け取ることになるだろうから、

  • IPv6のICMPを許可する(許可しないと通信ができなくなる)
  • 国判定の前に宛先ポートでacceptして、後続チェーンの判定に任せる
  • その後に国判定してdropする

といった設定にする必要がある。

なお、IPリストはかなり大きなものになるが、setを利用することでハッシュテーブルが使われるようになるので、物凄く速いとのこと。

さいごに

Geminiさんに色々と相談するようになって、こうした設定を実装するまでの速度があがった。
とても助かっているが、昨日Geminiさんを開いたところ、Flashの利用は10/9から有料サービスになるという。

いままでは広告で少しGoogleさんのお手伝いをしたくらいで、ほぼ無料でサービスを利用させてもらってきた。
サービスが止まったときは、似たような機能を持つOSSを利用させてもらい、自宅サーバーでサービスを立ち上げる等してきた。

だが、今度ばかりはそうも行かない。
ちょっと前までのFlash-Liteは、込み入った相談をするにはあまり向いていなかったが、今のバージョンはどんなもんなんでしょう。
適切なプランを選択して利用を継続するか、Flash-Liteでどうにかするか。

どういう選択をするにせよ、いまや生成AIなしにサーバー運営なんて考えにくい。
脳みその使い方が全然違うので、ここ数ヶ月で全く感覚が変わっており、簡単には元に戻れそうもない。

さて。
ここ数ヶ月、色々とネットワーク設定をいじってきたけれど、これがサービスの質に影響するかといえば、ノー。
何一つサービスの質は変わらない。むしろ、アクセスをブロックされて困ったりもした(苦笑)。

だけど、防御力はだいぶ上がったかなー。
守ろうとしているものは、所詮は個人のもっているつまらない情報でしかないが、昨今のインシデントをみていると、やっといて良かった。

これでちょうど一段落したし、しばらくはゲームで遊んだりなんかして、休もうかな。
やる気が出てきた頃に、もう一度今後を考え直すのも悪くない、よね?

ここから広告
広告
広告ここまで

コメントはこちらから お気軽にどうぞ ~ 投稿に関するご意見・感想・他