跳转至

Php基础知识

写在前面

本文主要介绍《php代码审计入门》所需要的php语言基础知识,但由于php语言经过多年不断迭代和发展的过程中,各个版本之间存在差异,本文尽可能讲诉一些php无版本差异的基础知识,样例代码以php7.1版本进行测试,如在编写php或在挖掘漏洞过程中出现任何问题,请及时查看php官方文档和自己的php版本信息,确认是否因php版本差异导致的。 除版本还有php配置项,很多诡异问题,可能会因为非默认配置项导致的。本文没有特殊说明,都是以php安装后,默认配置项进行讲解

数据处理流程

用户输入 -> 网络传输 -> 安全防护 -> web负载 -> web服务 -> db/redis/第三方服务

接收用户参数

单次请求响应流程: php首先需要接收用户提交的参数,将参数变成变量。参数具体指从浏览器/app端发出,用抓包工具能够抓到的完整请求包。 php中接收参数使用预定义变量

变量

代码审计主要是跟踪变量的执行过程,因此需要首先找到变量。

PHP 中的变量用一个美元符号后面跟变量名来表示。变量名是区分大小写的  

比如

<?php  $id = 1; ?> 

定义一个变量 并且进行赋值为 1

变量有其作用范围,就是作用域

<?php
$a = 1; /* 全局变量 */

function Test()
{   $a = 2;
    echo $a; /* 函数内部变量 */
}
echo $a;
echo '<br>';
Test();
?>

_GET

$_GET — HTTP GET 变量
通过 URL 参数(又叫 query string)传递给当前脚本的变量的数组。 注意:该数组不仅仅对 method 为 GET 的请求生效,而是会针对所有带 query string 的请求。

可以使用如下代码显示$_GET数组信息

<?php  
var_dump($_GET);  
?> 

当请求是POST,如果URL中有参数,同样可以使用$_GET取参

_POST

$_POST — HTTP POST 变量
当 HTTP POST 请求的 Content-Type 是 application/x-www-form-urlencoded 或 multipart/form-data 时,会将变量以关联数组形式传入当前脚本。 可以使用如下代码显示$_POST数组信息

<?php  
var_dump($_POST);  
?> 

$_COOKIE — HTTP Cookies
通过 HTTP Cookies 方式传递给当前脚本的变量的数组。
需要注意:

点 (.) 和空格 ( ) 在 cookie 名字里,将会被替换成下划线 (_)

如果不确定cooike名字 可以使用下面代码查看所有cookie名字
可以使用如下代码显示$_POST数组信息

<?php  
var_dump($_COOKIE);  
?> 

_REQUEST

$_REQUEST — HTTP Request 变量 默认情况下包含了 $_GET,$_POST 和 $_COOKIE 的数组。

由于 $_REQUEST 中的变量通过 GET,POST 和 COOKIE 输入机制传递给脚本文件,因此可以被远程用户篡改而并不可信。这个数组的项目及其顺序依赖于 PHP 的 request_order 和 variables_order 指令的配置。

_SESSION

$_SESSION — Session 变量
当前脚本可用 SESSION 变量的数组。更多关于如何使用的信息,参见 Session 函数 文档。 session在使用上与GET、POST、COOKIE、REQUEST基本相同,但session和cookie的最大区别是,session中保存的信息最终存储在服务器上,cookie是完全保存在客户端浏览器中,因此存在cookie可被篡改,而session无法篡改的说法。session使用sessionid对每个用户进行标识,并且sessionid是通过cookie保存到客户端,因此如果sessionid生成算法存在漏洞-可预测他人的sessionid,或者通过xss,session固化,网络流量劫持获得cookie,就可以盗取任何人的session

_SERVER

$_SERVER — 服务器和执行环境信息 $_SERVER 是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组。这个数组中的项目由 Web 服务器创建。不能保证每个服务器都提供全部项目;服务器可能会忽略一些,或者提供一些没有在这里列举出来的项目。

如果想知道$_SERVER数组都包括哪些信息,可以直接使用

<?php  
var_dump($_SERVER);  
?> 

请求后结果如下:


array(34) {
  ["USER"]=>
  string(3) "www"
  ["HOME"]=>
  string(9) "/home/www"
  ["HTTP_COOKIE"]=>
  string(1309) "request_token=8w8nqsSysyPDdEqQ0cmQviS5kQoke6I5uaO5XIYLrDFyAiIQ; Path=/www/wwwroot/safe/php"
  ["HTTP_ACCEPT_LANGUAGE"]=>
  string(47) "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"
  ["HTTP_ACCEPT_ENCODING"]=>
  string(13) "gzip, deflate"
  ["HTTP_ACCEPT"]=>
  string(124) "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
  ["HTTP_USER_AGENT"]=>
  string(134) "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102"
  ["HTTP_UPGRADE_INSECURE_REQUESTS"]=>
  string(1) "1"
  ["HTTP_CONNECTION"]=>
  string(10) "keep-alive"
  ["HTTP_HOST"]=>
  string(14) "test.baidu.com"
  ["PATH_INFO"]=>
  string(0) ""
  ["REDIRECT_STATUS"]=>
  string(3) "200"
  ["SERVER_NAME"]=>
  string(13) "www.baidu.com"
  ["SERVER_PORT"]=>
  string(2) "80"
  ["SERVER_ADDR"]=>
  string(13) "143.240.103.184"
  ["REMOTE_PORT"]=>
  string(5) "36915"
  ["REMOTE_ADDR"]=>
  string(14) "121.133.201.203"
  ["SERVER_SOFTWARE"]=>
  string(12) "nginx/1.20.2"
  ["GATEWAY_INTERFACE"]=>
  string(7) "CGI/1.1"
  ["REQUEST_SCHEME"]=>
  string(4) "http"
  ["SERVER_PROTOCOL"]=>
  string(8) "HTTP/1.1"
  ["DOCUMENT_ROOT"]=>
  string(41) "/www/wwwroot/safe"
  ["DOCUMENT_URI"]=>
  string(15) "/php/server.php"
  ["REQUEST_URI"]=>
  string(15) "/php/server.php"
  ["SCRIPT_NAME"]=>
  string(15) "/php/server.php"
  ["CONTENT_LENGTH"]=>
  string(0) ""
  ["CONTENT_TYPE"]=>
  string(0) ""
  ["REQUEST_METHOD"]=>
  string(3) "GET"
  ["QUERY_STRING"]=>
  string(0) ""
  ["SCRIPT_FILENAME"]=>
  string(56) "/www/wwwroot/safe/php/server.php"
  ["FCGI_ROLE"]=>
  string(9) "RESPONDER"
  ["PHP_SELF"]=>
  string(15) "/php/server.php"
  ["REQUEST_TIME_FLOAT"]=>
  float(1661308412.4399)
  ["REQUEST_TIME"]=>
  int(1661308412)
}

常用参数解读

REQUEST_METHOD  访问页面使用的请求方法;例如,“GET”, “HEAD”,“POST”,“PUT”    
SERVER_ADDR 当前运行脚本所在的服务器的 IP 地址 
PHP_SELF  当前执行脚本的文件名,与 document root 有关  
QUERY_STRING  query string(查询字符串),如果有的话,通过它进行页面访问。
DOCUMENT_ROOT  当前运行脚本所在的文档根目录。在服务器配置文件中定义。 
HTTP_HOST 当前请求头中 Host: 项的内容,如果存在的话。
HTTP_REFERER 引导用户代理到当前页的前一页的地址(如果存在)。由 user agent 设置决定。并不是所有的用户代理都会设置该项,有的还提供了修改 HTTP_REFERER 的功能。简言之,该值并不可信。
HTTP_USER_AGENT 当前请求头中 User-Agent: 项的内容,如果存在的话。该字符串表明了访问该页面的用户代理的信息。一个典型的例子是:Mozilla/4.5 [en] (X11; U; Linux 2.2.9 i586)。除此之外,你可以通过 get_browser() 来使用该值,从而定制页面输出以便适应用户代理的性能。 

_ENV

$_ENV — 环境变量
通过环境方式传递给当前脚本的变量的数组。

这些变量被从 PHP 解析器的运行环境导入到 PHP 的全局命名空间。很多是由支持 PHP 运行的 Shell 提供的,并且不同的系统很可能运行着不同种类的 Shell,所以不可能有一份确定的列表。请查看你的 Shell 文档来获取定义的环境变量列表。

其他环境变量包含了 CGI 变量,而不管 PHP 是以服务器模块还是 CGI 处理器的方式运行。

如果不确定有哪些ENV信息,可以使用如下代码查看

<?php  
var_dump($_ENV);  
?> 

处理输入函数

上一小节介绍了PHP接收用户的参数函数,做代码审计的时候一般需要跟踪用户的参数,看研发同学是怎样处理输入的参数,然后确认是否出现相关漏洞,即寻找从用户输入到危险函数的执行路径
基础知识,php中使用function-exists判断函数是否存在get_defined_functions显示所有已经定义的函数数组

直接输出

将接收到用户的输入,直接返回到客户端显示,就是常见的XSS漏洞,php常见的输出函数是

echo  
print 
var_dump

更多字符串操作函数参考文档

典型反射xss漏洞

<?php 
echo $_GET["name"]; 
?>

测试请求

/php/xss.php?name=<script>alert(1)</script>

最常见的修复方案使用php自带的 htmlspecialchars 函数对用户输入进行编码输出

<?php 
echo htmlspecialchars($_GET["name"]); 
?>

但这种方案在富文本等需要特殊字符的场景下,会导致功能异常
在特定的一些输出点下,还可能继续存在XSS漏洞,你能想到吗?
所以对于解决方案,有时需要讨论具体的修复场景,选择最佳方案

执行命令

php本身可以使用system等函数执行操作系统提供的命令,来完成某些特定功能,但如果研发同学没有对用户输入进行任何限制,将会导致黑客通过该功能,执行任意命令,出现命令注入漏洞
典型命令注入漏洞

<?php  
system($_GET['cmd']);
?>

直接构成命令,即可触发漏洞

/php/cmd.php?cmd=whoami

php常用的执行系统命令函数

system 
exec 
passthru
shell_exec 
popen
proc_open
pcntl_exec 

更多函数参考 除此之外,反引号也可以执行命令

<?php 
$p = $_GET['p'];
echo `$p`;

?>

常见的修复方案使用php自带的escapeshellarg和 escapeshellcmd 函数进行转义

escapeshellarg — 把字符串转码为可以在 shell 命令里使用的参数
escapeshellcmd — shell 元字符转义

怎样使用这两个函数实现安全开发?是否使用了这两个函数就没有漏洞? 等待你的实践回复

代码执行

代码执行是可以执行任意php代码的漏洞,如下代码样例

<?php 
ev al($_GET['cod']);
?>

测试样例

/php/eval.php?code=phpinfo();

类似的函数有

eval 
assert
preg_replace

这个漏洞修复相对比较复杂,建议尽量不用类似的危险函数,否则就对输入的数据进行白名单校验

操作数据库

SQL注入是最常见的安全漏洞,曾经常年稳居OWASP Top 1
样例代码如下 登录功能的 SQL注入点

// 获取前端POST过来的数据
$user = $_POST["user"];
$pwd = $_POST["pwd"];
// 连接数据库
include '../../db_config/db_config.php';
// 创建连接
$conn = new mysqli($db_url, $db_user, $db_pwd, $db_name);
$check_user = "SELECT * FROM huoma_user WHERE user='$user' AND pwd='$pwd'";
$result_user = $conn->query($check_user);
// 验证结果
if ($result_user->num_rows > 0) {
}

接收用户输入的用户名和密码,然后拼接成SQL语句,查询数据库
修复方案一般使用PDO进行数据库查询,或者对用户输入的参数进行安全过滤

操作文件

文件主要包括读、写、删除等常见操作
对于文件类漏洞最主要需要关注操作文件的路径是否可控,如果能输入..进行路径回退,很多时候都会导致相关安全漏洞

文件包含样例

<?php 
include $_GET["url"];
?>

操作XML

调试环境准备

进阶漏洞

编写第一个php程序

常见安全函数汇总

htmlspecialchars  

参考文献

"php语言是世界上最好的语言",得益于其完整的开发文档,遇到不认识的函数都可以查找php中文文档,这是最全的一本php宝典,适合于php的开发、测试、运维、安全等工作人员。 希望任何php代码审计的初学者都养成遇到问题首先查官方文档的好习惯。

Back to top