PHP代码审计分段讲解(1)
PHP源码来自:https://github.com/bowu678/php_bugs
快乐的暑期学习生活+1
01 extract变量覆盖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <?php $flag = 'xxx' ; extract( $_GET ); if (isset( $shiyan )) { $content =trim( file_get_contents ( $flag )); if ( $shiyan == $content ) { echo 'ctf{xxx}' ; } else { echo 'Oh.no' ; } } ?> |
变量覆盖漏洞:自定义的参数值替换原有变量值的情况
extract()函数介绍:https://www.runoob.com/php/func-array-extract.html
源代码使用extract($_GET)接收了GET请求传输进来的数据,并且将键名和键值转换为变量名和变量的值,可以看到有$flag变量是在extract之前就定义了的,file_get_contents() 函数把整个文件读入一个字符串中。正常的代码流程读取$flag文件并且去除两边的空格,将结果存储在$content里面,然后使用我们传入的$shiyan变量与$content进行比较,如果相同的话就输出flag,否则输出 Oh no。
如果$flag文件存在的话,因为是题目服务器上的文件,我们没有权限知道里面的内容,在其读取到$content变量里面的时候,更无法与其进行比较,进而获取flag
突破点在于:extract()函数在flag变量值的下面,所以我们可以以GET方式对$flag重新赋值,进而读取一个不可能存在的文件,使$content为空,传入$shiyan变量也为空,即可获取flag
如图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | <?php $info = "" ; $req = []; $flag = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ; ini_set ( "display_error" , false); //为一个配置选项设置值 error_reporting (0); //关闭所有PHP错误报告 if (!isset( $_GET [ 'number' ])){ header( "hint:26966dc52e85af40f59b4fe73d8c323a.txt" ); //HTTP头显示hint 26966dc52e85af40f59b4fe73d8c323a.txt die ( "have a fun!!" ); //die — 等同于 exit() } foreach ([ $_GET , $_POST ] as $global_var ) { //foreach 语法结构提供了遍历数组的简单方式 foreach ( $global_var as $key => $value ) { $value = trim( $value ); //trim — 去除字符串首尾处的空白字符(或者其他字符) is_string ( $value ) && $req [ $key ] = addslashes ( $value ); // is_string — 检测变量是否是字符串,addslashes — 使用反斜线引用字符串 } } function is_palindrome_number( $number ) { $number = strval ( $number ); //strval — 获取变量的字符串值 $i = 0; $j = strlen ( $number ) - 1; //strlen — 获取字符串长度 while ( $i < $j ) { if ( $number [ $i ] !== $number [ $j ]) { return false; } $i ++; $j --; } return true; } if ( is_numeric ( $_REQUEST [ 'number' ])) //is_numeric — 检测变量是否为数字或数字字符串 { $info = "sorry, you cann't input a number!" ; } elseif ( $req [ 'number' ]!= strval ( intval ( $req [ 'number' ]))) //intval — 获取变量的整数值 { $info = "number must be equal to it's integer!! " ; } else { $value1 = intval ( $req [ "number" ]); $value2 = intval ( strrev ( $req [ "number" ])); if ( $value1 != $value2 ){ $info = "no, this is not a palindrome number!" ; } else { if (is_palindrome_number( $req [ "number" ])){ $info = "nice! {$value1} is a palindrome number!" ; } else { $info = $flag ; } } } echo $info ; |
代码比较长,但是不慌,从上往下看,用到函数的时候再到函数里再去看构造
1 2 3 4 5 6 7 8 9 10 11 12 13 | if (!isset( $_GET [ 'number' ])){ header( "hint:26966dc52e85af40f59b4fe73d8c323a.txt" ); //HTTP头显示hint 26966dc52e85af40f59b4fe73d8c323a.txt die ( "have a fun!!" ); //die — 等同于 exit() } foreach ([ $_GET , $_POST ] as $global_var ) { //foreach 语法结构提供了遍历数组的简单方式 foreach ( $global_var as $key => $value ) { $value = trim( $value ); //trim — 去除字符串首尾处的空白字符(或者其他字符) is_string ( $value ) && $req [ $key ] = addslashes ( $value ); // is_string — 检测变量是否是字符串,addslashes — 使用反斜线引用字符串 } } |
在这里使用GET方式传入number参数的值,同时也可以通过GET或者POST的方式传入其他参数的值
1 2 3 4 5 6 | if ( is_numeric ( $_REQUEST [ 'number' ])) //is_numeric — 检测变量是否为数字或数字字符串 { $info = "sorry, you cann't input a number!" ; } |
使用is_numeric函数检测传入的$number是否为数字,不能传入数字
1 2 3 4 5 6 | elseif ( $req [ 'number' ]!= strval ( intval ( $req [ 'number' ]))) //intval — 获取变量的整数值 { $info = "number must be equal to it's integer!! " ; } |
intval函数获取变量的整数值
strval — 获取变量的字符串值
这一步需要传入的是整数,也就与第一个if条件相矛盾
这两部分都可以通过在number参数前加%00进行绕过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | else { $value1 = intval ( $req [ "number" ]); $value2 = intval ( strrev ( $req [ "number" ])); if ( $value1 != $value2 ){ $info = "no, this is not a palindrome number!" ; } else { if (is_palindrome_number( $req [ "number" ])){ $info = "nice! {$value1} is a palindrome number!" ; } else { $info = $flag ; } } } echo $info ; |
这一部分第一步需要$value1和$value2相等,strrev函数令number取反,所以$number需要是一个回文数字,如12321,第二步需要绕过is_palindrome_number函数,使其返回值为false,其函数为:
1 2 3 4 5 6 7 8 9 10 11 12 13 | function is_palindrome_number( $number ) { $number = strval ( $number ); //strval — 获取变量的字符串值 $i = 0; $j = strlen ( $number ) - 1; //strlen — 获取字符串长度 while ( $i < $j ) { if ( $number [ $i ] !== $number [ $j ]) { return false; } $i ++; $j --; } return true; } |
该函数将$number数字转换为字符串,然后进行首尾依次比较,首尾相同则返回true,否则返回false。
这里看上去是矛盾的,既要令$number是回文,又要令$number不是回文。
分析可知,在绕过else里面的限制时,我们需要的是一个能逃逸出intval和is_numeric函数,却逃逸不出is_palindrome_number函数的字符
在P师傅的博客中,是通过查看PHP底层源代码进行分析的:
https://www.leavesongs.com/PENETRATION/some-sangebaimao-ctf-writeups.html
发现了\f字符能够进行实现上述的逃逸,看源代码当然很好,但是这里我们采用FUZZ的方法,python3脚本
1 2 3 4 5 6 7 | import requests for i in range ( 256 ): url = "http://127.0.0.1/php_bugs-master/02.php?number=%00%{:02X}12321" . format (i) rep = requests.get(url) if "x" in rep.text: print (url) |
运行之后结果为:
所以能够逃逸出来的字符为%0C 和 %2B,使用以上payload读取flag
__EOF__

本文链接:https://www.cnblogs.com/Cl0ud/p/13228066.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!