PHP Filesystem
Filesystem reference: read/write, tests, permissions, paths.
Key PointsBuild paths with `DIRECTORY_SEPARATOR` or `__DIR__` — never hand-written slashes.
Read and write
| Function | Purpose | Note |
|---|---|---|
| file_get_contents | 整个读入字符串 | 大文件别用 |
| file_put_contents | 字符串写入 | 默认覆盖,FILE_APPEND 追加 |
| fopen / fread / fwrite | 句柄式读写 | 可控内存 |
| fgets | 读一行 | 大文件按行处理 |
| file | 读成行数组 | FILE_IGNORE_NEW_LINES |
| readfile | 直接输出到缓冲区 | 下载文件常用 |
| fgetcsv | 读 CSV 一行 | 处理引号与逗号 |
PHP
<?php
$p = __DIR__ . "/demo.txt";
file_put_contents($p, "第一行\n第二行\n");
echo filesize($p), "\n";
echo count(file($p, FILE_IGNORE_NEW_LINES)), "\n";
echo file_get_contents($p);
file_put_contents($p, "追加\n", FILE_APPEND);
echo count(file($p)), "\n";
unlink($p);
echo file_exists($p) ? "还在" : "已删除", "\n";Output
20
2
第一行
第二行
3
已删除Tests and info
PHP
<?php
$p = __DIR__ . "/demo.txt";
file_put_contents($p, "x");
var_dump(is_file($p));
var_dump(is_dir($p));
var_dump(is_readable($p));
var_dump(is_writable($p));
echo pathinfo($p, PATHINFO_EXTENSION), "\n";
echo basename($p), "\n";
echo dirname($p) !== "" ? "有目录" : "无目录", "\n";
unlink($p);Output
bool(true)
bool(false)
bool(true)
bool(true)
txt
demo.txt
有目录is_file / is_dir results are cached (stat cache). Deleting then testing in the same request can still report true. Call clearstatcache().Permissions
PHP
<?php
$p = __DIR__ . "/perm.txt";
file_put_contents($p, "x");
printf("%o\n", fileperms($p) & 0777);
chmod($p, 0600);
clearstatcache();
printf("%o\n", fileperms($p) & 0777);
unlink($p);Output
644
600fileperms() includes file-type bits, so mask with 0777. Printing it raw gives a long octal string.
Upload and cache directories need
0755, **never 0777**. World-writable means any user on the box can modify it.Paths
PHP
<?php
$path = "/var/www/demo/a/b/c.txt";
echo basename($path), "\n";
echo dirname($path), "\n";
echo dirname($path, 2), "\n";
print_r(pathinfo($path));
echo implode(DIRECTORY_SEPARATOR, ["a", "b", "c"]), "\n";
echo realpath(__DIR__) !== false ? "可解析" : "不可解析", "\n";Output
/var/www/demo/a/b/c.txtSecurity
- Never use raw user input as a path ——
realpaththen verify the prefix - **Disable
allow_url_fopen** or never let users control URLs - Allowlist include paths —— dynamic includes are RCE
- Rename uploads —— keeping the original name gives up path control
include $_GET["page"] . ".php" is the classic LFI bug — combined with php://filter or log files it becomes RCE. Dynamic includes must come from an allowlist.读写
| 函数 | 作用 | 注意 |
|---|---|---|
| file_get_contents | 整个读入字符串 | 大文件别用 |
| file_put_contents | 字符串写入 | 默认覆盖,FILE_APPEND 追加 |
| fopen / fread / fwrite | 句柄式读写 | 可控内存 |
| fgets | 读一行 | 大文件按行处理 |
| file | 读成行数组 | FILE_IGNORE_NEW_LINES |
| readfile | 直接输出到缓冲区 | 下载文件常用 |
| fgetcsv | 读 CSV 一行 | 处理引号与逗号 |
PHP
<?php
$p = __DIR__ . "/demo.txt";
file_put_contents($p, "第一行\n第二行\n");
echo filesize($p), "\n";
echo count(file($p, FILE_IGNORE_NEW_LINES)), "\n";
echo file_get_contents($p);
file_put_contents($p, "追加\n", FILE_APPEND);
echo count(file($p)), "\n";
unlink($p);
echo file_exists($p) ? "还在" : "已删除", "\n";Output
20
2
第一行
第二行
3
已删除判断与信息
PHP
<?php
$p = __DIR__ . "/demo.txt";
file_put_contents($p, "x");
var_dump(is_file($p));
var_dump(is_dir($p));
var_dump(is_readable($p));
var_dump(is_writable($p));
echo pathinfo($p, PATHINFO_EXTENSION), "\n";
echo basename($p), "\n";
echo dirname($p) !== "" ? "有目录" : "无目录", "\n";
unlink($p);Output
bool(true)
bool(false)
bool(true)
bool(true)
txt
demo.txt
有目录is_file / is_dir 的结果会被缓存(stat cache)。同一请求里删了文件再判断,可能还返回 true。用 clearstatcache() 清掉。权限
PHP
<?php
$p = __DIR__ . "/perm.txt";
file_put_contents($p, "x");
printf("%o\n", fileperms($p) & 0777);
chmod($p, 0600);
clearstatcache();
printf("%o\n", fileperms($p) & 0777);
unlink($p);Output
644
600fileperms() 返回的数值还包含文件类型位,所以要和 0777 做与运算才能拿到权限部分。直接 printf("%o") 会得到一长串。
上传目录、缓存目录要
0755,**绝不能 0777**。0777 意味着同服务器上任何用户都能改,多站点环境下等于把写入权限交给邻居。路径处理
PHP
<?php
$path = "/var/www/demo/a/b/c.txt";
echo basename($path), "\n";
echo dirname($path), "\n";
echo dirname($path, 2), "\n";
print_r(pathinfo($path));
echo implode(DIRECTORY_SEPARATOR, ["a", "b", "c"]), "\n";
echo realpath(__DIR__) !== false ? "可解析" : "不可解析", "\n";Output
/var/www/demo/a/b/c.txt安全
- 用户输入不能直接当路径 —— 必须
realpath后校验前缀 - **禁用
allow_url_fopen** 或至少别让用户控制 URL - **
include的路径要白名单** —— 动态包含等于任意代码执行 - 文件名重命名 —— 保留原名等于放弃路径控制
include $_GET["page"] . ".php" 是最经典的本地文件包含漏洞,配合 php://filter 或日志文件就能变成远程代码执行。动态包含的路径必须来自白名单数组。