はじめに
今回は、緊急で、ブログ書いてます・・って
別件で、お客さんから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だったりと間口がおおい、
この画面からだけじゃない・・・
対策方法
?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 |
さいごに
とりあえず、急ぎで書いているのでまちがいがあるかもです。
では・・