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
$_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代码审计的初学者都养成遇到问题首先查官方文档的好习惯。