免责声明:本文章仅用于交流学习,因文章内容而产生的任何违法&未授权行为,与文章作者无关!!!
附:完整笔记目录~
ps:本人小白,笔记均在个人理解基础上整理,若有错误欢迎指正!
1.1 🐘全局变量(PHP)
-
引子:从本章开始,正式进入Web开发篇,当然文章所写内容并非如何从零开始成为一名合格的开发者,而是站在安全的角度学开发。再说白点,就是开发者在开发业务系统时,哪处容易出现安全问题就学哪处。本章则从PHP基础之全局变量开始。
-
$GLOBALS
$GLOBALS
全局变量,为PHP内置的全局数组,存储了当前脚本所有的全局作用域变量。(其中,全局作用域变量指在函数外部所定义的变量,可在整个脚本中被访问,不过在函数内部默认无法访问。)
demo:<?php $x = 1; $y = 2; $z = 19; function add() { # $z = $x + $y; # 由于x,y,z定义在函数外,无法直接调用, # 此时$GLOBALS全局变量的作用就体现出来了, # 可通过$GLOBALS全局数组实现对x,y,z变量的访问和更改 $GLOBALS['z'] = $GLOBALS['x'] + $GLOBALS['y']; } add(); echo "z的值为" . $z; # RES: z的值为3 # 综上所述,通过部分全局变量可实现对于原本无法直接访问变量的调用与修改。
-
$_GET
&$_POST
&$_REQUEST
$_GET
全局变量用于获取Http请求包中请求方法为get的数据,$_POST
全局变量用于获取请求方式为post的数据,$_REQUEST
可同时接收来自get&post的数据。
demo:<?php echo "接收到GET请求包的值为:" . $_GET['x'] . '<hr>'; # http://192.168.2.106:81/global/GlobalDemo2.php?x=19 # RES: 接收到GET请求包的值为:19 echo "接收到POST请求包的值为:" . $_POST['y'] . '<hr>'; # http://192.168.2.106:81/global/GlobalDemo2.php?x=19 # body: y=19 # RES: 接收到POST请求包的值为:19 echo "接收到GET/POST请求包的值为:" . $_REQUEST['z']; # http://192.168.2.106:81/global/GlobalDemo2.php?x=19&z=19 # body: y=19 # RES: 接收到GET/POST请求包的值为:19 # http://192.168.2.106:81/global/GlobalDemo2.php?x=19 # body: y=19&z=19 # RES: 接收到GET/POST请求包的值为:19
-
$_COOKIE
&$_SESSION
用于访问&存储Cookie&Session值信息。
demo1:<?php // 获取用户名和密码 $username = $_POST['username']; $password = $_POST['password']; // 保存为 Cookie setcookie('username', $username, time() + 86400, '/'); setcookie('password', $password, time() + 86400, '/'); // 其中 'username' & 'password' 为Cookie Name // $username & $password 为Cookie Value // time() + 86400 为Cookie有效期,即从当前开始持续24小时 // '/' 为Cookie生效范围,'/'即为用户访问该网站任意路径都会携带Cookie echo "Cookie中的username值为:" . $_COOKIE['username'] . '<hr>'; echo "Cookie中的password值为:" . $_COOKIE['password'] . '<hr>'; // RES:Cookie中的username值为:test // Cookie中的password值为:test
demo2:
<?php // 启动会话 session_start(); // 获取用户名和密码 $username = $_POST['username']; $password = $_POST['password']; // 将用户名和密码保存到Session中 $_SESSION['username'] = $username; $_SESSION['password'] = $password; echo '生成的Session ID为:' . session_id() . '<hr>'; // 生成的Session ID为:q738a9r9i2knp06ctb08gtu16b echo "Session中的username值为:" . $_SESSION['username'] . '<hr>'; // Session中的username值为:test
由于Session的设计原理,生成Session后会返回给客户端一个Session ID,待客户端下次访问时会携带该Session ID且与存储于服务端的Session文件名(文件名即为Session ID)比较,若文件存在则证明该用户身份正确。需要注意的是,Session ID默认会在浏览器关闭后失效。
因此我们可以根据Session ID尝试寻找被存储的Session文件并查看其文件内容:
-
$_Files
该全局变量访问&存储经POST方法上传的文件信息,如文件名、MIME类型、文件大小等信息。
什么是MIME类型?
MIME:Multipurpose Internet Mail Extensions,也称媒体类型,为互联网通信中表示文件内容的类型。最初于电子邮件系统中被用于对不同类型文件的正确接收&发送,后被广泛应用至Http等网络协议中。
demo:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>文件上传</title> </head> <body> <h2>文件上传表单</h2> <form action="GlobalDemo5.php" method="post" enctype="multipart/form-data"> <label for="file">选择文件:</label> <input type="file" name="file" id="file" required><br><br> <button type="submit">上传文件</button> </form> </body> </html> <?php echo '<br>'; echo "被上传文件的临时存储路径为:".$_FILES['file']['tmp_name'].'<hr>'; echo "被上传文件名为:".$_FILES['file']['name'].'<hr>'; echo "被上传文件大小为:".$_FILES['file']['size'].'<hr>'; echo "被上传文件类型为:".$_FILES['file']['type']; // 被上传文件的临时存储路径为:C:\Windows\phpB828.tmp, // --> 临时路径可由php.ini中upload_tmp_dir指定,若未指定则采取系统默认 // 被上传文件名为:1.png // 被上传文件大小为:376394 // 被上传文件类型为:image/png
-
$_ENV
&$_SERVER
其中$_ENV
存储了系统环境变量信息,$_SERVER
存储了服务器&执行环境信息。
demo:<?php // 若想使$_ENV全局变量能访问当前系统环境变量, // 需在php.ini文件中启用 variables_order Default Value: "EGPCS" echo "PHP脚本路径为:".$_ENV['SCRIPT_NAME'].'<hr>'; echo "PHP配置文件路径为:".$_ENV['PHPRC'].'<hr>'; echo "PHP运行环境为:".$_ENV['SERVER_SOFTWARE'].'<hr>'; // PHP脚本路径为:/global/GlobalDemo6.php // PHP配置文件路径为:D:/Lan/IDE/Extra/PHPkaifa/phpstudy_pro/Extensions/php/php7.3.4nts // PHP运行环境为:Apache/2.4.39 (Win64) OpenSSL/1.1.1b mod_fcgid/2.3.9a mod_log_rotate/1.02 echo "访问服务端的客户端UA头为:".$_SERVER['HTTP_USER_AGENT'].'<hr>'; echo "访问服务端的客户端ip为:".$_SERVER['REMOTE_ADDR'].'<hr>'; echo "访问服务端的客户端port为:".$_SERVER['REMOTE_PORT'].'<hr>'; echo "服务端的ip为:".$_SERVER['SERVER_ADDR'].'<hr>'; // 访问服务端的客户端UA头为:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 // 访问服务端的客户端ip为:192.168.2.106 // 访问服务端的客户端port为:63323 // 服务端的ip为:192.168.2.106
感觉下来,这两个全局变量所存储的信息内容差不多,
$_ENV
能访问&存储的信息,$_SERVER
也可以。只不过$_ENV
默认无法访问到系统环境变量信息,需在php.ini文件中开启。 -
实验
好了,你已经学会php Web开发了,现在开始代码审计吧(不是。-
实验案例:DuomiCms
由于笔者比较菜(😭),因此本文的审计流程为“对着答案出题”。想了解完整代审流程可参考这位大佬的文章:https://blog.csdn.net/qq_59023242/article/details/135080259
-
审计目标:绕过账户&密码验证,实现后台登录。
这里直接给出答案,也就是payload。/interface/comment.php?_SESSION[duomi_admin_id]=19&_SESSION[duomi_group_id]=1&_SESSION[duomi_admin_name]=sj
-
首先分析一下这段payload,/interface/comment.php为访问路径,? 后为传递参数,该参数的含义为为全局变量_SESSION所存储内容重新赋值。
当输入错误账户&密码后,生成的Session文件内容为:
输入payload后,Session文件内容被覆盖为:
再尝试缩减该payload为:?_SESSION[duomi_group_id]=1&_SESSION[duomi_admin_id]=19
发现此时仍可绕过账户&密码验证,可以推测Session中duomi_group_id & duomi_admin_id的值是绕过前台登陆的关键,但又因为duomi_admin_id的值为我随意赋的,因此我们需要重点关注的仅剩duomi_group_id字段。ok,接下来看代码。
-
找到intereface/comment.php文件,搜索duomi_group_id发现无结果,但在文件开头发现该文件包含了common.php 与 core.class.php的脚本文件。
-
在common.php文件中仍未找到duomi_group_id,但发现了:
<?php ... foreach(Array('_GET','_POST','_COOKIE') as $_request) { foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v); } # 该代码将来自Http请求数据中的 键 & 值 分别存储至 $_k & $_v 变量中 # 也就是说该文件有接收&处理用户提交数据的功能
虽然得知了common.php文件具有接收&处理用户所传数据的功能,但文件中并未发现duomi_group_id,也就是说对于duomi_group_id数据的处理并不在该文件中。推测该文件可能仅是对接收数据的初步处理,更深一步的处理可能在别的且包含该文件的文件中。
-
全局搜索包含/common.php的文件
由于后台登录路径为/admin,而我们要实现后台登录绕过,因此优先看admin目录下文件。虽然仍未找到对duomi_group_id的处理,但在/admin/config.php文件下找到了:<?php ... //检验用户登录状态 $cuserLogin = new userLogin(); if($cuserLogin->getUserID()==-1) { header("location:login.php?gotopage=".urlencode($EkNowurl)); exit(); } # 由注释可知,该代码的功能为检验用户登录状态,以及一个对所返回userid的判断 # 但我们仍不知其所返回userid是否与duomi_group_id相关,因此追踪一下userLogin对象
-
查看duomiphp/check.admin.php文件,也就是定义userLogin对象的文件,成功找到了定义duomi_group_id的代码。
并在49-54行中,发现了将keepgroupidTag(doumi_gourp_id)赋值给了groupid,也就是说传递给_SESSION[duomi_group_id]的值最终被groupid所接收,但在该check.admin.php文件中发现并未处理获取到的groupid,因此还需找处理groupid的代码。
-
全局搜索groupid,最终在/admin/admin_manager.php文件中找到对groupid的处理逻辑。
也就验证了为什么payload的写法为:/interface/comment.php?_SESSION[duomi_admin_id]=19&_SESSION[duomi_group_id]=1&_SESSION[duomi_admin_name]=sj
-
试验结束!
ps:接下来如果还有类似的代审实验,笔者应该不会再记录了,因为记得有点像流水账,有很多细节我自己都不太懂,还是不误导大家了。这里还是建议有一定开发底子后再接触代审。
-