WordPressのブルートフォースアタック

らら
らら

はじめに

今回は、緊急で、ブログ書いてます・・って

別件で、お客さんからWordPress攻撃されたから、調べてほしいとのことで・・・

うちで設置・設定してないので・・・あれなんですが・・

お願いされたので・・・調べてみた。。

最近のプラグインとか・・知らないしねぇ

ログ解析・・してみた。

どうも、ブルートフォースアタックぽい、パスワードは12桁で記号、英数字大文字小文字の組み合わせだったぽい・・

うーむ。何十年もかかるんじゃぁ・・と・・

最近は、ビットコインとかの生成ハード(グラボ複数枚構成のやつ)使って、やっているよとか・・巷で聞くけど・・

ログでは、?author=1 実行されていた・・・まだこのコマンドあるの?!!うちでは、最近WordPress設置しないので、疎いですが・・・

実行してみると・・動いちゃうのね・・・・うううぅ・・・

こうなると・・ログインIDはわかっちゃうので・・・パスワードだけの解析になっちゃうのね。

xmlrpc.phpもアクセスがあり・・これ、禁止にしてないのね・・・最近は、不具合なくなったからとはいえ、これでもユーザーIDわかるってしらないのかね・・

その後、wp-login.phpとかあって・・ログインされてる感じ、でプラグインのインストールで、wp-file-manager入れられて・・・

定番のPHPファイラーがいくつか・・・入れられてる感じで・・・

shell.phpとか、ss.phpとかファイルが上がってて・・・ありゃぁ・・って感じ・・

12桁でもパスされちゃうのね。。最近・・・・

どうなんだろか・・・作業者のPCからパスもれたのか・・どうなんだろか。。。あきらかに・・海外のIPからログインされてるしね、

ログだけでは、形跡は、あるけど・・ブルートフォースアタックで得たのか、単なるパスワード漏洩なのかまでは・・・

各コマンドの意味を晒してみる・・

日本じゃ、あんまり、こういう脆弱性ぽいの書くと、良くないよねぇみたいな風潮もあるが・・

海外じゃ。。逆・・・

ブルートフォースアタックってwordpressのログイン画面に対して、行われると思っている人たちもいるかと・・

WordPressの場合、xmlrpc.php REST APIだったりと間口がおおい、

この画面からだけじゃない・・・

WordPressのブルートフォースアタック

対策方法

?author=1でユーザー名の特定する

下記でアクセスすると・・


https://サイトドメイン/?author=1

下記へリダイレクトされます。

これでログインIDが取得できてしまいます。


https://サイトドメイン/author/ワードプレスのログインID

対応方法

functions.phpに下記を追加

最近のは、よくわからないので、動作するかは確認してください。


function disable_author_archive_url() {
	if (is_author()) {
		wp_safe_redirect(home_url(), 301);
//		wp_redirect(home_url());
		exit;
	}
}
add_action('template_redirect','disable_author_archive_url');

Edit Author Slugプラグインもあるけど、プラグインの脆弱性も怖いので・・

xmlrpc.phpでユーザー名の特定する

下記にxmlをPOSTする


https://サイトドメイン/xmlrpc.php
POSTするxml

<?xml version="1.0" encording="iso-8859-1"?>
  <methodCall>
    <methodName>wp.getUsersBlogs</methodName>
    <params>
      <param>
        <value>wordpressのユーザー</value>
      </param>
      <param>
        <value>wordpressのパスワード</value>
      </param>
    </params>
  </methodCall>

Linuxコンソールで・・


curl -d '<?xml version="1.0" encording="iso-8859-1"?><methodCall><methodName>wp.getUsersBlogs</methodName><params><param><value>wordpressのユーザー</value></param><param><value>wordpressのパスワード</value></param></params></methodCall>' https://サイトドメイン/xmlrpc.php

ID,パスが違うと・・こんな親切なメッセージを送ってくれる、Wordpress


<?xml version="1.0"?>
<methodResponse>
  <fault>
    <value>
      <struct>
        <member>
          <name>faultCode</name>
          <value><int>403</int></value>
        </member>
        <member>
          <name>faultString</name>
          <value><string>誤ったログイン/パスワードの組み合わせ。</string></value>
        </member>
      </struct>
    </value>
  </fault>
</methodResponse>

ID,パスが一致しちゃうと・・・こんな感じが返ってくる・・


<?xml version="1.0"?>
<methodResponse>
  <params>
    <param>
      <value>
      <array><data>
  <value><struct>
  <member><name>isAdmin</name><value><boolean>1</boolean></value></member>
  <member><name>url</name><value><string>https://サイトドメイン/</string></value></member>
  <member><name>blogid</name><value><string>1</string></value></member>
  <member><name>blogName</name><value><string>サイト名</string></value></member>
  <member><name>xmlrpc</name><value><string>https://サイトドメイン/xmlrpc.php</string></value></member>
</struct></value>
</data></array>
      </value>
    </param>
  </params>
</methodResponse>

対応方法

.htaccessで下記を設定するか。ブログを使っていないのであれば、xmlrpc.php消してもよいが、バージョンアップで入ってきちゃうと思うので・・


<Files "xmlrpc.php">
order deny,allow
deny from all
</Files>

あとは、これ


add_filter( 'xmlrpc_enabled', '__return_false' );

下記で


remove_action( 'wp_head', 'rsd_link' );

上記でlink rel="EditURI"を削除します。


<link rel="EditURI" type="application/rsd+xml" title="RSD" href="https://ドメイン/xmlrpc.php?rsd" />

REST API でユーザー名の特定する方法

これは、トークン取得してやらないとだめなんじゃ、コマンド叩くだけで、ログインIDはやばい・・内部的なユーザーID(シーケンシャル)ならまだ・・

下記でアクセスすると・・。


https://サイトドメイン/wp-json/wp/v2/users
https://サイトドメイン/?rest_route=/wp/v2/users

下記のjsonがもれなく帰ってきます


[{"id":1,"name":"ワードプレスのログインID","url":"","description":"","link":"https:.....

対応方法

functions.phpに下記を追加


function disable_rest_endpoints( $endpoints ) {
	if(isset($endpoints['/wp/v2/users'])) {
		unset($endpoints['/wp/v2/users']);
	}
	if(isset($endpoints['/wp/v2/users/(?P[\d]+)'])) {
		unset($endpoints['/wp/v2/users/(?P[\d]+)']);
	}
	return $endpoints;
}
add_filter('rest_endpoints', 'disable_rest_endpoints',10,1);

wp-sitemap-users-1.xmlでユーザー名の特定する

これは、初めて、知りました・・Ver5.5かららしい・・


https://サイトドメイン/wp-sitemap-users-1.xml

対応方法

functions.phpに下記を追加


add_filter('wp_sitemaps_enabled', '__return_false');

その他WordPressと推定されないために

絵文字JavaScriptとStyleSheetの削除

消すの下記


		<script type="text/javascript">
			window._wpemojiSettings = {"baseUrl":"https:\/\/s.w.org\/images\/core\/emoji\/11\/72x72\/","ext":".png","svgUrl":"https:\/\/s.w.org\/images\/core\/emoji\/11\/svg\/","svgExt":".svg","source":{"concatemoji":"https:\/\/www.maruko.com\/wordpress\/wp-includes\/js\/wp-emoji-release.min.js?ver=4.9.26"}};
			!function(e,a,t){var n,r,o,i=a.createElement("canvas"),p=i.getContext&&i.getContext("2d");function s(e,t){var a=String.fromCharCode;p.clearRect(0,0,i.width,i.height),p.fillText(a.apply(this,e),0,0);e=i.toDataURL();return p.clearRect(0,0,i.width,i.height),p.fillText(a.apply(this,t),0,0),e===i.toDataURL()}function c(e){var t=a.createElement("script");t.src=e,t.defer=t.type="text/javascript",a.getElementsByTagName("head")[0].appendChild(t)}for(o=Array("flag","emoji"),t.supports={everything:!0,everythingExceptFlag:!0},r=0;r<o.length;r++)t.supports[o[r]]=function(e){if(!p||!p.fillText)return!1;switch(p.textBaseline="top",p.font="600 32px Arial",e){case"flag":return s([55356,56826,55356,56819],[55356,56826,8203,55356,56819])?!1:!s([55356,57332,56128,56423,56128,56418,56128,56421,56128,56430,56128,56423,56128,56447],[55356,57332,8203,56128,56423,8203,56128,56418,8203,56128,56421,8203,56128,56430,8203,56128,56423,8203,56128,56447]);case"emoji":return!s([55358,56760,9792,65039],[55358,56760,8203,9792,65039])}return!1}(o[r]),t.supports.everything=t.supports.everything&&t.supports[o[r]],"flag"!==o[r]&&(t.supports.everythingExceptFlag=t.supports.everythingExceptFlag&&t.supports[o[r]]);t.supports.everythingExceptFlag=t.supports.everythingExceptFlag&&!t.supports.flag,t.DOMReady=!1,t.readyCallback=function(){t.DOMReady=!0},t.supports.everything||(n=function(){t.readyCallback()},a.addEventListener?(a.addEventListener("DOMContentLoaded",n,!1),e.addEventListener("load",n,!1)):(e.attachEvent("onload",n),a.attachEvent("onreadystatechange",function(){"complete"===a.readyState&&t.readyCallback()})),(n=t.source||{}).concatemoji?c(n.concatemoji):n.wpemoji&&n.twemoji&&(c(n.twemoji),c(n.wpemoji)))}(window,document,window._wpemojiSettings);
		</script>
		<style type="text/css">
img.wp-smiley,
img.emoji {
	display: inline !important;
	border: none !important;
	box-shadow: none !important;
	height: 1em !important;
	width: 1em !important;
	margin: 0 .07em !important;
	vertical-align: -0.1em !important;
	background: none !important;
	padding: 0 !important;
}
</style>

対応方法

functions.phpに下記を追加


remove_action('wp_head','print_emoji_detection_script', 7);
remove_action('admin_print_scripts','print_emoji_detection_script');
remove_action('wp_print_styles','print_emoji_styles');
remove_action('admin_print_styles','print_emoji_styles');

meta generatorの削除


<meta name="generator" content="WordPress 6.8" />

対策


remove_action('wp_head', 'wp_generator');

WordPressとプラグイン?ver=の削除


function remove_wp_pg_ver_css_js($src) {
	if(strpos($src, 'ver=')) {
		$src = remove_query_arg('ver',$src);
	}
	return $src;
}
add_filter('style_loader_src', 'remove_wp_pg_ver_css_js',9999);
add_filter('script_loader_src', 'remove_wp_pg_ver_css_js',9999);

link wlwmanifest削除

下記


<link rel="wlwmanifest" type="application/wlwmanifest+xml" href="http://ドメイン/wp-includes/wlwmanifest.xml" />

対策


remove_action( 'wp_head', 'wlwmanifest_link' );

WordPress管理画面にベーシック認証

ないよりは、あったほうが・・・

wp-login.phpにベーシック認証をかける

.htpasswdの作り方は・・ぐぐってください。。

.htaccess ファイル内容


<Files wp-login.php>
AuthType Basic
AuthUserFile /htpasswdのサーバーパス/.htpasswd
AuthGroupFile /dev/null
AuthName "Please enter your ID and password"
require valid-user
</Files>

/wp-admin/内すべてにベーシック認証をかける

.htaccessは、/wp-admin/内に作成します。

.htaccess ファイル内容


AuthType Basic
AuthUserFile /htpasswdのサーバーパス/.htpasswd
AuthGroupFile /dev/null
AuthName "Please enter your ID and password"
Require valid-user
<FilesMatch "(admin-ajax.php)$">
Satisfy Any
Order allow,deny
Allow from all
Deny from none
</FilesMatch>

最近のパスワードの桁数の強度?

下記から引用してます。

https://www.hivesystems.com/password

文字数 数字のみ 小文字のみ 大文字、小文字 数字、大文字、小文字 数字、大文字、小文字、記号
4 Instantly Instantly 3 secs 6 secs 9 secs
5 Instantly 4 secs 2 mins 6 mins 10 mins
6 Instantly 2 mins 2 hours 6 hours 12 hours
7 4 secs 50 mins 4 days 2 weeks 1 month
8 37 secs 22 hours 8 months 3 years 7 years
9 6 mins 3 weeks 33 years 161 years 479 years
10 1 hour 2 years 1k years 9k years 33k years
11 10 hours 44 years 89k years 618k years 2m years
12 4 days 1k years 4m years 38m years 164m years
13 1 month 29k years 241m years 2bn years 11bn years
14 1 year 766k years 12bn years 147bn years 805bn years
15 12 years 19m years 652bn years 9tn years 56tn years
16 119 years 517m years 33tn years 566tn years 3qd years
17 1k years 13bn years 1qd years 35qd years 276qd years
18 11k years 350bn years 91qd years 2qn years 19qn years

上記テーブルの単位

京の先なんて・・しらないよぉ・・

Table Abbreviation Word e.g. (10^n zeros)
k thousand 1,000.00
m million 1,000,000.00
bn billion 1,000,000,000.00
tn trillion 1,000,000,000,000.00
qd quadrillion 1,000,000,000,000,000.00
qn quintillion 1,000,000,000,000,000,000.00
sx sextillion 1,000,000,000,000,000,000,000.00
spt septillion 1,000,000,000,000,000,000,000,000.00
oct octillion 1,000,000,000,000,000,000,000,000,000.00
non nonillion 1,000,000,000,000,000,000,000,000,000,000.00
dec decillion 1,000,000,000,000,000,000,000,000,000,000,000.00

追記

汚染されたWordPressをみてみた。

wp-content\uploadsは、初めに・・汚染されてた・・

最近の、マルウェアは、base64、16進数でごまかすだけでなく。8進数と16進数でエンコードされてたり,goto分で解析できなくしてたり、複雑なんですね。

eval+base64_decodeが何十にかけられていて。1回2回じゃ・・コードにたどりつけない感じで・・

筆者は、下記を使ってみました。goto分などは変換できませんが、8進数と16進数、base64はデコードできます。

https://www.unphp.net/

ひと昔前は、1つファイルを上げておいて、タイムスタンプ変えておいて、それをもとにアップロードするファイルのタイムスタンプを先にに変えたタイムスタンプに変えるとか・・

今回は、タイムスタンプは。攻撃日から-500日とかして、タイムスタンプ書き換えてたり、いろいろだから、あてにならない。攻撃側も複数なのか?邪魔しあい?みたなのもあったり意味不明・・

BASE64ぽいファイルも、実際のファイルから先頭を数バイトはぶしてやらないと・・だめとか・・

マルウェア本体は、ファイラーソフト、ファイラーソフトをダウンロードしてくるもの、htaccessを生成するもの、ヘッダー、フッダーphpとかみたいな感じでした。

特定のPHPをアクセスすることで、htaccessできたりとっていうリモート操作的な感じなんですかね。phpだと拡張子phpにしちゃえばうごいちゃうので・・

WordPress本体のタイムスタンプもバラバラなので・・・それぽいファイル名、タイムスタンプにしておけば・・・わかんないよね。。みたいな・・

ファイラーもウィルスソフトで感知できなかったので、

wp-content\uploadsでphpの実行を禁止

wp-includesの実行禁止は・・下記URLから・・


<Directory "UPLOADS-PATH/var/www/wp-content/uploads/">
<Files "*.php">
Order Deny,Allow
Deny from All
</Files>

その他は下記を参照・・・

https://gist.github.com/danielmcclure/65f74034724d8fb3e7637d0c1290fd5f


# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
# Protect Important WP and Server Files
<FilesMatch "^.*(error_log|wp-config\.php|php.ini|\.[hH][tT][aApP].*)$">
Order deny,allow
Deny from all
</FilesMatch>
# Disable Index Browsing
Options All -Indexes
# Prevent Script Injections 
Options +FollowSymLinks
RewriteEngine On
RewriteCond %{QUERY_STRING} (<|%3C).*script.*(>|%3E) [NC,OR]
RewriteCond %{QUERY_STRING} GLOBALS(=|[|%[0-9A-Z]{0,2}) [OR]
RewriteCond %{QUERY_STRING} _REQUEST(=|[|%[0-9A-Z]{0,2})
RewriteRule ^(.*)$ index.php [F,L]
# Protect WP Includes Directory 
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
</IfModule>
# Prevent Username Enumeration
RewriteCond %{QUERY_STRING} author=d
RewriteRule ^ /? [L,R=301]
####
# The following settings require customisation to work - remove if unsure. 
####
# Block Bots from WP Admin
ErrorDocument 401 /index.php?error=404
ErrorDocument 403 /index.php?error=404
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_METHOD} POST
RewriteCond %{HTTP_REFERER} !^http://(.*)?YOURDOMAIN.com [NC]
RewriteCond %{REQUEST_URI} ^(.*)?wp-login\.php(.*)$ [OR]
RewriteCond %{REQUEST_URI} ^(.*)?wp-admin$
RewriteRule ^(.*)$ - [F]
</IfModule>
# Prevent Remote PHP Execution 
<Directory "UPLOADS-PATH/var/www/wp-content/uploads/">
<Files "*.php">
Order Deny,Allow
Deny from All
</Files>
 to join this convers

さいごに

とりあえず、急ぎで書いているのでまちがいがあるかもです。

では・・

関連記事