关于 CTF 中 php 考点与绕过那些事的总结

  金牌会员 | 2024-8-23 20:49:32 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 993|帖子 993|积分 2979

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
关于 CTF 中常见 php 绕过的总结可以参考我之前的博客:
  
  CTF之PHP特性与绕过
  
  PHP特性之CTF中常见的PHP绕过-CSDN博客
  
  其中主要介绍了 md5()、sha1()、strcmp、switch、intval、$_SERVER 函数、三元运算符、strpos() 、数组、非法参数名传参等干系的绕过。
  在此基础上我们进行一些其他补充
目次

1、PHP 系统预定义变量(超全局变量)
2、 PHP 错误控制运算符
3、PHP 变量默认值
4、$_GET 和 $_POST
5、内置函数的松散性
(1)php 弱范例
(2)array_search 函数 与 in_array 函数
(3)switch 函数
(4)is_numeric 函数
(5)ereg 函数
(6)preg_match 函数
(7)extract 变量覆盖
(8)parse_str 函数变量覆盖缺陷
(9)$$ 变量覆盖
(10)unset 函数
(11)mt_rand() 伪随机数
(12)rand() 函数


1、PHP 系统预定义变量(超全局变量)

  1. $_POST //获取post数据,是一个字典
  2. $_GET //获取get数据,是一个字典
  3. $_COOKIE //获取cookie数据
  4. $_SESSION //获取session数据
  5. $_FILES //获取上传的文件
  6. $_REQUEST //获取$_GET, $_POST, $_COOKIE中的数据
  7. $_SERVER //用户和服务器的基本信息数据库
  8. $_ENV //环境数据
  9. $GLOBALS //所有全局变量
复制代码


2、 PHP 错误控制运算符

PHP 支持一个错误控制运算符:@
当将其放置在一个 PHP 表达式之前,该表达式大概产生的任何错误信息都被忽略掉。
(阻止错误信息表现在用户的界面上,并继承执行代码)
测试代码:
由于文件不存在,file_get_contents() 函数将会产生一个警告
  1. <?php
  2. // 启用错误报告
  3. error_reporting(E_ALL);
  4. ini_set('display_errors', 1);
  5. $file = 'nonexistentfile.txt';
  6. // 如果文件不存在,则会触发警告
  7. $content = file_get_contents($file);
  8. if ($content !== false) {
  9.     echo "文件内容:$content";
  10. } else {
  11.     echo "无法读取文件或文件不存在!";
  12. }
  13. ?>
复制代码
运行结果: 

下面我们使用 @ 来抑制错误信息的表现
@ 符号直接加在大概导致错误的函数或表达式前面
运行结果:


3、PHP 变量默认值

在PHP中,各种范例的变量在被定义但未被赋值时,它们的默认值如下:
   整数 (integer):默认值为0。
  
浮点数 (float):默认值为0.0。
  
字符串 (string):默认值为空字符串 ''。
  
布尔值 (boolean):默认值为false。
  
数组 (array):默认值为一个空数组 array()。
  
对象 (object):默认值为一个空对象。
  
4、$_GET 和 $_POST

如果 GET 参数中设置 name[]=a,那么 $_GET['name'] = [a],php 会把 []=a 当成数组传入, $_GET 会自动对参数调用 urldecode 函数进行 URL 解码。
$_POST 同样存在这样的毛病,提交的表单数据,user[]=admin,$_POST['user'] 得到的是['admin'],是一个数组。

5、内置函数的松散性

关于 md5()、sha1()、strcmp、switch()、intval()、strpos() 等函数我们在前面的文章中已经进行过例题的演示和讲解,这里不再过多赘述,下面我们进行补充。
(1)php 弱范例

php 中有两种比较的符号: == 与 ===
=== 是严格相称运算符,在进行比较时,会先判断两种字符串的范例是否相称,再比较;
== 是相称运算符,在进行比较时,会先将字符串范例转化成相同,再比较,好比比较一个数字和字符串或者比较涉及到数字内容的字符串,字符串会被转换成数值并按照数值来进行比较
先来看一个测试:
  1. <?php
  2. var_dump("myon" == 0);
  3. var_dump("1myon" == 1);
  4. var_dump("myon1" == 0);
  5. var_dump("m1y2o3n4" == 0);
  6. var_dump("0e12345" == "0e6789");
  7. ?>
复制代码
运行结果: 

从运行结果我们可以看到,所有比较均为 true 
"myon" == 0 比较时,会将 myon 转化成数值,强制转化成数值,由于 myon 是字符串,转化的结果是 0 ,自然和 0 是相称的;
第二个 "1myon" == 1 可推断出比较时将 1myon 转化成数值,结果为 1;
但是第三个 "myon1" == 0 ,为什么比较时 myon1 会被转化成 0 呢?
查了 php 手册得知:
   当一个字符串被当作一个数值来取值,其结果和范例如下:如果该字符串没有包罗
'.','e','E'而且其数值值在整形的范围之内,该字符串被当作 int 来取值,其他所有情
况下都被作为 float 来取值,该字符串的开始部分决定了它的值,如果该字符串以合
法的数值开始,则使用该数值,否则其值为 0。
  因此 "m1y2o3n4" == 0 中,m1y2o3n4 也被转化成了 0 ;
至于为什么 "0e12345" == "0e6789",在比较时,会将 0e 这类字符串辨认为科学计数法的数字,0 的无论多少次方都是零,所以相称。
(2)array_search 函数 与 in_array 函数

array_search() 函数用于在数组中搜索指定的值,如果找到则返回其键,否则返回 false。
语法:array_search(value,array,strict)
参数形貌value必须。规定在数组中搜索的键值。array必须。规定被搜索的数组。strict可选。如果该参数被设置为 TRUE,则函数在数组中搜索数据范例和值都一致的元素。大概的值:     

  • true
  • false - 默认
如果设置为 true,则在数组中检查给定值的范例,好比数字 5 和字符串 5 是不同的。 这样一看 array_search 函数其实就雷同于 == (在第三个参数为 false 的情况下)
测试代码:
  1. <?php
  2. $a = array(0,1);
  3. var_dump(array_search("myon",$a));
  4. var_dump(array_search("1myon",$a));
  5. ?>
复制代码
运行结果:
可以看到 myon 被转化成了 0 ;
1myon 则被转化成了 1 ;
与我们前面使用 == 测试的结果一样。

in_array() 函数用于搜索数组中是否存在指定的值,如果找到指定的值,则返回 true,否则返回 false。
语法:bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
参数形貌needle必须。规定要在数组搜索的值。haystack必须。规定要搜索的数组。strict可选。如果该参数设置为 TRUE,则 in_array() 函数检查搜索的数据与数组的值的范例是否相同。 基本功能是一样的,因此绕过方法也是一样的,同上。
(3)switch 函数

如果 switch 是数字范例的 case 的判断时, switch 会将其中的参数转换为 int 范例。
测试代码:
  1. <?php
  2. $content = "2myon";
  3. switch ($content) {
  4.     case 0:
  5.     case 1:
  6.     case 2:
  7.         echo "content 小于3";
  8.         break;
  9.     case 3:
  10.         echo "content 是3";
  11.         break;
  12.     default:
  13.         echo "没有匹配";
  14. }
  15. ?>
复制代码
运行结果:

输出的是content 小于3,是由于 switch() 函数将 $content 进行了范例转换,转换结果为 2。
如果 $content = "m2yon" ,则转换结果为 0。
(4)is_numeric 函数

PHP 提供了 is_numeric 函数,用于检测变量是否为数字或数字字符串。但是函数的范围比较广 泛,不仅仅是十进制的数字,它还可以辨认其他进制的数字字符串,以及科学计数法表示的数字等。
测试代码:
  1. <?php
  2. var_dump(is_numeric(123));
  3. var_dump(is_numeric('123'));
  4. var_dump(is_numeric(0x7e));
  5. var_dump(is_numeric('0x7e'));
  6. var_dump(is_numeric(0e123));
  7. var_dump(is_numeric('0e123'));
  8. ?>
复制代码
运行结果:
原本我以为都会返回 true 
但是十六进制的字符型返回了 false
而科学计数法 0e 开头的数字则是都返回的 true
对于数字和字母组合的字符串,无论是否以数字开头,都是返回 false

因此我们可以将 payload 转换为其他进制传入,或者传入 0e 开头的参数来实现绕过。
(5)ereg 函数

ereg() 函数用于进行正则表达式的匹配,不过它已经在PHP 5.3.0中被废弃。
关于它的两个特性:
ereg() 只能处置惩罚字符串,碰到数组会返回null,null !== false;
当 ereg() 读到 %00 的时间,就截止了。
(6)preg_match 函数

preg_match 函数用于执行一个正则表达式匹配。
用法:preg_match(pattern, input, matches, flags, offset)
参数形貌pattern必须。包罗一个正则表达式,指示要搜索的内容input必须。将在其中执行搜索的字符串matches可选。此参数中使用的变量将填充一个包罗所有找到的匹配项的数组flags可选。一组改变匹配数组布局的选项:     

  • PREG_OFFSET_CAPTURE - 启用此选项后,每个匹配项将不是一个字符串,而是一个数组,其中第一个元素是包罗匹配项的子字符串,而 第二个元素是输入中子字符串的第一个字符的位置。
  • PREG_UNMATCHED_AS_NULL - 启用此选项后,不匹配的子模式将返回为 NULL 而不是空字符串。
offset可选。默认为 0。指示开始搜索到字符串的深度。 preg_match() 函数不会找到出如今此参数中给定位置之前的匹配项 关于这个函数的一些特性与绕过:
preg_match 只能处置惩罚字符串,当传入数组时会返回 false;
测试代码:
我们匹配字符 world,原本是能匹配到的,但是由于我们传入的是数组,则会直接匹配失败。
  1. <?php
  2. $pattern = '/world/';
  3. $string = array('hello', 'world');
  4. $result = preg_match($pattern, $string);
  5. var_dump($result);
复制代码
运行结果:

回溯绕过:pcre.backtrack_limit 给 pcre 设定了一个回溯次数上限,默认为1000000,如果回溯次数超过这个数字,preg_match 就会返回 false,即匹配失败,从而绕过正则匹配。
%0a 换行绕过:.不会匹配换行符,而且在非多行模式下,当出现换行符 %0a 的时间,会被当做两行处置惩罚,而此时只可以匹配第一行,后面的行就会被忽略。(原因其实是正则誊写不妥)
(7)extract 变量覆盖

extract() 函数从数组中把变量导入到当前的符号表中。对于数组中的每个元素, 键名用于变量名,键值用于变量值。
例子:
  1. <?php
  2. $flag='flag.php';
  3. extract($_GET);  
  4. if(isset($ceshi))
  5. {
  6. $content=trim(file_get_contents($flag));
  7. if($ceshi==$content)
  8. {
  9.     echo'flag{xxxxxxx}';
  10. }
  11. else
  12. {
  13.     echo'Oh.no';
  14. }
  15. }
  16. ?>
复制代码
大抵分析是要求我们GET传参进去值会颠末extract()函数,下来会有两个if,第一个 if 判断 ceshi 这个变量是否存在,存在则继承执行 if 里面的,使用 file_get_contents() 读取 flag 变量里面的文件传递给 content 变量 之后,再进行判断传进来 ceshi 变量的值等不等于 $content,如果等于则打印出 flag 。
因为通过extract()函数我们传进的值会变成一个变量,比方我们GET传入 ceshi=1 ,则会存在$ceshi=1 ,所以我们构造 GET 传参 pyaload:
  1. $ceshi=&$flag=  
复制代码
这样就会有两个为空的变量,而 $flag=空 则覆盖了上面的 $flag 中的值,进行判断都是空,所以为真,则可以得到 flag 。
(8)parse_str 函数变量覆盖缺陷

parse_str() 的作用是解析字符串,并注册成变量。在注册变量之前不会验证当前变量是否存在,所以会直接覆盖掉已有变量。与 parse_str() 雷同的函数还有 mb_parse_str(),parse_str 会将字符串解析成多个变量,如果参数 str 是 URL 传递入的查询字符串(query string),则将它解析为变量并设置到当前作用域。
这里我们也来看一个例子:
  1. $a = "test123";
  2. $b = $_GET['b'];
  3. @parse_str($b);
  4. if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) {
  5.    echo $flag;
复制代码
虽然 a 的值看似已经固定,但是我们可以利用变量覆盖对 a 进行重新赋值
构造 payload:
(这里还利用了 md5 的缺陷,它会把每个以0E开头的哈希值都解释为0)
  1. ?b=a[0]=s878926199a
复制代码
(9)$$ 变量覆盖

如果把变量本身的 key 也当变量,也就是使用了 $$,就大概存在题目。
(10)unset 函数

unset($bar) 用来烧毁指定的变量,如果变量 $bar 包罗在请求参数中,大概出 现烧毁一些变量而实现步伐逻辑绕过。
(11)mt_rand() 伪随机数

mt_rand() 函数是一个伪随机发生器,即如果知道随机数种子是可以猜测的。
linux 64 位系统中,rand() 和 mt_rand() 产生的最大随机数都是 2147483647,恰好是 2^31-1,也就是说随机播种的种子也是在这个范围中的, 0 – 2147483647 的这个范围是可以爆破的。 但是用 php 爆破比较慢,有一个 C 的版本,可以根据随机数,爆破出种子 php_mt_seed。
在 php > 4.2.0 的版本中,不再必要用 srand() 或 mt_srand() 函数给随机数 发生器播种,现已由 PHP 自动完成。php 中产生一系列的随机数时,只进行了一 次播种,而不是每次调用 mt_rand() 都进行播种。
(12)rand() 函数

在使用 rand() 函数天生随机数时,如果没有先调用 srand() 函数来设置随机数种子,天生的随机数会遵循伪随机数天生算法,其天生的随机数序列就会有规律可循。

此外,关于 PHP 代码干系的毛病还有文件包罗、RCE、反序列化等,关于这些毛病的利用方法与绕过技巧,可以参考我之前 web 专栏的博客。
创作不易,期待大家的关注与支持!










免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表