记一次Redis被攻击的事件
最近几个月非常忙,所以很少有时间写博客,这几天终于闲了一些,于是就在整理平时的一些笔记。恰好这几天Redis服务器发生了问题,就记录一下。
我司有两款分别是2B和2C的App,类似于阿里旺旺的卖家版和买家版,里面有一个聊天的功能模块。双方可以通过这个功能聊天。内部通讯使用了环信,只是将本地账号和环信账号进行了关联。其他的信息,比如用户基本信息,好友关系,群组关系等存在Redis中,为防止Redis出现问题导致数据丢失(尽管配置了持久化),同时使用消息队列将数据写入SQLServer中进行了冗余。这是一种Redis的典型使用场景,从速度和效率上满足要求。
线上环境一直运行正常,但是在上周日(一个本该休息的日子),领导打电话过来说线上环境的用户登录不了,无法聊天,没有群相关信息。我想估计是Redis出现了问题,让领导不要着急,先让运维看看服务是否还在运行,不行的话,把Redis重启一下,因为之前设置过持久化,数据应该不会丢失。于是继续陪夫人吃饭,看电影。
到了晚上,还是打电话过来说有问题,没办法只有来公司一趟。打开机器用RedisClient连Linux环境上的Redis,发现里面的数据全没了,只有几个新注册的孤零零的用户在里面,我当时惊呆了,以为是运维那边没搞好,是重启的整个服务器而不是重启的Redis,可能是Redis没有及时保存把数据给弄丢了。看到现象之后跟领导电话告知目前的现象和建议的解决方案,在授权下,重新把用户相关数据从SQLServer同步到了Redis中,关键的数据还好没丢失,然后让测试简单测试了一下,一切正常就没有太在意。而且由于Redis是安装在Linux上的,是由运维同事维护的,出问题了我这边也查不了,于是就回去了。
但是事情远没有那么简单,星期一的时候,领导又打电话过来说聊天又不能用了,比较急说要赶紧处理,我说好,于是不紧不慢的收拾好出门去公司。心里非常不爽,前段时间加班太猛,周一周二全公司开发都调休放假的。就我一个开发的来到公司,打开远程又发现Redis里面的数据全没了。于是又从SQLServer把数据同步到了Redis里,然后检查代码,把除了和聊天相关之外的其他逻辑,比如Redis定时同步服务相关的可能会影响到的地方都暂停了。因为前段时间做了一次重构,担心是代码导致数据丢失,搞完了之后就回去了。
然而好景不长,消停了一天没出问题。今天早上过来上班,领导走过来语重心长的说,聊天又登不上了,上去一看,Redis里面的数据又没了。正好运维的同事也在,也就一起找下存在的漏洞和可能的原因。
原因

然后意识到是不是Redis没有设置密码访问,导致产生了这个漏洞被利用和攻击了。于是自查,发现了如下内容:

里边多了一个名为crackit的字符串,并且db0这个库是没有使用了的,现有的数据和逻辑都在db1上,刚打开的时候,db1是空的,上图是我重新同步过数据之后的结构。
对比乌云报的漏洞的最后一部分:

这简直就是一模一样啊。并且运维那边发现redis的持久化文件被写到了authorized_keys文件中:

确认被攻击之后,于是开始着手修复。之前在开发的时候,其实是有考虑给Redis加访问密码的,不知道后来太忙了,竟然给忘了,也想着公司比较小,应该不会被黑之类的,没想到这次就撞上了。
解决方法
解决方法当然是给Redis加密码,然后在访问的时候,设置密码访问。
最初,访问Redis的客户端我们使用的是ServiceStack.Redis,我之前也写过几篇Redis相关的文章。 由于考虑到授权的原因,使用的是2.0版本的dll,在其API中,从签名看,没有地方可以设置密码:

这个地方只能设置主机名称和端口号。
于是在使用中,比如下面这个删除群组的操作,RedisHelper类是对ServiceStack.Redis的一个封装类,只需要设置主机和端口号:
public static bool DeleteGroup(string groupId)
{
lock (lockDeleteGroup)
{
bool result = true;
using (RedisHelper redis = new RedisHelper(HOST, PORT))
{
var group = redis.GetWhereObj<GroupTable>(GROUPTABLE, x => x.GroupId == groupId);
group.Is_Deleted = true;
result = redis.Update<GroupTable>(GROUPTABLE, x => x.GroupId == groupId, group);
}
return result;
}
}
后面为了安全考虑,需要给Redis设置个密码,于是下载了最新版本的4.0的ServiceStack.Redis,这个是商业化版本的,使用中发现,是有限制的,在其下载页面最下角也有说明:

每小时只能请求6000次,这显然不能满足要求。除了以上限制之外,在使用过程中也出现过一些相当诡异的问题,比如通过Id查找的时候,其只能在1k条范围内进行查询等等,当然,也有可能是因为使用不正确,考虑到以上原因,加之之前的数据组织和结构设计不合理,于是决定重构。
重构的时候,就直接换了另一个C#客户端,StackExchange.Redis。
private static ConnectionMultiplexer _redis;
private static IDatabase _db;
private static IServer _server;
private static bool needSave = false;
private void Init(string host, int port, string pwd, int database)
{
var options = ConfigurationOptions.Parse(host + ":" + port);
options.SyncTimeout = int.MaxValue;
options.AllowAdmin = true;
if (!string.IsNullOrEmpty(pwd))
{
options.Password = pwd;
}
if (_redis == null)
_redis = ConnectionMultiplexer.Connect(options);
if (_server == null)
_server = _redis.GetServer(host + ":" + port);
if (_db == null)
_db = _redis.GetDatabase(database);
needSave = false;
}
这里面,可以直接对options对象设置Password属性。于是对该对象进行了包装,后面使用Redis可以这样,在构造函数里边传入PWD即可,比如下面判断用户是否存在的接口:
public static bool HasShopUser(string userName)
{
bool hasUser = false;
ShopUserEntity userEntity;
userEntity = null;
using (RedisHelper redis = new RedisHelper(HOST, PORT, PWD))
{
userEntity = redis.GetShopUserInfo(userName);
}
if (userEntity != null)
{
hasUser = true;
}
return hasUser;
}
在替换Redis客户端访问类的时候,顺便对之前Redis里面的数据结构进行了一次重构,经过这次重构,速度提升很明显。于是,于是就直接弄到正式环境然后就把设置密码这个事情给忘记了。
当然,部署有Redis的Linux服务器也按照漏洞建议做了登陆限制和修复。
总结
其实这是一个很低级的错误,访问Redis没有设置密码(当然也可能是Redis所在的Linux服务器本身没有对登录做限制),也感谢有乌云这么好的一个平台,能够及时发现系统的问题和漏洞,避免出现更大的损失。作为一个码农其实不应该抱有这样的侥幸心理,就像墨菲定律说的那样“会出错的,终将会出错“ 。最后,希望这篇文章能给大家一个提醒和一些帮助。
记一次Redis被攻击的事件的更多相关文章
- Redis被攻击
记一次Redis被攻击的事件 最近几个月非常忙,所以很少有时间写博客,这几天终于闲了一些,于是就在整理平时的一些笔记.恰好这几天Redis服务器发生了问题,就记录一下. 我司有两款分别是2B和2C ...
- 如何利用redis key过期事件实现过期提醒
https://blog.csdn.net/zhu_tianwei/article/details/80169900 redis自2.8.0之后版本提供Keyspace Notifications功能 ...
- SpringBoot实现监听redis key失效事件
需求: 处理订单过期自动取消,比如下单30分钟未支付自动更改订单状态 解决方案1: 可以利用redis天然的key自动过期机制,下单时将订单id写入redis,过期时间30分钟,30分钟后检查订单状态 ...
- Redis实现之事件
事件 Redis服务器是一个事件驱动程序,服务器需要处理以下两类事情: 文件事件(file event):Redis服务器通过套接字与客户端(或者其他Redis服务器)进行连接,而文件事件就是服务器对 ...
- Redis Key过期事件
解决方案1: 可以利用redis天然的key自动过期机制,下单时将订单id写入redis,过期时间30分钟,30分钟后检查订单状态,如果未支付,则进行处理但是key过期了redis有通知吗?答案是肯定 ...
- 记一次 Confluence 被攻击事件
故事开始 4 月 14 日,星期天,天气不好,呆在家玩 LOL,正 Happy 的时候同事打电话给我,说 Confluence 看文档的时候挂了,报错:502. 一寻思,不就挂了吗,小意思,重启呗,于 ...
- 记Windows服务器Redis 6379被攻击 被设置主从模式同步项目数据
在工作中第一次经历被攻击,我是一个前端,同时复负责维护一个已上线的项目,在最近一段时间小程序与后台经常出现这个报错, 搜了下说我的从机是只读模式,不能写入,问了同事得知这个项目是单机模式,根本不存在从 ...
- Redis源码 - 事件管理
Redis 的事件分类 分类 描述 定时器 线程内定时响应,更新缓存时间.关闭非活动的客户端连接等等 pipe 线程间通信,用于其他线程通知主线程退出aeApiPoll() unixsocket 本地 ...
- 公司外网测试服务器 redis 被攻击复盘
最近 公司外网的测试的 redis 服务器被攻击,最开始是用 docker 搭建的 直接裸奔在外网,任何域名都可以通过 ip+6379来访问,最开始想的是测试服务器也没有啥,后面直接就被人登陆进去改了 ...
随机推荐
- CoreCRM 开发实录——Travis-CI 实现 .NET Core 程度在 macOS 上的构建和测试 [无水干货]
上一篇文章我提到:为了使用"国货",我把 Linux 上的构建和测试委托给了 DaoCloud,而 Travis-CI 不能放着不用啊.还好,这货支持 macOS 系统.所以就把 ...
- ASP.NET Core的路由[3]:Router的创建者——RouteBuilder
在<注册URL模式与HttpHandler的映射关系>演示的实例中,我们总是利用一个RouteBuilder对象来为RouterMiddleware中间件创建所需的Router对象,接下来 ...
- .NET Core的日志[1]:采用统一的模式记录日志
记录各种级别的日志是所有应用不可或缺的功能.关于日志记录的实现,我们有太多第三方框架可供选择,比如Log4Net.NLog.Loggr和Serilog 等,当然我们还可以选择微软原生的诊断框架(相关A ...
- 零OCR基础6行代码实现C#验证码识别
这两天因为工作需要,要到某个网站采集信息,一是要模拟登陆,二是要破解验证码,本想用第三方付费打码,但是想想网上免费的代码也挺多的,于是乎准备从网上撸点代码下来,谁知道,撸了好多个都不行,本人以前也没接 ...
- Spring配置文件标签报错:The prefix "XXX" for element "XXX:XXX" is not bound. .
例如:The prefix "context" for element "context:annotation-config" is not bound. 这种 ...
- [Unity3D]利用Raycast实现物体的选择与操作
本文系作者原创 转载请注明出处 如果是一个2D的平面项目或者说需要在三维空间选择一个物体时(经常表现为抓取物件),我们需要用到Raycast事件 那么首先先说说什么是Raycast 按照字面上来理解的 ...
- 关于Java中进程和线程的详解
一.进程:是程序的一次动态执行,它对应着从代码加载,执行至执行完毕的一个完整的过程,是一个动态的实体,它有自己的生命 周期.它因创建而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务而 ...
- Spring MVC初始化参数绑定
初始化参数绑定与类型转换很类似,初始化绑定时,主要是参数类型 ---单日期 在处理器类中配置绑定方法 使用@InitBinder注解 在这里首先注册一个用户编辑器 参数一为目标类型 proper ...
- [转载]MVVM模式原理分析及实践
没有找到很好的MVVM模式介绍文章,简单找了一篇,分享一下.MVVM实现了UI\UE设计师(Expression Blend 4设计界面)和软件工程师的合理分工,在SilverLight.WPF.Wi ...
- MVP初探
什么是MVP MVP是一种UI的架构模式,是MVC的一种变体,适用于基于事件驱动的应用框架.MVP中的M和V分别对应了MVC中的Model和View,而P代替了Controller,而它更多地体现在了 ...