公司官网一直部署在第三方平台,近期遭遇黑客攻击,官网显示乱码。
于是决定迁移到自己的云服务器上,整个流程比较繁琐,本文记录过程,以备后用。
从第三方平台下载 wordpress 源码和数据库备份;
看了一下源码,乱码主要存在哪个文件这会给整忘记了,当时是找到的,直接把乱码部分删掉就好了。
还发现一些恶意文件,主要在 wp-content/plugins 目录下,有很多以字符串.开头的二进制文件藏在代码里,恶意文件的名称为数字和小写字母组成的12位随机字符串,格式如 .vqef7iifvhfq,这玩意是挖矿木马,会占用机器的进程和资源。
写一个脚本全部删掉。
# find_dot_files.py
import os
import sys
import stat
def find_hidden_files(directory):
"""递归查找目录下所有以点号开头的隐藏文件"""
hidden_files = []
for root, dirs, files in os.walk(directory):
for file in files:
if file.startswith('.') and file not in [ '.DS_Store', '.gitignore', '.gitkeep', '.htaccess', '.ibase_pconnection' ]:
full_path = os.path.join(root, file)
hidden_files.append(full_path)
return hidden_files
def save_to_txt(file_list, output_file='tmp.txt'):
"""将文件列表保存到文本文件"""
with open(output_file, 'w') as f:
for file_path in file_list:
f.write(file_path + '\n')
def delete_files(file_paths):
"""
批量删除指定文件路径列表中的所有文件
:param file_paths: 文件路径列表,例如 ['/path/to/file1.txt', '/path/to/file2.log']
:return: 包含删除结果的字典,格式为 {文件路径: 删除状态}
"""
results = {}
for file_path in file_paths:
try:
# 检查文件是否存在
if not os.path.exists(file_path):
results[file_path] = "文件不存在"
continue
# 检查是否为文件(非目录)
if not os.path.isfile(file_path):
results[file_path] = "路径指向目录而非文件"
continue
# 处理只读文件:修改权限为可写
if not os.access(file_path, os.W_OK):
os.chmod(file_path, stat.S_IWRITE)
# 执行文件删除
os.remove(file_path)
# 二次验证是否删除成功
if not os.path.exists(file_path):
results[file_path] = "删除成功"
else:
results[file_path] = "文件仍存在(未知错误)"
except PermissionError:
results[file_path] = "权限不足(即使已尝试修改权限)"
except FileNotFoundError:
results[file_path] = "文件在删除过程中消失"
except IsADirectoryError:
results[file_path] = "路径指向目录(应使用shutil.rmtree)"
except Exception as e:
results[file_path] = f"未知错误: {str(e)}"
return results
if __name__ == "__main__":
# 获取命令行参数或使用当前目录
target_dir = sys.argv[1] if len(sys.argv) > 1 else os.getcwd()
if not os.path.isdir(target_dir):
print(f"错误:{target_dir} 不是有效目录")
sys.exit(1)
# 查找并保存隐藏文件
hidden_files = find_hidden_files(target_dir)
print(f"找到 {len(hidden_files)} 个隐藏文件")
delete_files(hidden_files)
print(f"找到 {len(hidden_files)} 个隐藏文件,已删除")
运行命令自动删除,这里只递归删除 plugins 目录下的恶意文件。
$ python3 find_dot_files.py /Users/dkvirus/wordpress/wp-content/plugins
创建一个目录结构如下:
|-- wordpress-docker
|-- wordpress # wordpress 源码
|-- init.sql # 数据库备份
|-- docker-compose.yml # docker-compose 配置文件
|-- Dockerfile # dockerfile
|-- nginx.conf # nginx 配置文件
wordpress 和 init.sql 就是上一步准备的源码和数据库备份。
nginx.conf 文件内容如下:
(啥也不用改)
server {
listen 80;
server_name localhost;
root /var/www/html;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass wordpress-php:9000; # 指向PHP-FPM容器
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
location ~ /\.ht {
deny all;
}
}
Dockerfile 文件内容如下:
(啥也不用改)
FROM php:8.2-fpm
RUN docker-php-ext-install pdo_mysql mysqli
docker-compose.yml 文件内容如下:
(<root_password>,<database_name>,<username>,<password> 需要自行修改)
注意:database_name 数据库名称需要和 init.sql 里面创建的数据库名称保持一致。
services:
# 数据库服务
mysql:
image: mysql:8.0.0 # MySQL 8.0 官方镜像通常支持多架构
container_name: wordpress-mysql
restart: always
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p$$MYSQL_ROOT_PASSWORD"]
interval: 5s
timeout: 3s
retries: 10
environment:
MYSQL_ROOT_PASSWORD: <root_password> # 数据库 root 密码
MYSQL_DATABASE: <database_name> # 数据库名称
MYSQL_USER: <username> # 数据库管理员用户名(非 root 用户)
MYSQL_PASSWORD: <password> # 数据库管理员密码(非 root 用户)
volumes:
- mysql_data:/var/lib/mysql # 数据持久化
- ./init.sql:/docker-entrypoint-initdb.d/init.sql # 初始化 SQL 脚本
ports:
- "3306:3306"
networks:
- wordpress-network
# PHP 处理服务
php:
build: . # 使用 Dockerfile 构建镜像
container_name: wordpress-php
restart: always
volumes:
- ./wordpress:/var/www/html # 挂载 WordPress 源码
depends_on:
- mysql
networks:
- wordpress-network
# Web 服务器
nginx:
image: nginx:alpine # 轻量级Nginx镜像
container_name: wordpress-nginx
restart: always
ports:
- "80:80" # 将本地 80 端口映射到容器
volumes:
- ./wordpress:/var/www/html # 静态文件和 PHP 代码
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro # Nginx配置
depends_on:
- php
networks:
- wordpress-network
# 定义网络和数据卷
networks:
wordpress-network:
driver: bridge
volumes:
mysql_data:
driver: local
运行命令启动服务:
# 构建 PHP 镜像
$ docker-compose build php
# 启动所有服务
$ docker-compose up -d
上面的步骤执行完,打开浏览器访问 http://localhost,理论上可以看到 wordpress 网站。
docker-compose 常用命令补充如下:
# 停止容器的同时,会移除为这些服务创建的容器和默认网络,但会保留数据卷
docker-compose down
# 删除在 docker-compose.yml 文件中定义的所有数据卷,其中的数据将无法恢复
docker-compose down -v
# 暂时停止服务,比如为了节省资源,并且计划稍后快速恢复
docker-compose stop
# 启动已停止的服务
docker-compose start
# 重启服务
docker-compose restart
# 构建镜像
$ docker-compose build php
# 启动所有服务
docker-compose up -d
访问 http://localhost,浏览器地址栏会自动跳转另一个网址 https://your_domain.com
解决方法:
进入数据库容器
(用 docker-compose.yml 中定义的数据库用户名和密码替换下面的 <username> 和 <password>)
$ docker exec -it wordpress-mysql mysql -u <username> -p<password>
查看表字段,可以看到 siteurl 和 home 两个字段是你的域名访问地址。
(用 docker-compose.yml 中定义的数据库名称替换下面的 <database_name>)
> show databases; # 查看所有数据库
> use <database_name>; # 用 docker-compose.yml 中使用的数据库 <database_name>
> SELECT option_name, option_value FROM <database_name>.wp_options WHERE option_name IN ('siteurl', 'home'); # 查看 wp_options 表中 siteurl 和 home 两个字段的值
+-------------+------------------+
| option_name | option_value |
+-------------+------------------+
| home | https://your_domain.com |
| siteurl | https://your_domain.com |
+-------------+------------------+
2 rows in set (0.02 sec)
使用如下命令将这两个字段值修改为 http://localhost 即可。
(用 docker-compose.yml 中定义的数据库名称替换下面的 <database_name>)
> UPDATE <database_name>.wp_options
SET option_value = 'http://localhost'
WHERE option_name IN ('siteurl', 'home');
访问 http://localhost,自动变成了 https://localhost
原因:wordpress 使用了 wp force ssl 插件。
解决方法:/wp-content/plugins/ 目录,找到对应的SSL插件文件夹并将其重命名(例如,在文件夹名后加 -old)
验证: curl -I http://localhost # 应返回 HTTP 200,非 301/302
HTTP/1.1 403 Forbidden原因:Nginx 没有权限访问 wordpress 源码目录。
解决:修改文件访问权限。
$ chmod -R 755 ./wordpress-docker
在本地测试没问题,就可以弄到服务器上部署,并且通过域名进行访问,以下所有操作都是在远程 Linux 服务器上进行的。
新机器如果没有安装 Docker,可以参考《Linux 安装 Docker》 进行安装。
提前将 wordpress 源码和数据库备份复制到服务器上。
创建一个目录结构如下:
|-- wordpress-docker
|-- wordpress # wordpress 源码
|-- init.sql # 数据库备份
|-- docker-compose.yml # docker-compose 配置文件
|-- Dockerfile # dockerfile
|-- nginx.conf # nginx 配置文件
|-- certs
|-- website.crt # 证书
|-- website.key # 私钥
wordpress 和 init.sql 就是上一步准备的源码和数据库备份。
certs 目录存放证书,需要 https,里面存放证书和私钥两个文件。
编辑 nginx.conf 文件,内容如下:
(需要将 <your_domain.com> 替换为你的域名)
server {
listen 80;
server_name <your_domain.com>; # 替换为你的域名
return 301 https://$host$request_uri; # 访问 http 跳转到 https
}
server {
listen 443 ssl;
server_name <your_domain.com>; # 替换为你的域名
# SSL证书配置
ssl_certificate /etc/nginx/certs/website.crt; # 证书路径(容器内)
ssl_certificate_key /etc/nginx/certs/website.key; # 私钥路径(容器内)
ssl_session_timeout 5m; # 缓存有效期
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; # 加密算法
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # 安全链接可选的加密协议
ssl_prefer_server_ciphers on; # 使用服务器端的首选算法
root /var/www/html;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass wordpress-php:9000; # 指向PHP-FPM容器
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
location ~ /\.ht {
deny all;
}
}
编辑 Dockerfile 文件,内容如下:
FROM php:8.2-fpm
RUN docker-php-ext-install pdo_mysql mysqli
编辑 docker-compose.yml 文件,内容如下:
services:
# 数据库服务
mysql:
image: mysql:8.0.0 # MySQL 8.0 官方镜像通常支持多架构
container_name: wordpress-mysql
restart: always
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p$$MYSQL_ROOT_PASSWORD"]
interval: 5s
timeout: 3s
retries: 10
environment:
MYSQL_ROOT_PASSWORD: <root_password> # 数据库 root 密码
MYSQL_DATABASE: <database_name> # 数据库名称
MYSQL_USER: <username> # 数据库管理员用户名(非 root 用户)
MYSQL_PASSWORD: <password> # 数据库管理员密码(非 root 用户)
volumes:
- mysql_data:/var/lib/mysql # 数据持久化
- ./init.sql:/docker-entrypoint-initdb.d/init.sql # 初始化SQL脚本[6](@ref)[8](@ref)
ports:
- "3306:3306"
networks:
- wordpress-network
# PHP 处理服务
php:
build: . # 使用PHP 8.2 FPM镜像
container_name: wordpress-php
restart: always
volumes:
- ./wordpress:/var/www/html # 挂载WordPress源码
depends_on:
- mysql
networks:
- wordpress-network
# Web 服务器
nginx:
image: nginx:alpine # 轻量级Nginx镜像
container_name: wordpress-nginx
restart: always
ports:
- "80:80" # 将本地80端口映射到容器
- "443:443"
volumes:
- ./wordpress:/var/www/html # 静态文件和PHP代码
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro # Nginx配置[2]
- ./certs:/etc/nginx/certs:ro
depends_on:
- php
networks:
- wordpress-network
# 定义网络和数据卷
networks:
wordpress-network:
driver: bridge
volumes:
mysql_data:
driver: local
运行命令启动服务:
# 构建 PHP 镜像
$ docker-compose build php
# 启动所有服务
$ docker-compose up -d
前往域名注册平台,将域名解析到服务器对应的 IP 地址。(可能需要几分钟生效)
访问你的域名 https://your_domain.com 不出意外就可以看到网站了。
这里不需要像在本地测试那样修改 wp_options 表中的 siteurl 和 home 字段了,因为本身就是要通过域名进行访问的。
通常买一台云服务器,默认关闭绝大多数端口,需要前往云服务控制台手动放开对应的端口号。
检查服务器是否放开了 80 和 443 端口。
通过域名访问返回 HTTP/1.1 403 Forbidden,原因是 Nginx 没有权限访问 wordpress 源码目录。
$ chmod -R 755 ./wordpress-docker
↶ 返回首页 ↶