PHP ftp_raw() 函数用于向FTP服务器发送原始命令并返回服务器的响应数组。
ftp_raw(resource $ftp, string $command): array
| 参数 | 描述 |
|---|---|
ftp |
必需。FTP连接的标识符,由ftp_connect()或ftp_ssl_connect()返回 |
command |
必需。要发送到FTP服务器的原始命令字符串 |
| 命令 | 描述 | 等效PHP函数 |
|---|---|---|
USER username |
指定登录用户名 | ftp_login()的一部分 |
PASS password |
指定登录密码 | ftp_login()的一部分 |
PWD |
打印工作目录 | ftp_pwd() |
CWD path |
改变工作目录 | ftp_chdir() |
LIST [path] |
列出目录内容 | ftp_rawlist() |
NLST [path] |
名称列表 | ftp_nlist() |
PASV |
进入被动模式 | ftp_pasv() |
RETR file |
检索(下载)文件 | ftp_get() |
STOR file |
存储(上传)文件 | ftp_put() |
DELE file |
删除文件 | ftp_delete() |
MKD path |
创建目录 | ftp_mkdir() |
RMD path |
删除目录 | ftp_rmdir() |
RNFR oldname |
重命名从 | ftp_rename()的一部分 |
RNTO newname |
重命名为 | ftp_rename()的一部分 |
SIZE file |
获取文件大小 | ftp_size() |
MDTM file |
获取文件修改时间 | ftp_mdtm() |
SYST |
获取系统类型 | ftp_systype() |
QUIT |
断开连接 | ftp_quit()或ftp_close() |
<?php
// 连接FTP服务器
$ftp_server = "ftp.example.com";
$ftp_user = "username";
$ftp_pass = "password";
$ftp_conn = ftp_connect($ftp_server);
if (!$ftp_conn) {
die("无法连接到 $ftp_server");
}
// 使用原始命令登录
echo "使用原始命令登录...\n";
$response = ftp_raw($ftp_conn, "USER $ftp_user");
print_r($response);
$response = ftp_raw($ftp_conn, "PASS $ftp_pass");
print_r($response);
// 检查是否登录成功
$response = ftp_raw($ftp_conn, "SYST");
echo "服务器系统类型: ";
print_r($response);
// 获取当前目录
$response = ftp_raw($ftp_conn, "PWD");
echo "当前目录: ";
print_r($response);
// 列出目录内容
$response = ftp_raw($ftp_conn, "LIST");
echo "目录列表: \n";
foreach ($response as $line) {
echo " $line\n";
}
// 关闭连接
$response = ftp_raw($ftp_conn, "QUIT");
print_r($response);
ftp_close($ftp_conn);
?>
<?php
class RawFTPClient {
private $conn;
private $debug = false;
public function __construct($server, $port = 21, $timeout = 90) {
$this->conn = ftp_connect($server, $port, $timeout);
if (!$this->conn) {
throw new Exception("无法连接到FTP服务器: $server");
}
$this->log("连接到服务器: $server");
}
public function login($username, $password) {
$this->sendCommand("USER $username");
$response = $this->sendCommand("PASS $password");
// 检查登录是否成功(响应代码 230)
if ($this->getResponseCode($response[0]) == 230) {
$this->log("登录成功: $username");
return true;
}
throw new Exception("登录失败: " . implode("\n", $response));
}
public function sendCommand($command) {
$this->log("发送命令: $command");
$response = ftp_raw($this->conn, $command);
$this->log("服务器响应: " . implode(" | ", $response));
return $response;
}
public function getSystemType() {
$response = $this->sendCommand("SYST");
if (count($response) > 0) {
// 移除响应代码,只返回系统类型
return trim(substr($response[0], 4));
}
return "未知";
}
public function getDetailedList($path = "") {
$command = $path ? "LIST $path" : "LIST";
return $this->sendCommand($command);
}
public function getSimpleList($path = "") {
$command = $path ? "NLST $path" : "NLST";
return $this->sendCommand($command);
}
public function getFileSize($filename) {
$response = $this->sendCommand("SIZE $filename");
if (count($response) > 0) {
$code = $this->getResponseCode($response[0]);
if ($code == 213) {
// 213 后面是文件大小
return intval(trim(substr($response[0], 4)));
}
}
return -1;
}
public function getFileModificationTime($filename) {
$response = $this->sendCommand("MDTM $filename");
if (count($response) > 0) {
$code = $this->getResponseCode($response[0]);
if ($code == 213) {
// 返回格式: 213 YYYYMMDDHHMMSS[.sss]
$timestamp = trim(substr($response[0], 4));
return $this->parseFTPTimestamp($timestamp);
}
}
return 0;
}
public function createDirectory($dirname) {
$response = $this->sendCommand("MKD $dirname");
$code = $this->getResponseCode($response[0]);
return $code == 257; // 257 表示目录创建成功
}
public function deleteDirectory($dirname) {
$response = $this->sendCommand("RMD $dirname");
$code = $this->getResponseCode($response[0]);
return $code == 250; // 250 表示请求的文件操作完成
}
public function rename($from, $to) {
$response = $this->sendCommand("RNFR $from");
$code = $this->getResponseCode($response[0]);
if ($code != 350) {
return false; // 350 表示需要进一步的信息
}
$response = $this->sendCommand("RNTO $to");
$code = $this->getResponseCode($response[0]);
return $code == 250;
}
public function setPassiveMode($passive = true) {
$command = $passive ? "PASV" : "PORT";
$response = $this->sendCommand($command);
$code = $this->getResponseCode($response[0]);
return $passive ? $code == 227 : $code == 200;
}
public function enableDebug($debug = true) {
$this->debug = $debug;
}
public function close() {
$this->sendCommand("QUIT");
ftp_close($this->conn);
$this->log("连接已关闭");
}
private function getResponseCode($responseLine) {
// FTP响应以3位数字代码开头
if (strlen($responseLine) >= 3 && is_numeric(substr($responseLine, 0, 3))) {
return intval(substr($responseLine, 0, 3));
}
return 0;
}
private function parseFTPTimestamp($timestamp) {
// 格式: YYYYMMDDHHMMSS 或 YYYYMMDDHHMMSS.sss
$timestamp = substr($timestamp, 0, 14);
if (strlen($timestamp) == 14) {
$year = substr($timestamp, 0, 4);
$month = substr($timestamp, 4, 2);
$day = substr($timestamp, 6, 2);
$hour = substr($timestamp, 8, 2);
$minute = substr($timestamp, 10, 2);
$second = substr($timestamp, 12, 2);
return mktime($hour, $minute, $second, $month, $day, $year);
}
return 0;
}
private function log($message) {
if ($this->debug) {
echo "[FTP] " . date('Y-m-d H:i:s') . " - $message\n";
}
}
public function __destruct() {
if ($this->conn) {
$this->close();
}
}
}
// 使用示例
try {
$ftp = new RawFTPClient('localhost');
$ftp->enableDebug(true);
// 登录
$ftp->login('user', 'pass');
// 获取系统信息
echo "服务器类型: " . $ftp->getSystemType() . "\n";
// 获取详细列表
$list = $ftp->getDetailedList();
echo "目录内容:\n";
foreach ($list as $item) {
echo " $item\n";
}
// 获取文件大小
$size = $ftp->getFileSize('test.txt');
echo "文件大小: " . ($size > 0 ? "$size 字节" : "未知") . "\n";
// 设置被动模式
$ftp->setPassiveMode(true);
// 创建目录
if ($ftp->createDirectory('new_folder')) {
echo "目录创建成功\n";
}
// 关闭连接
$ftp->close();
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}
?>
<?php
class FTPScanner {
private $conn;
public function connect($server, $port = 21) {
$this->conn = @ftp_connect($server, $port, 5);
return $this->conn !== false;
}
public function scanServer() {
if (!$this->conn) {
return ['error' => '未建立连接'];
}
$info = [
'server' => '',
'features' => [],
'system' => '',
'welcome' => '',
'supported_commands' => []
];
try {
// 获取欢迎信息(通常发送空命令或等待初始响应)
$response = ftp_raw($this->conn, "NOOP"); // NOOP 是空操作命令
$info['welcome'] = implode("\n", $response);
// 获取系统类型
$response = ftp_raw($this->conn, "SYST");
if (count($response) > 0) {
$info['system'] = $response[0];
}
// 获取服务器特性(FEAT 命令)
$response = ftp_raw($this->conn, "FEAT");
if (count($response) > 0) {
// 移除第一行(211-Features:)和最后一行(211 End)
array_shift($response); // 移除 "211-Features:"
array_pop($response); // 移除 "211 End"
foreach ($response as $line) {
$line = trim($line);
if (!empty($line)) {
$info['features'][] = $line;
}
}
}
// 测试支持的FTP命令
$commands = [
'ABOR', 'ACCT', 'ALLO', 'APPE', 'CDUP', 'CWD', 'DELE', 'HELP',
'LIST', 'MKD', 'MODE', 'NLST', 'NOOP', 'PASS', 'PASV', 'PORT',
'PWD', 'QUIT', 'REIN', 'REST', 'RETR', 'RMD', 'RNFR', 'RNTO',
'SITE', 'SIZE', 'STAT', 'STOR', 'STOU', 'STRU', 'SYST', 'TYPE',
'USER', 'XCUP', 'XMKD', 'XPWD', 'XRMD'
];
foreach ($commands as $cmd) {
$response = ftp_raw($this->conn, $cmd);
if (count($response) > 0) {
$code = intval(substr($response[0], 0, 3));
// 500 或 502 表示命令不被识别
if ($code != 500 && $code != 502) {
$info['supported_commands'][$cmd] = [
'response' => $response[0],
'code' => $code
];
}
}
}
// 获取STAT信息(服务器状态)
$response = ftp_raw($this->conn, "STAT");
if (count($response) > 0) {
$info['status'] = implode("\n", $response);
}
// 尝试获取HELP信息
$response = ftp_raw($this->conn, "HELP");
if (count($response) > 0) {
$info['help'] = implode("\n", $response);
}
} catch (Exception $e) {
$info['error'] = $e->getMessage();
}
return $info;
}
public function testAnonymousAccess() {
// 测试匿名登录
$response = ftp_raw($this->conn, "USER anonymous");
if (count($response) > 0 && strpos($response[0], '331') === 0) {
$response = ftp_raw($this->conn, "PASS anonymous@example.com");
if (count($response) > 0 && strpos($response[0], '230') === 0) {
return [
'success' => true,
'message' => '匿名登录成功',
'response' => $response[0]
];
}
}
return [
'success' => false,
'message' => '匿名登录失败'
];
}
public function bruteForceTest($usernames, $passwords) {
$results = [];
foreach ($usernames as $user) {
foreach ($passwords as $pass) {
$response = ftp_raw($this->conn, "USER $user");
if (count($response) > 0 && strpos($response[0], '331') === 0) {
$response = ftp_raw($this->conn, "PASS $pass");
if (count($response) > 0 && strpos($response[0], '230') === 0) {
$results[] = [
'username' => $user,
'password' => $pass,
'success' => true
];
// 找到后退出当前用户测试
break;
}
}
}
}
return $results;
}
public function close() {
if ($this->conn) {
@ftp_raw($this->conn, "QUIT");
@ftp_close($this->conn);
}
}
public function __destruct() {
$this->close();
}
}
// 使用示例
$scanner = new FTPScanner();
if ($scanner->connect('localhost')) {
echo "=== FTP服务器扫描 ===\n\n";
// 扫描服务器信息
$info = $scanner->scanServer();
echo "1. 欢迎信息:\n";
echo $info['welcome'] . "\n\n";
echo "2. 系统类型:\n";
echo $info['system'] . "\n\n";
echo "3. 服务器特性 (" . count($info['features']) . " 个):\n";
foreach ($info['features'] as $feature) {
echo " - $feature\n";
}
echo "\n";
echo "4. 支持的命令 (" . count($info['supported_commands']) . " 个):\n";
foreach ($info['supported_commands'] as $cmd => $data) {
echo " - $cmd (响应代码: {$data['code']})\n";
}
echo "\n5. 匿名登录测试:\n";
$anon = $scanner->testAnonymousAccess();
echo $anon['message'] . "\n";
// 注意:暴力破解仅用于授权的安全测试
// echo "\n6. 简单凭证测试:\n";
// $users = ['admin', 'user', 'test', 'ftp'];
// $passwords = ['password', '123456', 'admin', 'test'];
// $results = $scanner->bruteForceTest($users, $passwords);
// foreach ($results as $result) {
// if ($result['success']) {
// echo "找到有效凭证: {$result['username']}/{$result['password']}\n";
// }
// }
$scanner->close();
} else {
echo "无法连接到FTP服务器\n";
}
?>
ftp_raw() 是一个低级函数,需要了解FTP协议和命令语法PASS)可能以明文传输密码,存在安全风险ftp_raw()前,先用FEAT命令检查服务器支持的功能ftp_ssl_connect())保护敏感命令在以下情况下考虑使用ftp_raw():
SITE命令示例:使用SITE CHMOD 755 filename设置文件权限(如果服务器支持)。
FTP响应代码由3位数字组成,每位数字有特定含义:
| 数字位置 | 含义 | 常见值 |
|---|---|---|
| 第一位 | 响应类型 | 1-正面初步响应 2-正面完成响应 3-中间正面响应 4-暂时负面完成响应 5-永久负面完成响应 |
| 第二位 | 响应类别 | 0-语法 1-信息 2-连接 3-认证和计费 4-未指定 5-文件系统 |
| 第三位 | 具体响应 | 更详细的响应信息 |
// 解析响应代码示例
function parseFTPResponse($responseLine) {
if (strlen($responseLine) >= 3) {
$code = intval(substr($responseLine, 0, 3));
$message = trim(substr($responseLine, 4));
return [
'code' => $code,
'message' => $message,
'type' => floor($code / 100), // 第一位
'category' => floor(($code % 100) / 10), // 第二位
'success' => $code < 400 // 1xx, 2xx, 3xx 表示成功
];
}
return null;
}
使用ftp_raw()可能带来以下安全风险:
| 风险类型 | 描述 | 防范措施 |
|---|---|---|
| 命令注入 | 用户输入可能包含恶意FTP命令 | 严格验证和过滤所有输入,避免直接拼接命令 |
| 信息泄露 | 敏感信息(如密码)可能以明文传输 | 使用SSL/TLS连接(ftp_ssl_connect()) |
| 服务器攻击 | 恶意命令可能破坏服务器或数据 | 限制可执行的命令范围,使用最小权限账户 |
| 拒绝服务 | 频繁发送命令可能导致服务器过载 | 实现速率限制和适当的延迟 |
| 协议漏洞 | 利用FTP协议本身的漏洞 | 保持FTP客户端和服务器更新 |
// 安全的命令构建示例
function buildSafeCommand($baseCommand, $params = []) {
// 白名单验证允许的命令
$allowedCommands = ['LIST', 'NLST', 'PWD', 'SYST', 'FEAT'];
if (!in_array($baseCommand, $allowedCommands)) {
throw new Exception("不允许的命令: $baseCommand");
}
// 对参数进行安全过滤
$safeParams = array_map('escapeshellarg', $params);
return $baseCommand . (!empty($safeParams) ? ' ' . implode(' ', $safeParams) : '');
}
ftp_connect() - 建立FTP连接ftp_ssl_connect() - 建立SSL-FTP连接ftp_login() - 登录FTP服务器ftp_rawlist() - 返回目录的详细列表ftp_exec() - 在FTP服务器上执行命令(如果支持)ftp_systype() - 返回远程FTP服务器的系统类型ftp_set_option() - 设置各种FTP运行时选项