きっかけ
サーバーのフルマネージドをやっていると、新規でWordPressの設置依頼がよくあります。
ちょっと特殊かもしれないですが、WordPressのインストール画面を出すまでの状態にするためのスクリプトを作成してみました。
基本的に特にサーバーに設定を加えなくても、(うちはCentOSがメインなので)どのサーバーでも動かせるようにPython2系で作成してみました。
懸念点と対策
これまでWordPressを新規で設置してお客様に提供するまでにいくつかトラブルがあったのでまとめました。
パーミッション
Apache環境で一般ユーザーのFTPアカウントの場合、Webサーバーの実行ユーザー(Apache)とファイルのオーナー(FTPアカウント)が異なるため、ただWordPressファイルをアップロードしただけでは管理画面上のアップロードや更新についてパーミッションエラーになってしまいます。
通常その場合は、WordPressのアップロード後に wp-content 配下の themes uploads plugins の3ディレクトリのパーミッションを777にすることがあります。
=> スクリプトで自動設定することにしました。
Salt忘れ
WordPress設置後にwp-config.php内にあるSaltの設定を書き換え忘れてしまうことがあります。
(無くても動くけどあったほうが良い。それとめんどくさい。)
=> スクリプトで自動設定することにしました。
引き渡しまでの間の乗っ取り
WordPress設置完了後お客様に提供するまでの間、インストール画面が出ている状態なのですが、この僅かの間に攻撃車によって勝手にインストールが進められて乗っ取られてしまうことがありました。
=> インストール画面は自動的にBasic認証をかけることにしました。
このBasic認証は必要のない(いらない)時のためにon/off出来るようにします。
xmlrpc.phpへのアタック
外部投稿の仕組みを利用して、攻撃してくるパターンが多く、WordPressの乗っ取りやバックドアの配置等の攻撃を受けることが多いです。
=> xmlrpc.phpへのアクセスはデフォルト禁止にするようにしました。
お客様より利用したい旨の連絡があれば、IPアドレスによる制限解除等の措置を別途行うようにします。
uploads ディレクトリへの不審なPHPファイルの設置
万が一WordPressが攻撃を受けた時に、 wp-content/uploads ディレクトリにPHPファイルが置かれてしまう事が多いです。(パーミッション777にするため)
=> uploads 以下は画像等メディアの置き場なので基本的にPHPは置きません。なので、ここ以下のPHPファイルへのアクセスは禁止にします。
設定
これらの懸念点を解消するようにスクリプトを作成しました。
WordPressをインストールする環境のあるサーバーに2つファイルを設置します。
# wp-setup config
[init]
# Wordpress setup directory.
path=/var/www/vhosts/test.com/public_html
# wordpress version
# version = latest => download wordpress latest version
# or version number
# version list => https://ja.wordpress.org/download/releases/
# default : latest
# ex) setup worespress for version 4.9.7
# version = 4.9.7
version = latest
# set owner and group for wordpress files and directories.
# unix user and unix group.
user = username
group = groupname
# Set Basic Auth
# true or false
# .htpasswd file is generated into an installation pass automatically.
basic_auth = false
[mysql]
dbname = database_name
user = user_name
password = user_password
configファイルです。
基本的に設定はこのファイルで行います。
path= WordPress設置先のディレクトリです。
version = 設置するWordPressのバージョンです。環境(要望)によっては最新でないものの場合もあるので、バージョンを選べるようにしました。 latest が最新です。
user = group = 実際のファイルのオーナーです。基本的にFTPユーザーになると思います。
basic_auth = true/falseで設定。Basic認証のon/offです。onの場合、インストール完了後にBasic認証のログイン情報が出力されます。また.htpasswdファイルも自動生成されます。
[mysql] 欄。DBへの接続情報です。
# !/usr/bin/python
# -*- coding: utf-8 -*-
import sys
import os
import re
import pwd
import grp
import random
import string
import shutil
import urllib
import tarfile
from crypt import crypt
from ConfigParser import SafeConfigParser
configfile = 'config.ini'
tmpdir = '/tmp/'
salturl = 'https://api.wordpress.org/secret-key/1.1/salt/'
path = ''
basic_username = 'wpadmin'
def main():
version = getParam("init", "version")
path = getParam("init", "path")
if getParam("init", "basic_auth") == "true":
basic_auth = True
else :
basic_auth = False
params = {
'dbname' : getParam("mysql", "dbname"),
'user' : getParam("mysql", "user"),
'host' : getParam("mysql", "host"),
'password': getParam("mysql", "password")
}
if(not path.endswith("/")):
path = path + "/"
print "wordpress install to " + path
getWP(version, path)
salt = getSalt()
replaceWPConfig(path, params, salt)
pswd = setHtaccess(path, basic_auth)
setPermission(path)
print "A setup of wordpress has been completed."
if basic_auth :
print "--------"
print "Basic auth info"
print "username : " + basic_username
print "password : " + pswd
print "--------"
def getParam(section, param_name):
parser = SafeConfigParser()
parser.read(configfile)
return parser.get(section, param_name)
def setHtaccess(path, basic_auth):
pswd = ''
if basic_auth :
pswd = ''.join([random.choice(string.ascii_letters + string.digits) for i in range(12)])
htpasswd = open(path + '.htpasswd', 'a')
htpasswd.write(basic_username + ":" + crypt(pswd, 'generate_pswd'))
htpasswd.close()
f_output = open(path + '.htaccess', 'a')
f_output.write('#Generate by wp-setup.py\n')
f_output.write('\n# BEGIN WordPress\n')
f_output.write('<Files ~ "wp-content/uploads/*\.php$">\n')
f_output.write(' order deny,allow\n')
f_output.write(' deny from all\n')
f_output.write('</Files>\n')
f_output.write('<IfModule mod_rewrite.c>\n')
f_output.write('RewriteEngine On\n')
f_output.write('RewriteBase /\n')
f_output.write('RewriteRule ^xmlrpc\.php$ "http\:\/\/0\.0\.0\.0\/" [R=301,L]\n')
f_output.write('RewriteRule ^index\.php$ - [L]\n')
f_output.write('RewriteCond %{REQUEST_FILENAME} !-f\n')
f_output.write('RewriteCond %{REQUEST_FILENAME} !-d\n')
f_output.write('RewriteRule . /index.php [L]\n')
f_output.write('</IfModule>\n')
f_output.write('# END WordPress\n')
if basic_auth :
f_output.write('\n#Generate by wp-setup.py\n')
f_output.write('AuthUserfile ' + path + '.htpasswd\n')
f_output.write('AuthGroupfile /dev/null\n')
f_output.write('AuthName "Please enter your ID and password"\n')
f_output.write('AuthType Basic\n')
f_output.write('require valid-user\n')
f_output.close()
return pswd
def setPermission(path):
print "set owner and group"
uid = pwd.getpwnam(getParam("init", "user")).pw_uid
gid = grp.getgrnam(getParam("init", "group")).gr_gid
for file in findAllFiles(path):
os.chown(file, uid, gid)
os.chmod(path + "wp-content/", 0777)
os.chmod(path + "wp-content/plugins/", 0777)
os.chmod(path + "wp-content/themes/", 0777)
def findAllFiles(dir):
for root, dirs, files in os.walk(dir):
#if root == dir:
# continue
yield root
for file in files:
yield os.path.join(root, file)
def replaceWPConfig(path, params, salt):
print "set wordpress config data"
f_input = open(path + 'wp-config-sample.php')
f_output = open(path + 'wp-config.php', 'w')
for line in f_input:
if re.match(r"define\('DB_NAME", line):
line = "define('DB_NAME', '" + params["dbname"] + "');" + '\n'
elif re.match(r"define\('DB_USER", line):
line = "define('DB_USER', '" + params["user"] + "');" + '\n'
elif re.match(r"define\('DB_PASSWORD", line):
line = "define('DB_PASSWORD', '" + params["password"] + "');" + '\n'
elif re.match(r"define\('DB_HOST", line):
line = "define('DB_HOST', '" + params["host"] + "');" + '\n'
elif re.match(r"define\('AUTH_KEY'", line):
line = salt[0] + '\n'
elif re.match(r"define\('SECURE_AUTH_KEY'", line):
line = salt[1] + '\n'
elif re.match(r"define\('LOGGED_IN_KEY'", line):
line = salt[2] + '\n'
elif re.match(r"define\('NONCE_KEY'", line):
line = salt[3] + '\n'
elif re.match(r"define\('AUTH_SALT'", line):
line = salt[4] + '\n'
elif re.match(r"define\('SECURE_AUTH_SALT'", line):
line = salt[5] + '\n'
elif re.match(r"define\('LOGGED_IN_SALT'", line):
line = salt[6] + '\n'
elif re.match(r"define\('NONCE_SALT'", line):
line = salt[7] + '\n'
f_output.write(line)
f_input.close()
f_output.close()
def getSalt():
salt = urllib.urlopen(salturl).read().split("\n")
return salt
def getWP(ver, path):
if(not os.path.isdir(path)):
print path + " is not directory or no such directory."
sys.exit(1)
url = "https://ja.wordpress.org/"
regex = r'\d\.\d+.\d+'
if re.match(regex, ver):
filename = "wordpress-" + ver + "-ja.tar.gz"
else :
filename = "latest-ja.tar.gz"
# download wordpress and untar
print "downloading... " + url + filename
urllib.urlretrieve(url + filename, tmpdir + filename)
untar(tmpdir + filename)
print "done."
# move to setup directory
srcdir = tmpdir + "wordpress/"
files = os.listdir(srcdir)
for fname in files:
print "move from " + srcdir + fname + " to " + path
shutil.move(srcdir + fname, path)
print "delete tmp file " + tmpdir + filename
os.remove(tmpdir + filename)
print "delete tmp dir " + tmpdir + "wordpress/"
os.removedirs(tmpdir + "wordpress/")
def untar(fname):
if (fname.endswith("tar.gz")):
print "untar " + fname
tar = tarfile.open(fname)
tar.extractall(tmpdir)
tar.close()
else:
print "Not a tar.gz file: " + fname
if __name__ == '__main__':
main()
スクリプト本体です。
実行
試しに /var/www/html/ 以下、Basic認証ありWordPressを設置するとして、下記のようにconfig.iniを記載します。
# wp-setup config
[init]
# Wordpress setup directory.
path=/var/www/html
# wordpress version
# version = latest => download wordpress latest version
# or version number
# version list => https://ja.wordpress.org/download/releases/
# default : latest
# ex) setup worespress for version 4.9.7
# version = 4.9.7
version = latest
# set owner and group for wordpress files and directories.
# unix user and unix group.
user = root
group = root
# Set Basic Auth
# true or false
# .htpasswd file is generated into an installation pass automatically.
basic_auth = true
[mysql]
dbname = hoge
user = hoge
password = hogepass
host = localhost
実行します。
$ sudo ./wp-setup.py
wordpress install to /var/www/html/
downloading... https://ja.wordpress.org/latest-ja.tar.gz
untar /tmp/latest-ja.tar.gz
done.
move from /tmp/wordpress/wp-trackback.php to /var/www/html/
move from /tmp/wordpress/wp-cron.php to /var/www/html/
move from /tmp/wordpress/wp-comments-post.php to /var/www/html/
move from /tmp/wordpress/wp-blog-header.php to /var/www/html/
move from /tmp/wordpress/wp-config-sample.php to /var/www/html/
move from /tmp/wordpress/wp-includes to /var/www/html/
move from /tmp/wordpress/xmlrpc.php to /var/www/html/
move from /tmp/wordpress/index.php to /var/www/html/
move from /tmp/wordpress/wp-login.php to /var/www/html/
move from /tmp/wordpress/readme.html to /var/www/html/
move from /tmp/wordpress/wp-signup.php to /var/www/html/
move from /tmp/wordpress/wp-load.php to /var/www/html/
move from /tmp/wordpress/wp-content to /var/www/html/
move from /tmp/wordpress/wp-admin to /var/www/html/
move from /tmp/wordpress/wp-activate.php to /var/www/html/
move from /tmp/wordpress/wp-settings.php to /var/www/html/
move from /tmp/wordpress/license.txt to /var/www/html/
move from /tmp/wordpress/wp-mail.php to /var/www/html/
move from /tmp/wordpress/wp-links-opml.php to /var/www/html/
delete tmp file /tmp/latest-ja.tar.gz
delete tmp dir /tmp/wordpress/
set wordpress config data
set owner and group
A setup of wordpress has been completed.
--------
Basic auth info
username : wpadmin
password : jdpFPVotHpLv
--------
設置できました。Basic認証の情報も出力されているので、これでブラウザからアクセスしてみます。

Basic認証の画面が出てきたので先程の情報を入力してみます。
インストール画面が出せました。
実際に設置されている .htaccess の中身は下記のようになります。
(実際には行中のコメントはありません)
/var/www/html/.htaccess
# BEGIN WordPress
<Files ~ "wp-content/uploads/*\.php$">
order deny,allow
deny from all
</Files>
# ↑uploadsディレクトリのアクセスを禁止する。
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^xmlrpc\.php$ "http\:\/\/0\.0\.0\.0\/" [R=301,L]
# ↑xmlrpc.php へのアクセスは0.0.0.0にリダイレクトさせてアクセスさせません。
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
# ↑WordPressいつもの設定
</IfModule>
# END WordPress
# Generate by wp-setup.py
AuthUserfile /var/www/html/.htpasswd
AuthGroupfile /dev/null
AuthName "Please enter your ID and password"
AuthType Basic
require valid-user
# ↑Basic認証の設定。falseの場合はこの記載がありません。
1つのサーバーに複数のWordPressを置く場合は楽になりました。
