CTFWeb篇07-php危险函数与绕过
非常非常重要
- PHP通过一些函数包含的文件在网页中是不可见的,即使查看源代码也看不到该文件
- PHP是后端语言,前端是无法查看的。即使将PHP源代码转码成其他形式,也无法查看。
- 前端看到的是PHP代码在服务器端执行,然后生成HTML格式的页面,发送到客户端进行显示。
- 因此,无论怎样转换,都无法查看原始的PHP源代码。
- 但是,如果浏览器无法执行该PHP文件,那么PHP代码将不会被执行,而是会以纯文本的形式显示出来。(关键!!!)
- 这是因为浏览器只能解析和显示HTML、CSS和JavaScript等前端资源,而无法执行PHP等后端脚本。因此,当浏览器无法执行PHP文件时,它就会将PHP代码作为纯文本显示出来
- 浏览器无法直接执行Base64编码的PHP文件
- 因此经常使用PHP伪协议对包含的文件的源代码进行转码为base64,使浏览器无法执行该PHP文件,从而获得php源代码
URL/index.php
- 在Web开发中,index.php通常作为默认入口文件,处理路由和请求,即网站的初始页面
- 在CTF题目中,可能会涉及到通过操纵URL参数(比如GET参数、路径遍历)来触发漏洞,比如文件包含、代码执行等
- 当访问目录时,服务器会默认加载index.php(优先级高于index.html等)
代码审计
- 若获得index.php源码,检查以下危险函数:
- PHP的危险函数
- 文件操作:include, require, file_get_contents,include_once,require_once
- 命令执行:exec, system, passthru,eval(执行字符串中的PHP代码),passthru(执行系统命令并输出结果),shell_exec(执行系统命令并返回输出)
- 反序列化:unserialize
include
- 作用:用于加载并执行指定路径的文件
- 路径解析遵循以下规则:
- 如果路径是相对路径,PHP 会根据当前工作目录(getcwd())解析路径
- 如果路径是绝对路径,PHP 会直接解析该路径
- 如果路径中包含路径穿越(如 ../../../../),PHP 会尝试解析这些路径,从而可能导致访问系统文件
- 若文件不存在,则抛出警告,脚本继续执行
- 该函数的路径解析行为
? 的作用
- 在路径中,
?是一个特殊字符,通常用于分隔 URL 的路径部分和查询字符串部分 - 例如:source.php?test 中,source.php 是路径部分,test 是查询字符串。
- 在文件路径中,? 的行为可能会导致 PHP 解析路径时的行为变化。具体来说:
include("source.php?test")会尝试加载 source.php 文件,而 test 会被忽略。
- 然而,如果路径中包含路径穿越(如 ../../../../etc/passwd),? 后面的内容可能会影响路径解析。
为什么需要 ?/ 而不是直接 ../../../../etc/passwd
- 路径穿越攻击的关键在于如何绕过 PHP 的路径解析机制。?/ 的作用是:
- ?:用于分隔路径部分和查询字符串部分,使得 PHP 在解析路径时会忽略 ? 后面的内容。
- /:在 ? 后面添加一个 /,是为了确保路径穿越部分(如 ../../../../etc/passwd)被正确解析,而不是被当作查询字符串的一部分
- PHP 会将 source.php../../../../etc/passwd 视为一个完整的路径
- 如果 source.php 是一个有效文件,PHP 会尝试加载 source.php,而后面的 ../../../../etc/passwd 会被忽略。
- 如果 source.php 不存在,PHP 会报错,但不会尝试解析路径穿越部分
require
- 作用:与
include类似,但错误处理更严格 - 若文件不存在,则触发致命错误,脚本终止
include_once
- 作用:与
include相同,但会检查文件是否已被包含过,避免重复引入 - 适用场景:包含函数库或类文件(避免重复定义错误)
require_once
- 作用:与require相同,但检查文件是否已包含
eval
- 作用:将字符串作为PHP代码执行
- 行为:
- 直接解析字符串中的PHP代码
- 返回值为代码执行结果(无显式返回则null)
system
- 作用:执行外部命令并直接输出结果
- 行为:
- 返回命令输出的最后一行
- 输出直接打印到标准输出(如浏览器)
exec
- 作用:执行命令,返回最后一行输出
- 行为:
- 第二个参数可获取全部输出的数组
- 第三个参数返回命令的退出状态码
passthru
- 作用:执行命令并直接输出原始二进制结果(如图像、文件流)。
- 行为:
- 无返回值,直接输出结果
- 适用于需要保留原始输出的场景
- 示例:
passthru('cat image.jpg', $status);
shell_exec
- 作用:通过Shell执行命令,返回全部输出字符串
- 行为:
- 返回值为命令的完整输出(字符串形式)
- 若输出为空,返回null
URL/index.phps
- 通常,.phps扩展名是PHP Source的缩写,用于让服务器将PHP文件以语法高亮的形式显示源码,而不是执行它
- 比赛中常见的情况是,如果服务器错误配置,当访问index.phps时,可能会直接返回index.php的源码,从而暴露敏感信息,比如数据库凭据、逻辑漏洞或隐藏的路由
- 在Web安全和CTF比赛中,
/index.phps是一个可能暴露敏感信息的特殊文件 - phps文件就是php的源代码文件,通常用于提供给用户(访问者)查看php代码,因为用户无法直接通过Web浏览器看到php文件的内容,所以需要用phps文件代替
$_REQUEST['file']
- 这是 PHP 中的一个超全局变量的用法,它用于获取通过
GET、POST或COOKIE方法传递的变量。具体来说,$_REQUEST是一个数组,包含了用户提交的所有数据 - 使用场景
- 通过 URL 参数传递
- 通过表单提交
- 如果有一个表单,其中包含一个名为 file 的输入框,用户提交表单后,$_REQUEST['file'] 会获取用户输入的值。
- 通过 Cookie 传递
PHP弱类型比较及绕过
PHP中的弱类型比较
php是一种弱类型语言,对数据的类型要求并不严格,可以让数据类型互相转换
强类型与弱类型
- 强类型:
- 所谓强类型(Strongly typed),顾名思义就是强制数据类型定义的语言。也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型。
- Java、.NET、C++等都是强类型语言,在变量使用之前必须声明变量的类型和名称;且不经强制转换不允许两种不同类型的变量互相操作。
- 弱类型:
- 对数据的类型要求并不严格,可以让数据类型互相转换。
PHP中 == 和 === 的区别
php中其中两种比较符号:/
==:先将字符串类型转化成相同,再比较,即先转换再比较
===:先判断两种字符串的类型是否相等,再比较,即先判断再比较
PHP转换规则
字符串和数字比较
使用==时,字符串会先转换为数字类型再比较,若字符串以数字开头,则取开头数字作为转换结果,不能转换数字的字符串(例如"aaa"(整体为非数字)或"aa1258"(开头为非数字)是不能转换为数字的字符串而"123"或"123aa"就是可以转换为数字的字符串)或null,则转换为0。例如:
var_dump(12=="12") //true
var_dump(12=="12aa") //true
var_dump( "admin"==0) //true
var_dump(false==""==0==NULL) //true
var_dump('a' == 0); //bool(true)
var_dump('1a' == 1); //bool(true)
var_dump('12a' == 1); //bool(false)
布尔型和任意比较
布尔值true和任意字符串都弱相等,除了0和false,因为0也认为是bool false,true是不等于false的,例如:
var_dump(true=="hyuf") //true
var_dump(True == 0); //bool(false)
var_dump(True == 'False'); //bool(true)
var_dump(True == 2); //bool(true)
科学计数法和字符串'0'的比较
- 只要是以0e开头,后面为数字的字符串和字符串'0'比较值都是相等的,因为不管0不论和多少相乘都是0
- 所以当hash出来的32个值,开头前两个为0e,后面全部为数字的话,就会和字符串'0'相等
例如:
$str1 = "a";
echo md5($str1); //0cc175b9c0f1b6a831c399e269772661
var_dump(md5($str1) == '0'); //bool(false)
# 只是0开头,所以只能当普通字符串,结果为false
---------------------------------------------------------
$str2 = "s224534898e";
echo md5($str2); //0e420233178946742799316739797882
var_dump(md5($str2) == '0'); //bool(true)
# 0e后面全为数字,符合要求,结果为true
---------------------------------------------------------
$str3 = 'a1b2edaced';
echo md5($str3); //0e45ea817f33691a3dd1f46af81166c4bool
var_dump(md5($str3) == '0'); //bool(false)
# 虽然为0e,但是后面不全为数字,所以结果为false
---------------------------------------------------------
var_dump('0e111111111111' == '0'); //bool(true)
# 告诉大家,不是只有hash才能和字符串0相等
- 数字和“e”开头加上数字的字符串(例如"1e123”)会当作科学计数法去比较
- 当字符串被当作一个数值来处理时,如果该字符串没有包含’.’,‘e’,'E’并且其数值在整形的范围之内,该字符串作为int来取值,其他所有情况下都被作为float来取值
- 并且字符串开始部分决定它的取值,开始部分为数字,则其值就是开始的数字,否则,其值为0
Strcmp函数的漏洞

注意:
strcmp(s1,s2)
说明:
当s1<s2时,返回为负数 注意不一定是-1,测试结果是比较字符串长度
当s1==s2时,返回值= 0
当s1>s2时,返回正数 注意不一定是1,测试结果是比较字符串长度
如果两个字符串不等同,但是字符串长度相同,就比较从哪一位开始不同的,然后比较那一位的大小。
即:两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇'\0'为止。
特别注意:只能比较字符串,不能比较数字等其他形式的参数,如果出现了其它参数,则会retrun null,有些题目就是利用这一点来进行和0的完成比较,不过测试的结果是只有数组或者一个object的时候才会出现这个问题,数字不会
详细说明漏洞:在5.3之前的php中,显示了报错的警告信息后,将return 0 !!!! 也就是虽然报了错,但却判定其相等了。这对于使用这个函数来做选择语句中的判断的代码来说简直是一个致命的漏洞,当然,php官方在后面的版本中修复了这个漏洞,使得报错的时候函数不返回任何值。但是我们仍然可以使用这个漏洞对使用老版本php的网站进行渗透测试。
示例:
<?php
$test = 8;
if (strcmp($test,"test")){
echo "ok!";
echo "\n";
echo strcmp($test,"test");
}
else
echo "oh no!"
?>
运行结果
ok!
-1
<?php
$test = array();
$test[0]=1;
print_r($test);
if (strcmp($test,"test")){
echo "ok!";
echo "\n";
echo strcmp($test,"test");
}
else
echo "oh no!"
?>
运行结果
Array
(
[0] => 1
)
oh no!
PHP Warning: strcmp() expects parameter 1 to be string, array given in /box/main.php on line 6
例题:
<?php
highlight_file(__FILE__);
include("password.php");
if(isset($_POST['password']))
{
if(strcmp($_POST['password'],$password) == 0)
{
$a = $_REQUEST['a'];
if(is_numeric($a)){
die("Oh No");
}
switch($a){
case 0:
echo "NONONO";
break;
case 1:
echo "You are hacker";
break;
case 2:
echo file_get_contents("/flag");
break;
default:
echo "123";
}
}
else
{
die("what's a shame");
}
}
?>
//其实php为了可以上传一个数组,会把结尾带一对中括号的变量,例如 xxx[]视为数组(就是$_POST中的key)
# 错误构造:password=password[0]=1&a=2
# 正确构造:password[]=1&a=2a
绕过
is_numeric()函数的绕过
- 方法一
- 利用数组+十六进制来进行绕过:
j[]=58B
- 利用数组+十六进制来进行绕过:
- 方法二
- is_ numeric() 判断变量是否为数字或数字字符串,不仅检查10进制,16进制是可以。
- is_ numeric函数对于空字符%00,无论是%00放在前后都可以判断为非数值,而%20空格字符只能放在数值后。所以,查看函数发现该函数对对于第-个空格字符会跳过空格字符判断,接着后面的判断!
j=1315%20或j=1315%00
- 方法三:php弱类型比较绕过
==、>=、<=、>、<绕过
- php弱类型比较绕过
===绕过
- PHP比较运算符 === 在进行比较的时候,会先判断两种字符串的类型是否相等,再比较值是否相等。只要两边字符串类型不同会返回false
- 绕过方法:数组绕过
if($_GET['name'] != $_GET['password']){
if(MD5($_GET['name']) == MD5($_GET['password'])){
echo $flag;
}
echo '?';
}
//name[]=1&password[]=2
PHP中md5()函数无法处理数组(会返回NULL)
==的也可以用数组绕过
intval()函数
语法
int intval ( mixed $var [, int $base = 10 ] )
参数说明:
$var:要转换成 integer 的数量值。
$base:转化所使用的进制。
如果 base为空,通过检测 var 的格式来决定使用的进制:
函数主要用于将不同类型的变量转换为整数。如果变量是浮点数,会去掉小数部分取整;如果是字符串,会根据字符串的内容和进制基数进行转换,如果字符串不能转换为整数,则返回 0
如果字符串包括了 "0x" (或 "0X") 的前缀,使用 16 进制 (hex);
否则,如果字符串以 "0" 开始,使用 8 进制(octal);
否则,将使用 10 进制 (decimal)。
示例:
echo intval(42); // 输出 42
echo intval(4.2); // 输出 4
echo intval('42'); // 输出 42
echo intval('4.2'); // 输出 4
echo intval('042'); // 输出 34(八进制转换)
echo intval('0x42'); // 输出 66(十六进制转换)
echo intval('42 monkeys'); // 输出 42
echo intval('monkeys'); // 输出 0
- 绕过方法:通过使用0x或者0开始的格式来绕过不相等的判断

浙公网安备 33010602011771号