之前开发的东华车管OBD的数据采集端一直比较稳定,所采用的技术方案是以Netty为网络框架,以Redis作为消息队列和存储工具,进行数据采集、存储和发送,虽然经历了不少问题,但是经过一段时间的处理之后,相对来说还是比较稳定的,最长时间连续运行近一个月没有出现过任何问题,不过最近我们的服务端几乎每天都崩溃,这让我非常的纳闷,已经稳定运行了好几个月的平台从未出现过如此严重的稳定性问题,所以我也非常重视这个Bug,以最快的时间开始着手解决问题。
Bug的内容是这样的:
Could not get a resource from the pool
java.lang.NullPointerException
第一句是说,没办法从jedis连接池里获取jedis对象了,第二句是说,空指针异常了。
从错误的本身理解,我的第一反应是,坏了,是不是我的Jedis连接池没有正常回收,导致连接池中的连接数被用完了?
当然我是比较怀疑的,因为:
1.采集的车辆数据并不多,由于并发造成的连接池连接数被用完的可能性微乎其微。
2.运行了好几个月了都没有出现这种情况,在车辆没有大批量增加且车辆总数并不多的情况下,怎么可能突然就开始出现这种问题呢?
但是,Bug is Bug,该解决你就得解决,所以我就立即从连接数回收的方向去入手,看看是不是我的Jedis写的有问题。
虚惊一场
通过谷歌,我找到了别人写的Jedis服务类:
// 生成多机连接信息列表
List shards = new ArrayList();
shards.add( new JedisShardInfo("127.0.0.1", 6379) );
shards.add( new JedisShardInfo("192.168.56.102", 6379) );
// 生成连接池配置信息
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxIdle(10);
config.setMaxTotal(30);
config.setMaxWaitMillis(3*1000);
// 在应用初始化的时候生成连接池
ShardedJedisPool pool = new ShardedJedisPool(config, shards);
// 在业务操作时,从连接池获取连接
ShardedJedis client = pool.getResource();
try {
// 执行指令
String result = client.set("key-string", "Hello, Redis!");
System.out.println( String.format("set指令执行结果:%s", result) );
String value = client.get("key-string");
System.out.println( String.format("get指令执行结果:%s", value) );
} catch (Exception e) {
// TODO: handle exception
} finally {
// 业务操作完成,将连接返回给连接池if (null != client) {
pool.returnResource(client);
}
} // end of try block// 应用关闭时,释放连接池资源
pool.destroy();
我又看了看我的Jedis回收写法:
public static void close(Jedis jedis) {
try {
jedis.close();
} catch (Exception e) {
if (jedis.isConnected()) {
jedis.quit();
jedis.disconnect();
}
}
}
发现了和人家写的怎么不一样啊?
这句:
pool.returnResource(client);
我怎么没有写啊?
当时大骂自己粗心,回收方法都没写对怎么就能心安理得的发布程序呢?于是赶紧打开IDE加上这句话,可是当我在我的程序中加上上边这句代码的时候,突然发现这行代码是被遗弃的了,旁边写着提示:
Deprecated. starting from Jedis 3.0 this method will not be exposed. Resource cleanup should be done using @see redis.clients.jedis.Jedis.close()
意思是Jedis 3.0以后,这句代码已经被jedis.close()方法替代了,所以我的写法是没有问题的,之前是虚惊一场。
初见端倪
好不容易缓口气,但是依旧迷茫,既然我的回收写法是正确的,那为什么我的服务突然就无法从连接池获取连接了呢?
带着一头的雾水,我开始在服务器的Redis服务上找原因。
当时我怀疑的是,是不是Redis的服务挂了?第一反应去WIndows的服务里去看,结果发现没有挂,正常运行中。
我想既然没挂,那我连接一下看看吧。
于是就用redis-cli命令直接连接服务,连上了没问题。
然后想说 看看缓存队列中还存着哪些数据吧?
于是就打了一个命令:
lrange obdMessage 0 -1
结果Redis直接报了警:
NOAUTH Authentication required
这里突然感觉不太对劲了,怎么好好的报了这个错误呢?而且我的Redis是有密码的,不过很简单是 111111
我赶紧用命令:
auth 111111
来访问我的Redis,结果还是报错,直到这里我才发现,可能是我的Redis访问密码被人动了手脚。
着手解决
于是我重启了Redis的服务,看看重启后能不能访问Redis,结果还好,重启后Redis是可以访问了,我在查看存储的数据的过程中,突然发现好像多了一个字段:crackit。
然后我去网上一搜,出现这个Crakit就说明我的Redis肯定是被人入侵了,那么很有可能就就是我的Redis被人入侵之后,黑客改了我的Redis的密码,导致我的Redis无法使用。
入侵的漏洞:
Redis 默认情况下,会绑定在 0.0.0.0:6379,这样将会将 Redis 服务暴露到公网上,如果在没有开启认证的情况下,可以导致任意用户在可以访问目标服务器的情况下未授权访问 Redis 以及读取 Redis 的数据。攻击者在未授权访问 Redis 的情况下可以利用 Redis 的相关方法,可以成功在 Redis 服务器上写入公钥,进而可以使用对应私钥直接登录目标服务器。
入侵的特征:
Redis 可能执行过 FLUSHALL 方法,整个 Redis 数据库被清空
在 Redis 数据库中新建了一个名为 crackit(网上流传的命令指令) 的键值对,内容为一个 SSH 公钥。
在 /root/.ssh 文件夹下新建或者修改了 authorized_keys 文件,内容为 Redis 生成的 db 文件,包含上述公钥
修复建议:
1.当然就是改密码了,之前的密码太弱了,可能是不是被人家给碰上了,直接就破解了,赶紧改一个复杂一点的密码才是王道,如果你的Redis连密码都没有,那更不行了,看看我的遭遇,赶紧加上密码吧!
在Redis服务的配置中加入密码项目,默认是注释的,你要手动加入密码,打开配置文件找到:
#requirepass foobared
去掉之前的注释,并修改为所需要的密码,保存文件并重启Redis服务。
启动命令是:
redis-server --service-install redis.windows-service.conf --loglevel debug
注意 --loglevel debug ,这里是加入了日志的启动方式,也是要在配置文件中进行修改的,打开配置文件找到:
loglevel verbose
去掉注释并改为:
loglevel debug
并且由于要存放日志的话则要给存放的Redis日志启用存储路径,在配置文件中搜索”logfile“就可以了,然后给其指定路径:
logfile "Logs/redis_log.txt"
注意:这里改完后,要启动服务前,在你的Redis的安装目录的根目录下手动创建一个名为"Logs"的文件夹,如果缺少这个文件夹,启动服务的时候会报错。
2.改端口号,默认的6379端口肯定是不能用了,赶紧在redis的配置文件中修改端口号吧!
在Redis的配置文件中找到:
port 6379
改为别的未被占用的端口即可。
3.禁止一些高危的命令:
修改Redis的配置文件,加入以下几行命令:
rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command KEYS ""
rename-command CONFIG ""
rename-command EVAL ""
﹡以下的一些设置是网上的,我没用用,因为不适合我的情况,仅供参考:
4.以权限运行Redis服务
为 Redis 服务创建单独的用户和家目录,并且配置禁止登陆
5.禁止外网访问 Redis
修改Redis配置文件,添加或修改:
bind 127.0.0.1
使得 Redis 服务只在当前主机可用
总结
浅薄啊!粗心啊!一切的一切都是因为自己在服务器和Redis安全性这里不够重视而导致的问题,其实只要设置一个复杂一点的密码,估计也不会这么轻易的被入侵了,希望我的遭遇能给你带来一些启发,少走或者不走弯路,赶紧加固自己的Redis安全防御吧!