编程基础 | PHP代码审记(上 )
字数 2759 2025-08-18 11:39:26

PHP代码审计与安全编程教学文档

1. PHP基础知识

1.1 全局变量

1.1.1 $GLOBALS

  • PHP内置变量,自动获取当前页面中所有变量的内容
  • 包含所有全局变量的关联数组

1.1.2 $_SERVER

  • 包含头信息、路径、脚本位置等信息的数组
  • 由Web服务器创建,不同服务器提供的信息可能不同

示例代码:

echo '<table>';
foreach($_SERVER as $key => $value) {
    echo '<tr>';
    echo '<td>'.$key.'</td><td>'.$value.'</td>';
    echo '</tr>';
}
echo '</table>';

1.1.3 $_FILES

  • 获取上传文件的信息

示例代码:

if ($_FILES["file"]["error"] > 0) {
    echo "错误: ".$_FILES["file"]["error"];
} else {
    echo "上传文件名: " . $_FILES["file"]["name"] . "<br>";
    echo "文件类型: " . $_FILES["file"]["type"] . "<br>";
    echo "文件大小: " . ($_FILES["file"]["size"] / 1024) . "kB<br>";
    echo "文件临时存储的位置: " . $_FILES["file"]["tmp_name"];
    
    if (file_exists("upload/" . $_FILES["file"]["name"])) {
        echo $_FILES["file"]["name"] . " 文件已经存在。";
    } else {
        move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]);
        echo "文件存储在: " . "upload/" . $_FILES["file"]["name"];
    }
}

1.1.4 $_GET

  • 通过URL方式传递数据
  • 传递方式:URL地址?参数1=值1&参数2=值2

示例代码:

<form action="#" method="get">
    USERNAME:<input type="text" name="username"><br>
    PASSWORD:<input type="password" name="password"><br>
    <input type="submit" name="submit">
</form>

1.1.5 $_POST

  • 通过HTTP协议的POST方式传递数据
  • 地址栏不可见传输内容

示例代码:

<form action="#" method="post">
    USERNAME:<input type="text" name="username"><br>
    PASSWORD:<input type="password" name="password"><br>
    <input type="submit" name="submit">
</form>

1.1.6 $_REQUEST

  • 默认可以接受GET、POST和COOKIE三种方式提交的数据
  • 如果设置只接受GET方式,POST提交会找不到值

1.2 正则表达式

1.2.1 正则表达式简介

  • 用于描述字符串匹配规则的代码
  • 比通配符更精确,但更复杂

1.2.2 正则表达式相关函数

  1. preg_match - 筛选到一个结果即停止
  2. preg_match_all - 筛选所有结果保存到数组
  3. preg_replace - 筛选并替换内容
  4. preg_split - 根据规则拆分字符串

1.2.3 正则表达式应用场景

  • 用户注册验证
  • 内容采集
  • 数据过滤

1.2.4 表达式语法

  • 定界符:表示规则的边界,通常使用/

  • 元字符

    • \w:字母、数字、下划线
    • \W:非字母、数字、下划线
    • \d:数字(0-9)
    • \D:非数字
    • \s:空格
    • \S:非空格
    • [a-z]:a-z任意字符
    • [^hel]:非h、e、l的任意字符
    • .:除换行符外的任意字符
    • |:或
  • 量词

    • {m}:固定m个
    • {n,m}:最少n个,最多m个
    • {n,}:最少n个
    • *:0个或多个
    • +:至少1个
    • ?:0个或1个
  • 模式修正符

    • i:忽略大小写
    • s:万能点模式
    • U:贪婪模式改为懒惰模式

1.2.5 正则实例

  1. 用户名验证(6-30位,字母开头,字母数字下划线组合)
$reg = '/^[a-zA-Z]\w{5,29}$/';
$res = 'ehllo1123';
preg_match($reg, $res, $match);
if($match) {
    echo "success";
} else {
    echo "fail";
}
  1. 密码验证(6-20位,字母、数字或符号)
  • 纯字母/数字:提示密码太简单
  • 字母+数字:提示比较安全
  • 字母+数字+特殊符号:提示非常安全
  1. 正则漏洞示例
$ip = '1.1.1.1 abcd'; // 可以绕过
if(!preg_match("/(\d+)\.(\d+)\.(\d+)\.(\d+)/", $ip)) {
    die('error');
} else {
    echo('key...');
}

问题:没有限制开始和结束,导致输入危险字符绕过

1.3 Socket函数

1.3.1 Socket介绍

  • 应用层与TCP/IP协议族通信的中间抽象层
  • 两种方案:
    1. 基于内核的socket:fsockopen, pfsockopen
    2. PHP扩展模块:socket_create, socket_bind, socket_connect, socket_accept

1.3.2 关键函数

  1. socket_create($net, $stream, $protocol) - 创建socket套接字
  2. socket_connect($socket, $ip, $port) - 连接套接字
  3. socket_bind($socket, $ip, $port) - 绑定套接字
  4. socket_listen($socket, $backlog) - 监听套接字
  5. socket_accept($socket) - 接收套接字资源信息
  6. socket_read($socket, $length) - 读取套接字资源
  7. socket_write($socket, $msg, $strlen) - 写入数据到套接字
  8. socket_close($socket) - 关闭套接字

1.3.3 服务端示例代码

// Server_socket.php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($socket, '127.0.0.1', 8888);
socket_listen($socket);
$accept = socket_accept($socket);
$string = socket_read($accept, 1024);
echo $string;
socket_write($accept, 'Server has received your information');
socket_close($accept);
socket_close($socket);

1.3.4 客户端示例代码

// Client_server.php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array("sec"=>1, "usec"=>0));
socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, array("sec"=>6, "usec"=>0));

if(socket_connect($socket, '127.0.0.1', 8888) == false) {
    echo 'connect fail massege:'.socket_strerror(socket_last_error());
} else {
    $message = 'l love you 我爱你 socket';
    $message = mb_convert_encoding($message, 'GBK', 'UTF-8');
    
    if(socket_write($socket, $message, strlen($message)) == false) {
        echo 'fail to write'.socket_strerror(socket_last_error());
    } else {
        echo 'client write success'.PHP_EOL;
        while($callback = socket_read($socket, 1024)) {
            echo 'server return message is:'.PHP_EOL.$callback;
        }
    }
}
socket_close($socket);

1.4 fsockopen函数

1.4.1 介绍

  • 打开一个网络连接或Unix套接字连接
  • 语法:resource fsockopen (string $hostname [, int $port = -1 [, int &$errno [, string &$errstr [, float $timeout = ini_get("default_socket_timeout")]]]])

1.4.2 参数

  1. hostname - 主机名
  2. port - 端口号(-1表示不使用端口)
  3. errno - 错误号
  4. errstr - 错误信息
  5. timeout - 连接时限(秒)

1.4.3 示例代码

$fp = fsockopen("127.0.0.1", 80, $errno, $errstr, 30);
if (!$fp) {
    echo "$errstr ($errno)<br />\n";
} else {
    fputs($fp, "GET / HTTP/1.0\r\nHost: 127.0.0.1\r\n\r\n");
    while (!feof($fp)) {
        echo fgets($fp, 128);
    }
    fclose($fp);
}

1.4.4 端口扫描器实现

$ip = $_POST['ip'];
if (ip2long($ip)) {
    $_ip = explode(".", $ip);
    foreach ($_ip as $key => $value) {
        if ($value < 0 || $value > 255) {
            die("Invalid IP");
        }
    }
}

$port = array(21, 23, 25, 79, 80, 110, 135, 137, 138, 139, 143, 443, 445, 1433, 3306);
$msg = array('Ftp', 'Telnet', 'Smtp', 'Finger', 'Http', 'Pop3', 'Location Service', 
             'Netbios-NS', 'Netbios-DGM', 'Netbios-SSN', 'IMAP', 'Https', 'Microsoft-DS', 
             'MSSQL', 'MYSQL', 'Terminal Services');

foreach ($port as $key => $value) {
    echo '<tr>';
    echo '<td>'.$key.'</td>';
    echo '<td>'.$value.'</td>';
    echo '<td>'.$msg[$key].'</td>';
    $fp = @fsockopen($ip, $value, $errno, $errstr, 1);
    $res = $fp ? '<span>Open</span>' : '<span>Close</span>';
    echo '<td>'.$res.'</td>';
}

1.4.5 常见安全端口

  • 21: FTP
  • 22: SSH
  • 23: Telnet
  • 25: SMTP
  • 80: HTTP
  • 110: POP3
  • 135-139: NetBIOS
  • 143: IMAP
  • 443: HTTPS
  • 445: Microsoft-DS
  • 1433: MSSQL
  • 3306: MySQL
  • 3389: 远程桌面
  • 6379: Redis
  • 8080: Tomcat
  • 27017: MongoDB

2. 安全编程实践

2.1 输入验证

  • 对所有用户输入进行严格验证
  • 使用白名单而非黑名单策略
  • 对特殊字符进行转义或过滤

2.2 文件上传安全

  • 检查文件类型(不要依赖客户端提供的信息)
  • 限制文件大小
  • 重命名上传文件
  • 存储在非web可访问目录
  • 设置适当权限

2.3 SQL注入防护

  • 使用预处理语句(PDO或MySQLi)
  • 避免直接拼接SQL语句
  • 对特殊字符进行转义

2.4 XSS防护

  • 对所有输出进行HTML实体编码
  • 使用Content Security Policy (CSP)
  • 设置HttpOnly标志的cookie

2.5 CSRF防护

  • 使用CSRF令牌
  • 检查Referer头
  • 敏感操作使用POST而非GET

2.6 会话安全

  • 使用安全的会话配置
  • 会话ID足够随机
  • 设置适当的会话超时
  • 用户登出时销毁会话

2.7 错误处理

  • 生产环境关闭错误显示
  • 记录错误到日志文件
  • 不向用户暴露敏感信息

3. 代码审计要点

3.1 常见漏洞点

  1. 未过滤的用户输入
  2. 直接拼接的SQL查询
  3. 不安全的文件操作
  4. 不正确的权限设置
  5. 硬编码的敏感信息
  6. 不安全的反序列化
  7. 不正确的加密实现
  8. 不安全的第三方组件

3.2 审计工具

  1. 静态代码分析工具
  2. 动态分析工具
  3. 交互式应用安全测试(IAST)
  4. 手动代码审查

3.3 审计流程

  1. 了解应用架构
  2. 识别入口点
  3. 跟踪数据流
  4. 识别信任边界
  5. 验证安全控制
  6. 报告发现的问题

4. 总结

本教学文档涵盖了PHP安全编程的基础知识,包括全局变量、正则表达式、Socket编程和fsockopen函数的使用。同时提供了安全编程的最佳实践和代码审计的关键要点。通过掌握这些知识,开发者可以编写更安全的PHP代码,审计人员可以更有效地发现潜在的安全漏洞。

PHP代码审计与安全编程教学文档 1. PHP基础知识 1.1 全局变量 1.1.1 $GLOBALS PHP内置变量,自动获取当前页面中所有变量的内容 包含所有全局变量的关联数组 1.1.2 $_ SERVER 包含头信息、路径、脚本位置等信息的数组 由Web服务器创建,不同服务器提供的信息可能不同 示例代码: 1.1.3 $_ FILES 获取上传文件的信息 示例代码: 1.1.4 $_ GET 通过URL方式传递数据 传递方式:URL地址?参数1=值1&参数2=值2 示例代码: 1.1.5 $_ POST 通过HTTP协议的POST方式传递数据 地址栏不可见传输内容 示例代码: 1.1.6 $_ REQUEST 默认可以接受GET、POST和COOKIE三种方式提交的数据 如果设置只接受GET方式,POST提交会找不到值 1.2 正则表达式 1.2.1 正则表达式简介 用于描述字符串匹配规则的代码 比通配符更精确,但更复杂 1.2.2 正则表达式相关函数 preg_match - 筛选到一个结果即停止 preg_match_all - 筛选所有结果保存到数组 preg_replace - 筛选并替换内容 preg_split - 根据规则拆分字符串 1.2.3 正则表达式应用场景 用户注册验证 内容采集 数据过滤 1.2.4 表达式语法 定界符 :表示规则的边界,通常使用 / 元字符 : \w :字母、数字、下划线 \W :非字母、数字、下划线 \d :数字(0-9) \D :非数字 \s :空格 \S :非空格 [a-z] :a-z任意字符 [^hel] :非h、e、l的任意字符 . :除换行符外的任意字符 | :或 量词 : {m} :固定m个 {n,m} :最少n个,最多m个 {n,} :最少n个 * :0个或多个 + :至少1个 ? :0个或1个 模式修正符 : i :忽略大小写 s :万能点模式 U :贪婪模式改为懒惰模式 1.2.5 正则实例 用户名验证(6-30位,字母开头,字母数字下划线组合) 密码验证(6-20位,字母、数字或符号) 纯字母/数字:提示密码太简单 字母+数字:提示比较安全 字母+数字+特殊符号:提示非常安全 正则漏洞示例 问题:没有限制开始和结束,导致输入危险字符绕过 1.3 Socket函数 1.3.1 Socket介绍 应用层与TCP/IP协议族通信的中间抽象层 两种方案: 基于内核的socket: fsockopen , pfsockopen PHP扩展模块: socket_create , socket_bind , socket_connect , socket_accept 1.3.2 关键函数 socket_create($net, $stream, $protocol) - 创建socket套接字 socket_connect($socket, $ip, $port) - 连接套接字 socket_bind($socket, $ip, $port) - 绑定套接字 socket_listen($socket, $backlog) - 监听套接字 socket_accept($socket) - 接收套接字资源信息 socket_read($socket, $length) - 读取套接字资源 socket_write($socket, $msg, $strlen) - 写入数据到套接字 socket_close($socket) - 关闭套接字 1.3.3 服务端示例代码 1.3.4 客户端示例代码 1.4 fsockopen函数 1.4.1 介绍 打开一个网络连接或Unix套接字连接 语法: resource fsockopen (string $hostname [, int $port = -1 [, int &$errno [, string &$errstr [, float $timeout = ini_get("default_socket_timeout")]]]]) 1.4.2 参数 hostname - 主机名 port - 端口号(-1表示不使用端口) errno - 错误号 errstr - 错误信息 timeout - 连接时限(秒) 1.4.3 示例代码 1.4.4 端口扫描器实现 1.4.5 常见安全端口 21: FTP 22: SSH 23: Telnet 25: SMTP 80: HTTP 110: POP3 135-139: NetBIOS 143: IMAP 443: HTTPS 445: Microsoft-DS 1433: MSSQL 3306: MySQL 3389: 远程桌面 6379: Redis 8080: Tomcat 27017: MongoDB 2. 安全编程实践 2.1 输入验证 对所有用户输入进行严格验证 使用白名单而非黑名单策略 对特殊字符进行转义或过滤 2.2 文件上传安全 检查文件类型(不要依赖客户端提供的信息) 限制文件大小 重命名上传文件 存储在非web可访问目录 设置适当权限 2.3 SQL注入防护 使用预处理语句(PDO或MySQLi) 避免直接拼接SQL语句 对特殊字符进行转义 2.4 XSS防护 对所有输出进行HTML实体编码 使用Content Security Policy (CSP) 设置HttpOnly标志的cookie 2.5 CSRF防护 使用CSRF令牌 检查Referer头 敏感操作使用POST而非GET 2.6 会话安全 使用安全的会话配置 会话ID足够随机 设置适当的会话超时 用户登出时销毁会话 2.7 错误处理 生产环境关闭错误显示 记录错误到日志文件 不向用户暴露敏感信息 3. 代码审计要点 3.1 常见漏洞点 未过滤的用户输入 直接拼接的SQL查询 不安全的文件操作 不正确的权限设置 硬编码的敏感信息 不安全的反序列化 不正确的加密实现 不安全的第三方组件 3.2 审计工具 静态代码分析工具 动态分析工具 交互式应用安全测试(IAST) 手动代码审查 3.3 审计流程 了解应用架构 识别入口点 跟踪数据流 识别信任边界 验证安全控制 报告发现的问题 4. 总结 本教学文档涵盖了PHP安全编程的基础知识,包括全局变量、正则表达式、Socket编程和fsockopen函数的使用。同时提供了安全编程的最佳实践和代码审计的关键要点。通过掌握这些知识,开发者可以编写更安全的PHP代码,审计人员可以更有效地发现潜在的安全漏洞。