DedeCMS 是国内被入侵率最高的几个 CMS 之一,2020 年起官方维护停滞之后情况更糟。安装后的默认权限基本是「写一个 webshell 进 uploads 目录就能拿管理员权限」的状态。我维护过的几台被攻陷的服务器,事后回溯都能追到三类根因:后台目录可猜、php 进程对全站可写、uploads 与 data 目录有脚本执行权限。本文按 Linux 部署场景给出系统化的加固方案,覆盖 Apache mpm-itk 与 Nginx php-fpm 两套权限隔离模型、后台目录迁移与重命名、php.ini 危险函数禁用、SELinux 策略、fail2ban 暴力破解防护、SSL/TLS 强制升级,以及如何在被入侵后快速止血并溯源。
DedeCMS 默认部署的攻击面
典型攻击路径
过去几年公开的 DedeCMS 攻击案例归纳起来就五条路径:
- uploads 目录上传 webshell:默认 uploads/ 目录有写入权限+脚本执行权限,结合后台未鉴权的 file_manager 接口或者前台某个有上传漏洞的 plus/ 程序,攻击者拿到 webshell。
- 后台爆破:默认后台目录就叫 dede/,有人写脚本扫遍了互联网上的 dede.php 登录入口做密码爆破。
- SQL 注入:plus/ 目录下若干历史漏洞(recommend.php、search.php、carbuyaction.php 等),未打补丁的站点容易被自动化工具命中。
- data 目录任意文件读写:data/ 目录在某些版本不被 web server 拒绝,攻击者直接读 config.cache.inc.php 拿到数据库密码。
- 会话劫持:sessions_xxx 目录权限过松,多用户共享主机时跨站点拿对方 session。
加固的核心思路
用「最小权限原则」分层隔离:
- web server 进程 ≠ 文件 owner:让 nginx/apache 进程对站点目录默认只读,写入只在白名单目录开放。
- 前台业务 ≠ 后台管理:前台 PHP 跑 www-data 账号,后台 PHP 跑 www-admin 账号,分别拥有自己的可写区域。
- uploads 与 data 不可执行:即便上传成功 .php 文件,web server 也拒绝执行。
- 后台 URL 不可猜:dede 改名 + IP 白名单 + Basic Auth 三重保护。
Apache 方案:mpm-itk 模块前后台权限分离
为什么必须装 mpm-itk
Apache 默认所有虚拟主机共享同一个进程用户(多数 Linux 发行版是 www-data 或 apache)。结果就是 A 站被入侵,攻击者立刻能读 B 站的 wp-config.php、config.inc.php 这些敏感配置。mpm-itk 模块允许每个 vhost 指定不同的运行用户,从根上隔离。
安装与配置
sudo apt-get install apache2-mpm-itk
sudo a2enmod mpm_itk
sudo systemctl restart apache2注意 mpm-itk 与 mpm_event、mpm_worker 互斥,安装时会自动卸载其它 MPM 模块。
后台运行账号
sudo useradd -g www-data -d /dev/null -s /usr/sbin/nologin www-admin
sudo usermod -L www-admin # 锁定密码登录nologin shell + 锁定密码确保 www-admin 账号无法直接登录,只能被 Apache 用作进程身份。
迁移后台目录
默认 /dede 目录是公开秘密,必须改名 + 移到独立路径。建议改成不可猜字符串:
sudo mkdir /var/dedecms-admin
sudo mv /var/dedecms/dede/* /var/dedecms-admin/
sudo rmdir /var/dedecms/dede
sudo chown -R www-admin:www-data /var/dedecms-admin
sudo chmod -R 750 /var/dedecms-adminchmod 750:owner(www-admin)可读写执行,group(www-data)只读执行,other 无权限。
独立后台 vhost 配置
新建 /etc/apache2/sites-available/dedecms-admin.conf:
<VirtualHost *:443>
ServerName admin-9k7x.example.com
DocumentRoot /var/dedecms-admin
SSLEngine on
SSLCertificateFile /etc/ssl/certs/example.com.crt
SSLCertificateKeyFile /etc/ssl/private/example.com.key
AssignUserId www-admin www-data
# IP 白名单(办公网/VPN 出口)
<Directory /var/dedecms-admin/>
Options -Indexes -FollowSymLinks
AllowOverride None
Require ip 203.0.113.0/24
Require ip 198.51.100.0/24
</Directory>
# Basic Auth 二重门
<Location />
AuthType Basic
AuthName "Admin Only"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user
</Location>
# 错误日志
ErrorLog ${APACHE_LOG_DIR}/dedecms-admin-error.log
CustomLog ${APACHE_LOG_DIR}/dedecms-admin-access.log combined
</VirtualHost>三个细节:
- 子域名不可猜:admin-9k7x.example.com 这种带随机串的子域名比 admin.example.com 安全很多,攻击者扫域名时找不到。
- IP 白名单:限定办公网或 VPN 出口 IP 才能访问。这是抵抗 90% 自动化扫描的最简手段。
- Basic Auth:哪怕 IP 被绕过(攻击者拿到内网跳板),还有一道密码门。
生成 htpasswd:
sudo htpasswd -c /etc/apache2/.htpasswd dedeops启用 vhost:
sudo a2ensite dedecms-admin
sudo systemctl reload apache2修复后台代码引用路径
后台目录迁移后,原本的相对路径都要调整。最少修改三个文件:
/var/dedecms-admin/config.php 第 12 行:
require_once(DEDEADMIN.'/../include/common.inc.php');改为:
require_once(DEDEADMIN.'/../dedecms/include/common.inc.php');/var/dedecms-admin/login.php 第 11 行与 /var/dedecms-admin/exit.php 第 11 行同样的 include 路径修复。
/var/dedecms/data/safe/inc_safe_config.php 第 2 行:
$safe_gdopen = '1,2,3,4,5,7';这个开关控制各处验证码是否开启,开越多越好。
必要的 include 资源同步
后台 UI 依赖 /var/dedecms/include/ 下的 dialog、js、ckeditor 等文件夹。把这些复制或软链到后台目录:
sudo mkdir /var/dedecms-admin/include
sudo ln -s /var/dedecms/include/dialog /var/dedecms-admin/include/dialog
sudo ln -s /var/dedecms/include/js /var/dedecms-admin/include/js
sudo ln -s /var/dedecms/include/ckeditor /var/dedecms-admin/include/ckeditor用软链而不是 cp -R 的好处是 DedeCMS 升级 include 时自动同步到后台。
Nginx 方案:php-fpm 独立 pool 实现权限隔离
为什么 php-fpm 比 mpm-itk 更优
php-fpm 默认就支持「不同 pool 不同用户」机制,无需额外模块。性能上 php-fpm + nginx 比 apache + mpm-itk 高 30-50%(因为 mpm-itk 每次请求 fork 一个进程,php-fpm 是常驻进程池)。
安装基础组件
sudo apt-get install nginx php-fpm
sudo apt-get install php-mysql php-gd php-curl php-mbstring php-xml php-zip
sudo systemctl stop apache2 # 如有旧 apache创建后台专属 php-fpm pool
复制默认 pool 配置:
cd /etc/php/8.1/fpm/pool.d/
sudo cp www.conf admin.conf编辑 admin.conf 改三处:
[admin]
user = www-admin
group = www-data
listen = /run/php/php8.1-fpm-admin.sock
listen.owner = www-admin
listen.group = www-data
listen.mode = 0660用 unix socket 而不是 127.0.0.1:9001 端口,性能更优、不暴露在外网。
nginx 前台站点配置
/etc/nginx/sites-available/dedecms-front:
server {
listen 443 ssl http2;
server_name www.example.com;
root /var/dedecms;
index index.php index.html;
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
# 禁止访问敏感目录
location ~ ^/(data|templets|include|dede|member|special|company)/.*\.(php|php5|phtml)$ {
deny all;
}
# uploads 目录禁止脚本执行
location ~* ^/uploads/.*\.(php|php5|phtml|pht|phar|cgi)$ {
deny all;
}
# 隐藏文件保护
location ~ /\. {
deny all;
}
# PHP 处理
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# 静态资源缓存
location ~* \.(jpg|jpeg|png|gif|css|js|woff|woff2|ico)$ {
expires 30d;
access_log off;
}
}
server {
listen 80;
server_name www.example.com;
return 301 https://$server_name$request_uri;
}nginx 后台站点配置
/etc/nginx/sites-available/dedecms-admin:
server {
listen 443 ssl http2;
server_name admin-9k7x.example.com;
root /var/dedecms-admin;
index index.php;
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
# IP 白名单
allow 203.0.113.0/24;
allow 198.51.100.0/24;
deny all;
# Basic Auth
auth_basic "Admin Only";
auth_basic_user_file /etc/nginx/.htpasswd;
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/run/php/php8.1-fpm-admin.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\. {
deny all;
}
}关键差异:admin 站点用 admin pool 的 socket(fpm-admin.sock),跑 www-admin 进程身份;前台用 default pool socket(fpm.sock),跑 www-data 进程身份。
启用站点
sudo ln -s /etc/nginx/sites-available/dedecms-front /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/dedecms-admin /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo systemctl restart php8.1-fpm文件权限的精细化设置
分层权限模型
# 整体默认 750(owner 读写执行,group 读执行)
sudo chown -R www-admin:www-data /var/dedecms
sudo find /var/dedecms -type d -exec chmod 750 {} \;
sudo find /var/dedecms -type f -exec chmod 640 {} \;
# uploads 与 data 需要 web 进程可写
sudo chown -R www-data:www-data /var/dedecms/uploads /var/dedecms/data
sudo chmod -R 770 /var/dedecms/uploads /var/dedecms/data
# install 目录可以删
sudo rm -rf /var/dedecms/installdata/safe 目录强加固
data/safe/ 里的 inc_safe_config.php 是站点配置,攻击者最想读。改 600 只让 owner 读:
sudo chmod 600 /var/dedecms/data/safe/inc_safe_config.php
sudo chmod 600 /var/dedecms/data/common.inc.phpplus 目录的处理
plus/ 下有几个高危文件历史上被反复曝出漏洞。如果你不用对应功能,直接删:
cd /var/dedecms/plus
sudo rm -f recommend.php carbuyaction.php carbuy.php advancedsearch.php mytag_js.php这一招能挡掉自动化扫描的 80%。
php.ini 危险函数禁用
编辑 /etc/php/8.1/fpm/php.ini 找到 disable_functions:
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_multi_exec,parse_ini_file,show_source,phpinfo,assert,eval,gzinflate,base64_decode,str_rot13这些函数是 webshell 最常用的入口。一旦禁用,绝大多数公开 webshell 工具直接失效。
同时修改:
expose_php = Off
allow_url_fopen = Off
allow_url_include = Off
display_errors = Off
log_errors = On
error_log = /var/log/php-fpm/php-fpm-error.log
session.cookie_httponly = 1
session.cookie_secure = 1重启 php-fpm 生效。
SELinux 与 AppArmor
CentOS / RHEL 用 SELinux
默认开启的 SELinux 会给 /var/www/ 下的文件打 httpd_sys_content_t 标签。如果你的 DedeCMS 装在非标准路径(比如 /opt/dedecms),需要手动打标:
sudo semanage fcontext -a -t httpd_sys_content_t "/opt/dedecms(/.*)?"
sudo restorecon -Rv /opt/dedecmsuploads 与 data 需要可写:
sudo semanage fcontext -a -t httpd_sys_rw_content_t "/opt/dedecms/uploads(/.*)?"
sudo semanage fcontext -a -t httpd_sys_rw_content_t "/opt/dedecms/data(/.*)?"
sudo restorecon -Rv /opt/dedecms/uploads /opt/dedecms/dataUbuntu 用 AppArmor
Ubuntu 默认开启的 AppArmor 提供类似功能。看当前状态:
sudo aa-status如果 nginx 或 apache 没有 profile,可以创建一个 enforce 模式的策略限制其访问范围。
fail2ban 暴力破解防护
即便后台改了名加了 IP 白名单,互联网层面仍有大量尝试性扫描。fail2ban 监控访问日志,发现异常行为自动封 IP:
sudo apt-get install fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local编辑 jail.local 启用 nginx-http-auth jail:
[nginx-http-auth]
enabled = true
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 5
findtime = 600
bantime = 8640010 分钟内 5 次 Basic Auth 失败的 IP 封禁 24 小时。
对 DedeCMS 后台登录接口写自定义 filter:
# /etc/fail2ban/filter.d/dedecms-login.conf
[Definition]
failregex = ^<HOST> .* "POST /login\.php.*" 200
ignoreregex =启用:
[dedecms-login]
enabled = true
filter = dedecms-login
logpath = /var/log/nginx/dedecms-admin-access.log
maxretry = 3
findtime = 600
bantime = 86400SSL/TLS 强制升级
HTTP 到 HTTPS 强制 301 跳转前面已经在 nginx/apache 配置里给了。补充几个加固头:
# nginx 后台站点 server 块内
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;HSTS 头让浏览器一年内强制走 HTTPS,连 HTTP 都不发起请求;CSP 防止 XSS 注入跨域脚本。
定期备份与日志审计
每日备份脚本
#!/bin/bash
# /usr/local/bin/dedecms-backup.sh
DATE=$(date +%Y%m%d)
BACKUP_DIR=/var/backups/dedecms
mkdir -p $BACKUP_DIR
# 数据库备份
mysqldump -u root -p'YOUR_PASS' --single-transaction --hex-blob dedecms_db | gzip > $BACKUP_DIR/db_$DATE.sql.gz
# 文件备份(排除大附件)
tar --exclude='/var/dedecms/uploads/big_files' -czf $BACKUP_DIR/files_$DATE.tar.gz /var/dedecms /var/dedecms-admin
# 保留最近 30 天
find $BACKUP_DIR -name "db_*.sql.gz" -mtime +30 -delete
find $BACKUP_DIR -name "files_*.tar.gz" -mtime +30 -deletecron 设置每天凌晨执行:
0 3 * * * /usr/local/bin/dedecms-backup.sh异地备份
本地备份不够,必须传到异地(OSS、S3、另一台机器)。用 rclone 或 awscli:
0 4 * * * rclone sync /var/backups/dedecms remote:dedecms-backup/$(date +\%Y\%m)被入侵后的应急响应
第一时间止血
- nginx/apache 改 server 配置返回 503,关掉访问。
- 断开 MySQL 远程访问:iptables 封 3306 端口。
- 临时拒绝所有 PHP 执行:在 nginx 配置里把 location ~ \.php$ 块整个 deny。
溯源调查
- 看 nginx access.log 找异常 POST 请求(特别是 plus/ 与 uploads/ 路径)。
- find /var/dedecms -newer /tmp/some-marker -type f 找最近修改过的文件。
- find / -name "*.php" -newer /tmp/some-marker 找全盘新增的 PHP 文件(webshell 嫌疑)。
- 看 mysql binlog 找异常 SQL(特别是 INSERT 进 dede_admin 表的)。
恢复流程
不要在被入侵的系统上「修一修就好」——攻击者多半已经留了多个后门。正确做法:从备份里彻底重建系统、重置所有密码(数据库、SSH、后台、SMTP)、重新生成 SSL 证书、清空 sessions。
常见问题解答
把后台目录改名后还需要其它防护吗?
需要。改名只解决「目录可猜」,但攻击者拿到一份你的备份文件或日志也能知道新目录名。完整防护是「改名 + IP 白名单 + Basic Auth + fail2ban」四件套。
mpm-itk 性能损失大吗?
每个请求 fork 进程比常驻进程慢 10-30%。中小站点流量不大感知不到,QPS 上千的高流量站点建议改用 nginx + php-fpm 多 pool 方案。
uploads 目录禁止 PHP 执行后图片缩略图功能受影响吗?
不影响。缩略图生成是 PHP 进程内 GD 调用,与「上传目录里的 .php 文件能不能被外部请求」是两回事。本文 nginx 配置只拦截 location ~* ^/uploads/.*\.(php|...) 这种通过 URL 直接访问的请求,PHP 进程内的 image_create 调用不走这条路径。
php.ini disable_functions 禁用 exec 后某些功能挂了?
少数 DedeCMS 模块(特别是图像处理、备份模块)会调 exec 调外部 ImageMagick。三种处理:用 PHP 内置函数替代(imagemagick 改 GD);指定具体可调的安全命令而不是禁用整个 exec;把这些功能转移到独立的内网服务通过 HTTP 调用。
为什么不直接用 chroot 把每个站点关进笼子?
chroot 配置复杂,要把所有依赖库(libc、libz 等)拷进 chroot 目录。对单台服务器跑多个 PHP 站点,php-fpm 多 pool + 不同 user 已经够用,chroot 边际收益不大。容器化(Docker)是更现代的隔离方案。
CentOS 的 SELinux 总是拦我的功能怎么办?
不要 setenforce 0 关掉 SELinux。正确做法是看 /var/log/audit/audit.log,找出具体哪个动作被拒,然后用 audit2allow 生成对应的策略允许。SELinux 的拦截多数是合理的安全限制。
fail2ban 把我自己也封了怎么办?
把办公网 IP 加到 fail2ban 的 ignoreip:在 jail.local 顶部 [DEFAULT] 区段加 ignoreip = 127.0.0.1/8 ::1 203.0.113.0/24。
HTTPS 证书续期失败怎么办?
多数证书续期失败是 Let's Encrypt 验证 .well-known 路径取不到(被 nginx 全站 deny 拦了)。在 server 块顶部加白名单:location /.well-known/acme-challenge/ { allow all; } 优先级高于其它规则。
已经被入侵了能不能不重装直接清理?
极不推荐。攻击者可能在系统的 cron、systemd unit、动态库注入、内核模块、ssh authorized_keys 等多处留了持久化后门。仅清理 webshell 就好的概率不到 30%。
有没有现成的扫描工具能查 DedeCMS 是否已被入侵?
可以用 maldet(Linux Malware Detect)扫描已知 webshell 特征:maldet -a /var/dedecms。但只能查已知样本,定制 webshell 查不到。配合人工审计 access.log 与近期文件修改才是完整方案。