How to Secure Your MediaWiki Site: Permission Advice
- 中文版 by me
上上周,我偶然发现我的网站有一个致命的权限问题,意识到风险后连夜采取安全措施。现在把我学到的保护 MediaWiki 站点的方法,分享给大家。注:本文只讨论 Apache 服务器情况,Nginx 请绕道。
思想理念
- 只把对外要用到的 PHP 入口文件放在 web 根目录,其他一切(配置文件、源码、开发文件、构建工具配置、测试文件、文档等)都应移到 web 根之外,然后用 .htaccess 或 webserver 配置限制访问。
- 业务逻辑(源码)、静态资源分开放置
- webserver 仅可读源码,不可写入,不可执行
- websever 可拥有静态资源
- 上传目录要禁止代码运行
- 要阻止对编译产生的 .dot 文件的访问
- root:root 755 比 root:www-data 750 更开放,后者有时更好,因为对 o 做了限制
- 任何人、程序、浏览器,访问内容时,都属于某个用户组
- www-data 既是一个用户名,也是一个用户组名
- others 指属主、属主之外的任意用户
- mediawiki.conf sites.conf sites-le-ssl.conf 分开更有逻辑性,不用合并
- 对源码的各类配置只能是第二道防线,第一道防线应该从 apache 或 nginx 入手
- 把源码放在 /var/www 本身就是大隐患,会暴露技术栈。源码另存,仅把入口文件放在 /var/www 或 var/www/html,则入口只是 /,更安全
- Apache 尝试访问某个文件时,该文件的每一个上级路径都要有 x 权限
- Apache 通过 symbollink 也能访问文件,symbollink 权限由指向的源文件决定
- .htaccess 本身就是针对当前目录生效的配置,所以 <Directory> 块只能放在 sth.conf
- 改文件前先备份一个
知识清单
+FollowSymLinks → 允许 Apache 跟随文件系统里的符号链接
-Indexes → 禁止列目录(防止别人看到文件清单)。不会阻止访问目录下的具体文件
AllowOverride All → 开放 .htaccess 管理权限
php_admin_flag engine off → 禁止执行 PHP(防止上传漏洞)
目录的列目录 list 权限是由 r 控制的。
根限配置
#!/bin/bash
# change-permissions-and-secure-your-mediawiki.sh
# 较佳的 MediaWiki 的权限设置脚本,一键解决权限问题,保护你的 MediaWiki 网站的安全
set -euo pipefail
# 建立目录
SRC="/home/qlzq/mediawiki" # 刚下载下来的位置
TARGET="/home/safe/mediawiki" # 理想的安全的位置
WEBROOT="/var/www/html" # 网站的入口
mkdir -p "$TARGET"
rm -rf "/var/www/html/*"
# 把源码复制进目标地址
rsync -av --progress "$SRC/" "$TARGET/" && rm -rf "$SRC/"
# 缩减权限
chown -R root:www-data "$TARGET"
find "$TARGET" -type d -exec chmod 750 {} \;
find "$TARGET" -type f -exec chmod 640 {} \;
# 缩减权限,严控维护脚本
# 未来升级前需临时放宽对脚本的管制
chown -R root:root "$TARGET/maintenance"
chmod -R 750 "$TARGET/maintenance"
find "$TARGET/maintenance" -type f -exec chmod 640 {} \;
# 开放 images 文件夹
chown -R www-data:www-data "$TARGET/images"
chmod -R 750 "$TARGET/images"
find "$TARGET/images" -type f -exec chmod 640 {} \;
# 开放 cache 权限
# 如果不开放,则会出现 CBD 错误
chown -R www-data:www-data "$TARGET/cache"
# 开放小文件
mv "$TARGET/favicon.ico" "$WEBROOT/"
mv "$TARGET/404.html" "$WEBROOT/"
# 软链入口文件
for f in change-to-some-index.php; do
chown root:www-data "$TARGET/$f"
chmod 640 "$TARGET/$f"
ln -sf "$TARGET/$f" "$WEBROOT/"
done
# 软链资源文件
for d in change-to-some-resources; do
ln -sfn "$TARGET/$d" "$WEBROOT/"
done
# 细节维护
mv "$TARGET/.htaccess" htaccess.bak
chmod a+x "$TARGET/extensions/SyntaxHighlight_GeSHi/pygments/pygmentize"
echo "请检查 LocalSettings.php。各 conf 记得改。php maintenance/update.php 一下。确认首页 index.php 能正常访问。"
echo "Migration complete!"
文件保护
要善用 Apache 配置文件,把重要文件保护起来。且上传用的文件夹,要禁止源码被 webserver 写入。
常用的四个定位用关键字为:
- <Directory> </Directory>
- <DirectoryMatch> </DirectoryMatch>
- <Files> </Files>
- <FilesMatch> </FilesMatch>
需要保护的文件、文件夹有:
- 根目录
- 最重要的配置文件
- images 目录
- extensions 目录
- 其他源码目录
- .dot 文件
其他
数据库
# /etc/mysql/mariadb.conf.d/50-server.cnf
skip-networking
# 或者
bind-address = 127.0.0.1
使生效
a2enconf mediawiki-hardening.conf && apachectl configtest && systemctl reload apache2
php update.php
Emacs
杜绝 ~ # 文件泄密。
(setq make-backup-files nil) ;; 禁止生成 xxx~
(setq auto-save-default nil) ;; 禁止生成 #xxx#
tar
sudo tar czf xx -C /var/www mediawiki
。
大写 C 代表切换进目录。
小写 c 代表 create。
小写 z 代表 gzip。
小写 f 代表 file。
整条命令意思是进入 /var/www 目录,对 mediawiki 文件夹执行打包操作,形成 xx 文件。
ln
ln -sfn 存在 不存在
小写 s 代表 symbolic,软链,俗称快捷方式
小写 f 代表 force,目标位置有同名文件则先删除再链接
小写 n 代表 no dereference,不跟随符号链接。如果目标路径已经是一个符号链接,那么 -n 会把它当作普通文件来覆盖,而不会去跟随这个符号链接的指向。 👉 没有 -n 时,假如目标是个符号链接,可能会去覆盖它指向的文件,比较危险。
&&
&&:只有当前一个命令返回 0(成功) 时,才会执行后一个命令。
||:只有当前一个命令返回 非 0(失败) 时,才会执行后一个命令。
&:是把命令放到后台执行,并不是条件执行。
go-w
chmod -R go-w /var/www/mediawiki
令该目录无写入权限。
- English Version translated by ChatGPT
Two weeks ago I accidentally discovered a critical permission issue on my site. After realizing the risk I spent the night applying fixes. Here I share what I learned about protecting a MediaWiki site. Note: this article covers Apache only — Nginx users please consult other resources.
Philosophy
Put only the PHP entry files that must be web-accessible in the web root. Move everything else (config files, source code, dev files, build/tool configs, tests, docs, etc.) outside the web root, and restrict access with .htaccess or the webserver configuration.
Keep application code (source) separate from static assets.
The webserver should be able to read the code but must not be able to write or execute it.
The webserver may own static assets (images, uploaded files).
Upload directories must forbid code execution.
Block access to compiled .dot files.
root:root 755 is more permissive than root:www-data 750; the latter is sometimes preferable because it restricts "others".
Every request (by a person, program, or browser) happens under some user/group.
www-data is both a username and a group name.
"others" means users outside the file owner and owner-group.
Splitting Apache config into mediawiki.conf, sites.conf, sites-le-ssl.conf is cleaner — no need to merge them.
Source-level config is a second line of defense; the first should be Apache (or Nginx) configuration.
Storing source in /var/www is risky because it exposes your stack. Keep source elsewhere and place only the entry files in /var/www or /var/www/html so the site root is just the entry.
When Apache tries to access a file, every parent path must have the x (execute/search) bit.
Apache can follow symlinks; symlink access is governed by the target file permissions.
.htaccess affects its current directory, so <Directory> blocks belong in *.conf files.
Back up any file before editing it.
Knowledge
FollowSymLinks → allow Apache to follow filesystem symlinks.
-Indexes → disable directory listing (prevents exposing file lists). It does not block access to files if their names are known.
AllowOverride All → allow .htaccess to take effect.
php_admin_flag engine off → disable PHP execution (useful to mitigate upload-to-RCE).
Directory listing is controlled by the read (r) bit.
Permission Example
#!/bin/bash
# change-permissions-and-secure-your-mediawiki.sh
# Recommended MediaWiki permission script — one-shot to fix permissions and harden your site.
set -euo pipefail
paths
SRC="/home/qlzq/mediawiki" # where you downloaded the source
TARGET="/home/safe/mediawiki" # preferred secure location
WEBROOT="/var/www/html" # site document root
mkdir -p "$TARGET"
rm -rf "$WEBROOT/*"
# copy source to target and remove original
rsync -av --progress "$SRC/" "$TARGET/" && rm -rf "$SRC/"
# tighten permissions
chown -R root:www-data "$TARGET"
find "$TARGET" -type d -exec chmod 750 {} ;
find "$TARGET" -type f -exec chmod 640 {} ;
# restrict maintenance scripts (relax temporarily for upgrades)
chown -R root:root "$TARGET/maintenance"
chmod -R 750 "$TARGET/maintenance"
find "$TARGET/maintenance" -type f -exec chmod 640 {} ;
# allow images to be writable by webserver
chown -R www-data:www-data "$TARGET/images"
chmod -R 750 "$TARGET/images"
find "$TARGET/images" -type f -exec chmod 640 {} ;
# allow cache to be webserver-owned (otherwise CDB errors)
chown -R www-data:www-data "$TARGET/cache"
# move small static files to webroot
mv "$TARGET/favicon.ico" "$WEBROOT/"
mv "$TARGET/404.html" "$WEBROOT/"
# symlink entry files (adjust names)
for f in change-to-some-index.php; do
chown root:www-data "$TARGET/$f"
chmod 640 "$TARGET/$f"
ln -sf "$TARGET/$f" "$WEBROOT/"
done
# symlink resource directories (adjust names)
for d in change-to-some-resources; do
ln -sfn "$TARGET/$d" "$WEBROOT/"
done
# other details
mv "$TARGET/.htaccess" htaccess.bak
chmod a+x "$TARGET/extensions/SyntaxHighlight_GeSHi/pygments/pygmentize"
echo "Check LocalSettings.php and other confs. Run php maintenance/update.php. Confirm index.php loads."
echo "Migration complete!"
File protection
Use Apache configuration to protect important files and ensure upload folders cannot be written to by the webserver as executable code.
Common Apache location blocks:
- <Directory>...</Directory>
- <DirectoryMatch>...</DirectoryMatch>
- <Files>...</Files>
- <FilesMatch>...</FilesMatch>
Paths and items to protect:
- the site root
- crucial configuration files (e.g. LocalSettings.php)
- images directory
- extensions directory
- other source directories
- .dot files
Other notes
Database
# /etc/mysql/mariadb.conf.d/50-server.cnf
skip-networking
# or
bind-address = 127.0.0.1
Apply changes
a2enconf mediawiki-hardening.conf && apachectl configtest && systemctl reload apache2
php update.php
Emacs
Prevent leakage of ~ and # backup files:
(setq make-backup-files nil) ;; disable xxx~ files
(setq auto-save-default nil) ;; disable #xxx# files
tar example
sudo tar czf archive.tar.gz -C /var/www mediawiki
C change to directory
c=create
z=gzip
f=file
ln
ln -sfn target linkname
-s symbolic
-f force
-n no-dereference (do not follow existing symlink)
&&
&& : run the next command only if the previous returned 0 (success)
|| : run the next command only if the previous returned non-zero (failure)
& : run command in background (not conditional)
go-w
chmod -R go-w /var/www/mediawiki
Make directory non-writable