目录

  1. 1. PHP特性
  2. 2. PHP弱类型
  3. 3. PHP短标签
  4. 4. 函数特性
    1. 4.1. intval()
    2. 4.2. in_array()函数
    3. 4.3. is_file()函数
    4. 4.4. is_numeric()函数
    5. 4.5. mt_srand函数&伪随机数漏洞
    6. 4.6. $_SERVER[‘argv’]
    7. 4.7. $_SERVER[‘QUERY_STRING’]
    8. 4.8. gettext()函数
    9. 4.9. get_defined_vars()函数
    10. 4.10. class_exists()函数
      1. 4.10.1. __autoload()函数
    11. 4.11. pathinfo函数
  5. 5. 字符串函数
    1. 5.1. md5
      1. 5.1.1. 等效值绕过
    2. 5.2. md4
    3. 5.3. sha1
    4. 5.4. substr
      1. 5.4.1. 重复取值突破substr限制
    5. 5.5. strpos
    6. 5.6. trim
    7. 5.7. htmlspecialchars
  6. 6. 正则特性
    1. 6.1. preg_match()
    2. 6.2. 常规绕过
    3. 6.3. 无视通配符
    4. 6.4. 溢出绕过/PCRE回溯
    5. 6.5. %0a换行绕过
    6. 6.6. 命名空间绕过
    7. 6.7. 数组绕过
    8. 6.8. ereg()函数
    9. 6.9. preg_replace和正则e模式
  7. 7. 路径绕过
  8. 8. 数字和运算符一起执行命令
  9. 9. 运算符优先级
    1. 9.1. and与=
    2. 9.2. &&与||
  10. 10. 内置类
    1. 10.1. php原生类
      1. 10.1.1. 反射类ReflectionClass
  11. 11. 编码绕过
    1. 11.1. 十六进制
    2. 11.2. url编码
    3. 11.3. 字符编码
  12. 12. 数组绕过
  13. 13. 变量覆盖
    1. 13.1. $$
    2. 13.2. extract()函数
    3. 13.3. parse_str()函数
    4. 13.4. foreach()函数
    5. 13.5. array_merge()函数
  14. 14. $_GET,$_POST,$_COOKIE
  15. 15. $_REQUEST
  16. 16. 非法传参

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

PHP特性

2023/3/15 Web PHP
  |     |   总文章阅读量:

PHP特性

总结 PHP 中常见的一些函数/语言特性

PHP弱类型

在 PHP 中,不同类型的变量可以相互转换

比较

<?php
var_dump("admin"==0);	// true
var_dump("1admin"==1); 	// true
var_dump("admin1"==1);	// false
var_dump("admin1"==0);	// true
var_dump("0e123456"=="0e4456789");	// true
var_dump(True=="a");	// true
var_dump(True==1);	// true
var_dump(False=="0");	// true
var_dump(False==0);	// true
?>

运算

<?php
$test=1 + "10.5"; 	// $test=11.5(float)
$test=1+"-1.3e3"; 	// $test=-1299(float)
$test=1+"bob-1.3e3";	// $test=1(int)
$test=1+"2admin";	// $test=3(int)
$test=1+"admin2";	// $test=1(int)
?>

这种题可以在本地多调试调试来找到需要的payload

例如将字符串转换为数字或将数字转换为字符串

  • 科学计数法绕过

    使用例

    if($year==2022 && $year+1!==2023){
        echo $flag; 
    此时传入202.2e1即可绕过
  • 与数字或布尔值比较时,字符串会转为数字或布尔值

    ctfshow web140

    例:字符串’<’会被转换为0,因为不是一个有效的数字


PHP短标签

https://www.php.net/manual/zh/language.basic-syntax.phptags.php

PHP 有一个 echo 标记简写 <?=, 它是更完整的 <?php echo 的简写形式

<? echo '123';?>  #前提是开启配置参数short_open_tags=on
<?=(表达式)?>  等价于 <?php echo (表达式)?>  #不需要开启参数设置
<% echo '123';%>   #开启配置参数asp_tags=on,并且只能在7.0以下版本使用
<script language="php">echo '123'; </script> #不需要修改参数开关,但是只能在7.0以下可用。
  • 可绕过过滤<?或php

函数特性

intval()

将给定变量转换成整型变量,获取变量的整数值

int intval ( var , base )
////var指要转换成 integer 的数量值,base指转化所使用的进制 
  • var 可以是任何标量类型,变量不能是array和object格式

    除非var参数是字符串,否则 intval()base 参数不会有效果

    var中存在字母的话遇到字母就停止读取

    但是e这个字母比较特殊,可以在PHP中不是科学计数法

  • 如果 base 0,通过检测 var 的格式来决定使用的进制:

    • 如果字符串包括了 “0x” (或 “0X”) 的前缀,使用 16 进制 (hex);
    • 否则,如果字符串以 “0” 开始,使用 8 进制(octal);
    • 否则,如果字符串以”0b”开始,使用2进制;
    • 否则,将使用 10 进制 (decimal)。
  • 漏洞:通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。

    处理开头是数字的字符串时,返回值为开头的数

  • 防止整数溢出:

    <?php echo intval('4200000000000000000000');?>

    回显:32位系统:2147483647;64位系统:9223372036854775807

利用方式

  • 过滤某个数字时,我们可以利用它的进制转换来绕过

    <?php
    echo intval(042);                     // 34
    echo intval(0x1A);                    // 2
    ?> 
  • 数组绕过

    返回值
    成功时返回 var 的 integer 值,失败时返回 0。空的 array 返回 0,非空的 array 返回 1。

    if(a!=b){
        if(a==b){
            
        }
    }
        //输入a[]=1和b[]=2绕过(此时两个不同但是都返回1)
  • 小数点绕过

    小数点后的数字会直接舍去

    echo intval(42);                      // 42
    echo intval(4.2);                     // 4
  • 单引号字母绕过

    单引号传值的时候,它只识别字母前面的一部分

    进行get传参时,默认加单引号

    echo intval(1e10);                    // 1410065408
    echo intval('1e10');                  // 1
    • 仅在php5下可实现1e10=1;php7下会转换成10的10次方
    • 计算时1e10会自动转换为10的10次方

in_array()函数

判断一个值是否在数组中

in_array(value,array,type)
//value :要搜索的值
//array :被搜索的数组
//type : 类型,true全等 ,false非全等(默认)

如果没有设置第三个参数,就会进行自动转换(弱类型比较忽略后面的字符串)

$n="1.php";
echo in_array($n,range(1,9));
// 1

is_file()函数

检查指定的文件是否是常规的文件。

如果文件是常规的文件,该函数返回 TRUE。

  • 可运用伪协议,不会将伪协议当作文件
  • 目录溢出:该函数最多能处理330位字符串(大概?),超出后会认为不是一个文件
/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p
roc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/pro
c/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/se
lf/root/proc/self/root/var/www/html/flag.php

is_numeric()函数

检测变量是否为数字或数字字符串

PHP 4, PHP 5, PHP 7

如果指定的变量是数字和数字字符串则返回 TRUE,否则返回 FALSE,注意浮点型返回 1,即 TRUE

  • 在数字前加上空格,也会被is_numeric函数认为是数字

    \n,\t,\f,\r,\v, .均可绕过

mt_srand函数&伪随机数漏洞

播种 Mersenne Twister 随机数生成器

mt_srand(seed);
  • seed:可选,规定播种值,设置之后即为伪随机

伪随机存在可预测性:

知道种子后,可以确定输出伪随机数的序列
知道随机数序列,可以确定你的种子

可以使用工具php_mt_seed爆破seed:下载地址www.openwall.com/php_mt_seed

用法:在php_mt_seed文件夹下执行命令

make
time ./php_mt_seed 123878781

$_SERVER 是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组

$_SERVER[‘argv’]

传递给该脚本的参数,以数组方式传回里面的内容

<?php
$a=$_SERVER['argv'];
var_dump($a);

这里分别测试一下传入参数1a=1&b=1a=1+b=1

image-20230804124857920

image-20230804124935710

image-20230804125002066

从这里可以发现几个问题

  • &无法分割参数,真正能分割参数的是+
  • 等号无法赋值,而是会直接被传进去当作参数

$_SERVER[‘QUERY_STRING’]

查询字符串,一般用来返回get请求中?后面的内容

image-20231122113059278

注意:这个函数匹配的是原始数据,就是没有url编码过的数据

所以当我们用url编码的时候,是可以绕过对$_SERVER['QUERY_STRING']中的值的匹配

image-20231122113835313


gettext()函数

可以直接输出文本,需要php扩展目录下有php_gettext.dll

拓展函数:

_()==gettext()

get_defined_vars()函数

返回由所有已定义变量所组成的数组

phpinfo()一样是无参方法,可以直接被call_user_func调用

class_exists()函数

PHP中的内置函数,用于检查是否定义了给定的类

class_exists( string $class_name, bool $autoload = TRUE )
  • $class_name:它拥有需要检查其存在的类名
  • $autoload:它检查默认情况下是否调用__autoload

__autoload()函数

尝试加载未定义的类

php版本 <= 5.3

通过定义这个函数来启用类的自动加载


pathinfo函数

返回文件路径的信息

pathinfo ($path [, int $options = PATHINFO_DIRNAME | PATHINFO_BASENAME | PATHINFO_EXTENSION | PATHINFO_FILENAME ] )

如果没有传入 options ,将会返回包括以下单元的数组array:

dirname:目录路径

basename:文件名

extension(如果有):文件后缀名。注:当出现多个.时,结果为最后一个.后面的内容。可以利用这个特性实现对后缀名检测的绕过。如/../../../../1.php,还是可以检测出php

filename:不包含后缀的文件名

根据/获取文件名,根据.获取到文件后缀名,再将不包含后缀的文件名和文件后缀名进行拼接

测试:

<?php
highlight_file(__FILE__);
$name = $_GET['name'];
var_dump($name);
$pathinfo_name=pathinfo($name);
var_dump($pathinfo_name);
?>

我们正常传入一个路径/1.php

image-20231119204150725

可以看到都被正常解析了

但是如果我们构造一个特殊的路径1.php/.呢?

image-20231119204410074

可以看到此时后缀名和文件名都解析失败了


字符串函数

md5

计算字符串的 MD5 散列值

md5(string $string, bool $binary = false): string

等效值绕过

弱类型比较:

md5($a) == md5($b);

以 0e 开头的数字后面会被忽略(科学计数法)

即在进行弱比较时两边的结果均为0

QNKCDZO
240610708
s878926199a
s155964671a
s1091221200a

强类型比较:

md5($a) === md5($b);

用MD值完全相同的字符来进行绕过

md5(urldecode("psycho%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00W%ADZ%AF%3C%8A%13V%B5%96%18m%A5%EA2%81_%FB%D9%24%22%2F%8F%D4D%A27vX%B8%08%D7m%2C%E0%D4LR%D7%FBo%10t%19%02%82%7D%7B%2B%9Bt%05%FFl%AE%8DE%F4%1F%84%3C%AE%01%0F%9B%12%D4%81%A5J%F9H%0FyE%2A%DC%2B%B1%B4%0F%DEcC%40%DA29%8B%C3%00%7F%8B_h%C6%D3%8Bd8%AF%85%7C%14w%06%C2%3AC%BC%0C%1B%FD%BB%98%CE%16%CE%B7%B6%3A%F3%99%B59%F9%FF%C2")) === md5(urldecode("psycho%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00W%ADZ%AF%3C%8A%13V%B5%96%18m%A5%EA2%81_%FB%D9%A4%22%2F%8F%D4D%A27vX%B8%08%D7m%2C%E0%D4LR%D7%FBo%10t%19%02%02%7E%7B%2B%9Bt%05%FFl%AE%8DE%F4%1F%04%3C%AE%01%0F%9B%12%D4%81%A5J%F9H%0FyE%2A%DC%2B%B1%B4%0F%DEc%C3%40%DA29%8B%C3%00%7F%8B_h%C6%D3%8Bd8%AF%85%7C%14w%06%C2%3AC%3C%0C%1B%FD%BB%98%CE%16%CE%B7%B6%3A%F3%9959%F9%FF%C2"))

因为客户端传参数时会过一层 url 解码,所以直接传整个 url 编码的 payload 即可


数组绕过:

<?php
$a = array("b" => 2);
$b = array("a" => 1);
var_dump(md5($a) === md5($b));

原理是md5等函数不能处理数组,导致函数返回Null。而 Null 是等于 Null 的,导致了绕过。


特殊比较:

  • 自身和 md5 后都是 0e 开头:

    "0e215962017" == md5("0e215962017");
  • 两次 md5 后还是 0e 开头:

    "0e1138100474" == md5(md5("0e1138100474"));

爆破固定前缀:

import hashlib

def md5_hash(text):
    return hashlib.md5(text.encode()).hexdigest()

target_prefix = "666"
found = False

for i in range(1000000):
    # 将当前数字作为字符串进行MD5哈希两次
    text = str(i)
    hash1 = md5_hash(text)
    hash2 = md5_hash(hash1)

    # 检查哈希结果的开头是否为目标前缀
    if hash2.startswith(target_prefix):
        print("找到匹配的字符串:", text)
        found = True
        break

if not found:
    print("未找到匹配的字符串。")

爆破 0e 开头:以上面那个两次 md5 和原始字符串比较为例

import hashlib
from tqdm import *
def md5_encrypt(value):
    return hashlib.md5(value.encode()).hexdigest()
def find_values():
    values = []
    for i in trange(10000,100000000000):
        value = f"0e{i}"
        md5_1 = md5_encrypt(value)
        md5_2 = md5_encrypt(md5_1)
        if md5_2.startswith("0e") and md5_2[2:].isdigit():
            values.append((value, md5_1, md5_2))
    return values
result = find_values()
print(result)

有需要的话自改


md4

来自强网杯2020 Funhash

弱比较:

$_GET['hash1'] == hash('md4',$_GET['hash1']))

hash1=0e251288019

爆破出的另一个结果:0e001233333333333334557778889


sha1

计算字符串的 sha1 散列值

sha1(string $string, bool $binary = false): string

弱类型比较:

sha1($a) == sha1($b)

0e开头:

aaO8zKZF
aaK1STfY

强类型绕过:

<?php
$a = "%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1";
$b = "%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1";
var_dump(sha1(urldecode($a)) === sha1(urldecode($b)));

数组绕过:和 md5 一样


substr

返回字符串的一部分

如果 参数start 是负数且 length 小于或等于 start,则 length 为 0。

substr(string,start,length)
  • start:

    正数 - 在字符串的指定位置开始

    负数 - 在从字符串结尾的指定位置开始

    0 - 在字符串中的第一个字符处开始

  • length:

    正数 - 从start参数所在的位置向后返回字符个数

    负数 - 从字符串末端指定位置向前返回字符个数

如果提取字符串失败则返回 FALSE,或者返回一个空字符串,所以在某些情况下可以利用这个特性传入数组实现绕过

image-20231122111544451

重复取值突破substr限制

ctfshow web133

linux 环境下

<?php
$F="`\$F`; ls";
$a=substr($F,0,6);
eval($a); 	// eval("`$F`; ")

eval 内的php代码:

`$F`; 

取变量 $F

此时 eval 执行的 linux 命令:

`$F`; ls

strpos

查找字符串首次出现的位置

  • 开头添加+%0a使返回数改变

strpos('01234', 0) 返回的结果是 0 对应的索引 0, 也就是 false

如果是 !strpos() 这种则会返回 true

代码使用了 if(!strpos($str, 0)) 对八进制进行过滤, 可以在字符串开头加空格绕过

strpos() 遇到数组返回 null

strrpos()stripos()strripos() 同理


trim

移除字符串两侧的空白字符或其他预定义字符

ltrim() 移除字符串左侧的空白字符或其他预定义字符。rtrim()移除字符串右侧的空白字符或其他预定义字符。

trim(string $string, string $characters = " \n\r\t\v\x00"): string

执行成功时返回删除了 string 字符串首部和尾部空格的字符串,发生错误时返回空字符串("")。如果任何参数的值为 NULL , trim() 函数返回 NULL

  • trim() 函数过滤空格以及 \n(%0a) \r(%0d) \t(%09) \v(%0b) \0(%00),但不会过滤换页符\f (%0c)

htmlspecialchars

将特殊字符转换为 HTML 实体

https://www.php.net/htmlspecialchars

htmlspecialchars(
    string $string,
    int $flags = ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401,
    ?string $encoding = null,
    bool $double_encode = true
): string

转义:

& (& 符号)  ===============  &amp;
" (双引号)  ===============  &quot;
' (单引号)  ===============  &apos;
< (小于号)  ===============  &lt;
> (大于号)  ===============  &gt;

正则特性

preg_match()

判断输入的值是否存在指定字符

  • 正则中的$:匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 ‘\n’ 或 ‘\r’。要匹配 $ 字符本身,请使用 $

  • 正则中的.:匹配除换行符外的任意一个字符

  • *:匹配0次、或1次、或多次其前面的字符

  • +:匹配1次或多次前面的字符

  • ?:匹配0次或1次其前面的字符

  • 模式修正符:

    • i:在和模式进行匹配时不区分大小写
    • m:多行匹配
    • s:匹配所有的字符,包括换行符
    • U:禁止贪婪匹配
  • 漏洞:无法处理数组

常规绕过

参考RCE部分绕过过滤的关键字

无视通配符

ctfshow web130

preg_match('/.+?ctfshow/is', $f)
?作为通配符可以匹配至少一个任意字符,但是如果没有这个字符则不匹配,因此ctfshow可以直接绕过

溢出绕过/PCRE回溯

ctfshow web130、131

可以看p神的文章:https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html

PHP为了防止DOS攻击。给pcre设置了一个回溯上限。默认是100万

var_dump(ini_get('pcre.backtrack_limit'))
// string(7) "1000000"

当待匹配的字符串超过100W,函数就会返回False。也就没未匹配到字符。

所以当正则表达式的限制与某些条件冲突时,我们可以利用str_repeat函数进行重复匹配实现溢出绕过

因为太长了所以直接用python脚本发送请求

import requests

url = "http://ca01df51-1030-4af4-a39d-ce3852f2ae35.challenge.ctf.show/"
data = {'f': 'very' * 250000 + '36Dctfshow'}
res = requests.post(url, data=data)
print(res.text)

%0a换行绕过

  • \n换行符即%0a

demo1:

<?php
$str = $_GET['b'];
if (preg_match('/^.flag/i', $str)) {
    echo 'waf';
} else {
    echo 'OK~';
}

上面也说过了,正则中的.会匹配除换行符外的任意一个字符,那当我们用%0a换行的时候就不会被正则识别到,从而能实现绕过

demo2:

if (preg_match('/^flag$/', $_GET['a']) && $_GET['a'] !== 'flag') {
    echo $flag;
}

这个正则匹配是以flag开头和结尾的,即只匹配flag这个字符串

而非多行模式m下,$会忽略在结尾的%0a,所以传入flag%0a即可绕过

命名空间绕过

ctfshow web147

对于以下正则

/^[a-z0-9_]*$/isD

因为匹配的是参数的开头和结尾,我们想调用函数的话就需要用到php的默认命名空间\,所有原生函数和类都在这个命名空间中

普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径; 而如果写\function_name()这样调用函数,则其实是写了一个绝对路径

数组绕过

上面介绍正则匹配函数的时候我们说过这个函数无法处理数组

foreach($_POST as $var){
        if(preg_match("/[a-zA-Z0-9]/",$var)){
            die("fxxk");
        }
    }

因此如果我们需要传入POST参数,此时可以用数组实现绕过


ereg()函数

用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false

搜索字母的字符是大小写敏感的

int ereg(string pattern, string originalstring, [array regs]);

可选的输入参数regs包含由正则表达式中的括号组成的所有匹配表达式的数组
  • 漏洞
    • NULL截断:使用%00截断前后语句的正则匹配
    • 只能处理字符串,遇到数组做参数返回NULL

preg_replace和正则e模式

SHCTF2023 Week1


路径绕过

原型

if(isset($_GET['u'])){
    if($_GET['u']=='flag.php'){
        die("no no no");
    }else{
        highlight_file($_GET['u']);
    }
} 

只匹配flag.php,所以可以加入当前路径

./flag.php
var/www/html/flag.php

目录穿越

读取文件时可以利用以下语句访问

../../../../etc/passwd
../../../../var/www/html

注:若路径中有不存在的文件夹会直接跳过此段路径查看

../aaa/../../../var/www/html == 上面那段语句

数字和运算符一起执行命令

1+phpinfo()+1
1*phpinfo()
1|phpinfo()|1

这样可以显示phpinfo.php

同理存在eval的情况下可以执行system()

运算符优先级

&& > || > = > and > or

and与=

赋值语句中出现=和多个and时只会赋第一个值

$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);

=的运算符比and高
对于v0的值只需要看v1,而v2、v3是干扰

&&与||

ctfshow web132

对于

if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin")

由于优先级的原因,前两个条件会先进行一次&&运算,结果通常为0,然后与最后一个条件进行||运算,只要让$username ==="admin",就能变成0||1即1


内置类

题目有调用类语句时可用

如:echo $v1($v2);

php原生类

直接看另一篇博客

反射类ReflectionClass

参考:https://blog.csdn.net/raoxiaoya/article/details/92797765

通过ReflectionClass,我们可以得到相关类的所有信息,接下来把类名传递即可

<?php
class A{
public static $flag="flag{123123123}";
const  PI=3.14;
static function hello(){
    echo "hello</br>";
}
}

$a=new ReflectionClass('A');	//实例化
 
 
var_dump($a->getConstants());  获取一组常量
输出
 array(1) {
  ["PI"]=>
  float(3.14)
}
 
var_dump($a->getName());    获取类名
输出
string(1) "A"
 
var_dump($a->getStaticProperties()); 获取静态属性
输出
array(1) {
  ["flag"]=>
  string(15) "flag{123123123}"
}
 
var_dump($a->getMethods()); 获取类中的方法
输出
array(1) {
  [0]=>
  object(ReflectionMethod)#2 (2) {
    ["name"]=>
    string(5) "hello"
    ["class"]=>
    string(1) "A"
  }
}

编码绕过

十六进制

$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
    $s = substr($v2,2);
    $str = call_user_func($v1,$s);
    echo $str;
    file_put_contents($v3,$str);
}
  • v1传入hex2bin函数
  • v2传入base64后的16进制ascll码
  • v3传入php伪协议写入文件

url编码

字符编码

chr(47).chr(102).chr(49).chr(97).chr(103)

数组绕过

md5(Array()) = null
sha1(Array()) = null
ereg(pattern,Array()) = null
preg_match(pattern,Array()) = false
strcmp(Array(), "abc") = null
strpos(Array(),"abc") = null
strlen(Array()) = null

null/false == 0


变量覆盖

  • post传入的参数并不能实现变量交换

用我们自定义的参数值替换程序原有的变量值

$$ 
extract()函数
parse_str()函数
import_request_variables()函数

$$

将之前定义的变量的值重新定义新的变量

$a=b
$b=c

$$a = $($a) = $b = c
    
$hell="abc";
$$hell="def";等同于$abc="def";
  • $GLOBALS

    返回全局作用域中可用的全部变量

    可在过滤相关命令执行函数情况下使用

extract()函数

将数组中的变量导入到当前的符号表

extract(array,extract_rules,prefix) 
    
//array 必需的,规定要使用的数组
//extract_rules 可有可无,如果为空,则默认为EXTR_OVERWRITE
//prefix可选。如果 extract_rules 参数的值是 EXTR_PREFIX_SAME、EXTR_PREFIX_ALL、 EXTR_PREFIX_INVALID 或 EXTR_PREFIX_IF_EXISTS,则 prefix 是必需的。该参数规定了前缀。前缀和数组键名之间会自动加上一个下划线。

parse_str()函数

把查询字符串解析到变量中

PHP4+

parse_str(string,array) 
    
没有返回值
//string 	必需。规定要解析的字符串。
//array 	可选。规定存储变量的数组名称。该参数指示变量存储到数组中。
//          如果未设置 array 参数,由该函数设置的变量将覆盖已存在的同名变量。

注释:如果未设置 array 参数,由该函数设置的变量将覆盖已存在的同名变量。

注释:php.ini 文件中的 magic_quotes_gpc 设置影响该函数的输出。如果已启用,那么在 parse_str() 解析之前,变量会被 addslashes() 转换。

<?php
parse_str("name=Peter&age=43",$myArray);
print_r($myArray);
?>
    
    输出
    Array ( [name] => Peter [age] => 43 ) 

foreach()函数

参考我的另一篇博客

array_merge()函数

参考[GFCTF 2021]Baby_Web

$_GET,$_POST,$_COOKIE

ctfshow web134

php中传参的主要代码如下

$_GET['key']
$_POST['key']

但是像_GET_POST_COOKIE这种超全局变量本身也是参数

所以我们可以直接对其进行传参,如?_GET=flag,此时参数内部为:

["_GET"]=>  string(4) "flag"

也可以传入数组形式的参数,如?_POST[a]=1&_POST[b]=12,此时参数内部为:

["_GET"]=>
    array(1) {
    ["_POST"]=>
        array(2) {
        ["a"]=>
            string(1) "1"
            ["b"]=>
            string(2) "12"
    }
}
["_POST"]=>
    array(2) {
    ["a"]=>
        string(1) "1"
        ["b"]=>
        string(2) "12"
}

$_REQUEST

当 GET 和 POST 有相同的变量时,优先匹配 POST 的变量

image-20231122115213938


非法传参

当变量名中出现点和空格时,变量名中的空格被转换成下划线

  • 版本:php < 8

  • 在变量解析中,php会把请求参数中的非法字符转为下划线

  • 如果参数中出现中括号[,中括号会被转换成下划线_,但是会出现转换错误导致接下来如果该参数名中还有非法字符并不会继续转换成下划线_,也就是说如果中括号[出现在前面,那么中括号[还是会被转换成下划线_,但是因为出错导致接下来的非法字符并不会被转换成下划线_