package main import ( // "bytes" // "bufio" "bytes" "fmt" "html/template" "io" "net/http" "os" "os/exec" "os/user" "path/filepath" "strings" "regexp" ) type PageData struct { CurrentDir string CurrentUsername string CommandLog []CommandOutput } type CommandOutput struct { Command string Output string Error string } type CommandNode struct { Operator string Left *CommandNode Right *CommandNode Command string } var commandLog []CommandOutput var variables = make(map[string]string) func expandCommandSubstitution(command string, currentDir string) (string, error) { // Do not expand single quoted strings reSingleQuotes := regexp.MustCompile(`'([^']*)'`) singleQuotedStrings := reSingleQuotes.FindAllString(command, -1) placeholder := "\x00PLACEHOLDER\x00" command = reSingleQuotes.ReplaceAllLiteralString(command, placeholder) // Expand variables reVar := regexp.MustCompile(`\$(\w+)`) command = reVar.ReplaceAllStringFunc(command, func(match string) string { varName := match[1:] if val, exists := variables[varName]; exists { return val } if envVal, exists := os.LookupEnv(varName); exists { return envVal } return match }) // Restore single-quoted strings and avoid expansion inside them for _, sq := range singleQuotedStrings { command = strings.Replace(command, placeholder, sq, 1) } // expand $(...) command substitution re := regexp.MustCompile(`\$\(([^()]+)\)`) for { match := re.FindStringSubmatch(command) if match == nil { break } subCommand := strings.TrimSpace(match[1]) subOutput, err := executeCommandTree(parseCommandTree(subCommand), currentDir) if err != nil { return "", err } command = strings.Replace(command, match[0], strings.TrimSpace(subOutput), 1) } return command, nil } func setVariable(command string) bool { re := regexp.MustCompile(`^(\w+)=(.*)$`) match := re.FindStringSubmatch(command) if match == nil { return false } varName := match[1] value := strings.Trim(match[2], `"`) if strings.HasPrefix(command, "export ") { os.Setenv(varName, value) } else { variables[varName] = value } return true } func parseCommandTree(command string) *CommandNode { if strings.Contains(command, ";") { parts := strings.SplitN(command, ";", 2) return &CommandNode{ Operator: ";", Left: parseCommandTree(strings.TrimSpace(parts[0])), Right: parseCommandTree(strings.TrimSpace(parts[1])), } } else if strings.Contains(command, ">") { parts := strings.SplitN(command, ">", 2) return &CommandNode{ Operator: ">", Left: parseCommandTree(strings.TrimSpace(parts[0])), Right: &CommandNode{Command: strings.TrimSpace(parts[1])}, } } else if strings.Contains(command, "<") { parts := strings.SplitN(command, "<", 2) return &CommandNode{ Operator: "<", Left: parseCommandTree(strings.TrimSpace(parts[0])), Right: &CommandNode{Command: strings.TrimSpace(parts[1])}, } } else if strings.Contains(command, "|") { parts := strings.SplitN(command, "|", 2) return &CommandNode{ Operator: "|", Left: parseCommandTree(strings.TrimSpace(parts[0])), Right: parseCommandTree(strings.TrimSpace(parts[1])), } } else if strings.Contains(command, "&&") { parts := strings.SplitN(command, "&&", 2) return &CommandNode{ Operator: "&&", Left: parseCommandTree(strings.TrimSpace(parts[0])), Right: parseCommandTree(strings.TrimSpace(parts[1])), } } else if strings.Contains(command, "||") { parts := strings.SplitN(command, "||", 2) return &CommandNode{ Operator: "||", Left: parseCommandTree(strings.TrimSpace(parts[0])), Right: parseCommandTree(strings.TrimSpace(parts[1])), } } return &CommandNode{Command: command} } func executeCommandTree(node *CommandNode, currentDir string) (string, error) { if node == nil { return "", nil } switch node.Operator { case "&&": leftOutput, err := executeCommandTree(node.Left, currentDir) if err != nil { return "", err } if leftOutput != "" { return executeCommandTree(node.Right, currentDir) } return executeCommandTree(node.Right, currentDir) case "||": leftOutput, err := executeCommandTree(node.Left, currentDir) if err == nil && leftOutput != "" { return leftOutput, nil } return executeCommandTree(node.Right, currentDir) case "|": leftOutput, err := executeCommandTree(node.Left, currentDir) if err != nil { return "", err } cmdArgs := parseCommandWithQuotes(node.Right.Command) if len(cmdArgs) == 0 { return "", fmt.Errorf("Invalid Command") } cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) cmd.Dir = currentDir cmd.Stdin = strings.NewReader(leftOutput) var outputBuffer bytes.Buffer cmd.Stdout = &outputBuffer cmd.Stderr = &outputBuffer if err := cmd.Run(); err != nil { return "", err } return outputBuffer.String(), nil case ">": leftOutput, err := executeCommandTree(node.Left, currentDir) if err != nil { return "", err } file, err := os.Create(strings.TrimSpace(node.Right.Command)) if err != nil { return "", err } defer file.Close() _, err = file.WriteString(leftOutput) return "", err case "<": file, err := os.Open(strings.TrimSpace(node.Right.Command)) if err != nil { return "", err } defer file.Close() cmdArgs := parseCommandWithQuotes(node.Left.Command) if len(cmdArgs) == 0 { return "", fmt.Errorf("Invalid command") } cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) cmd.Dir = currentDir cmd.Stdin = file var outputBuffer bytes.Buffer cmd.Stdout = &outputBuffer cmd.Stderr = &outputBuffer if err := cmd.Run(); err != nil { return "", err } return outputBuffer.String(), nil case ";": leftOutput, _ := executeCommandTree(node.Left, currentDir) rightOutput, _ := executeCommandTree(node.Right, currentDir) return leftOutput + rightOutput, nil } return executeSimpleCommand(node.Command, currentDir).Output, nil } func executeCommand(command string, args []string, dir string) (string, error) { cmd := exec.Command(command, args...) cmd.Dir = dir output, err := cmd.CombinedOutput() return string(output), err } func executeSimpleCommand(command string, currentDir string) CommandOutput { var cmdOutput CommandOutput // args := strings.Fields(command) args := parseCommandWithQuotes(command) if len(args) == 0 { return cmdOutput } cmd := exec.Command(args[0], args[1:]...) cmd.Dir = currentDir output, err := cmd.CombinedOutput() cmdOutput.Command = command cmdOutput.Output = string(output) if err != nil { cmdOutput.Error = err.Error() } return cmdOutput } func changeDirectory(command string, args []string, currentDir *string) CommandOutput { var cmdOutput CommandOutput if len(args) == 0 { homeDir, _ := os.UserHomeDir() err := os.Chdir(homeDir) if err != nil { cmdOutput = CommandOutput { Command: command, Error: "Failed to change to home directory: " + err.Error(), } } else { cmdOutput = CommandOutput { Command: command, Output: "Changed to home directory: " + homeDir, } *currentDir = homeDir } } else { newDir := args[0] err := os.Chdir(newDir) if err != nil { cmdOutput = CommandOutput { Command: command + " " + newDir, Error: "Failed to change to directory: " + err.Error(), } } else { cmdOutput = CommandOutput { Command: command + " " + newDir, Output: "Changed to directory: " + newDir, } *currentDir = newDir } } return cmdOutput } func fileUploadHandler(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodPost { file, header, err := r.FormFile("file") if err != nil { http.Error(w, "Failed to upload file", http.StatusInternalServerError) return } defer file.Close() uploadDir := r.FormValue("uploadDir") if uploadDir == "" { uploadDir = "./" } out, err := os.Create(filepath.Join(uploadDir, header.Filename)) if err != nil { http.Error(w, "Failed to save file", http.StatusInternalServerError) return } defer out.Close() _, err = io.Copy(out, file) if err != nil { http.Error(w, "Failed to write file", http.StatusInternalServerError) return } cmdOutput := CommandOutput{Command: "upload", Output: "File saved successfully", Error: ""} // fmt.Fprintf(w, "File %s uploaded successfully to %s", header.Filename, uploadDir) commandLog = append(commandLog, cmdOutput) } } // func fileDownloadHandler(w http.ResponseWriter, r *http.Request) { // if r.Method == http.MethodPost { // file, header, err := r.FormFile("file") func processCommand(command string, currentDir string) CommandOutput { var errorMsg string // Handle variable assignments before execution if setVariable(command) { return CommandOutput{Command: command, Output: "", Error: ""} } // This handles command substitution which is $(...) in bash expandedCommand, err := expandCommandSubstitution(command, currentDir) if err != nil { errorMsg = err.Error() return CommandOutput{Command: command, Output: "", Error: errorMsg} } tree := parseCommandTree(expandedCommand) output, err := executeCommandTree(tree, currentDir) if err != nil { errorMsg = err.Error() } return CommandOutput{Command: command, Output: output, Error: errorMsg} } func parseCommandWithQuotes(command string) []string { var args []string var current strings.Builder inSingleQuote, inDoubleQuote := false, false for i := 0; i < len(command); i++ { c := command[i] switch c { case '\'': if !inDoubleQuote { inSingleQuote = !inSingleQuote continue } case '"': if !inSingleQuote { inDoubleQuote = !inDoubleQuote continue } case ' ': if !inSingleQuote && !inDoubleQuote { if current.Len() > 0 { args = append(args, current.String()) current.Reset() } continue } case '\\': // Handle escaped characters if i+1 < len(command) { next := command[i+1] if next == '"' || next == '\'' || next == '\\' { current.WriteByte(next) i++ continue } } } current.WriteByte(c) } // Add the last argument if current.Len() > 0 { args = append(args, current.String()) } return args } func handler(w http.ResponseWriter, r *http.Request) { currentDir, _ := os.Getwd() currentUser, _ := user.Current() currentUsername := currentUser.Username if r.Method == http.MethodPost { input := r.FormValue("command") parts := strings.Fields(input) if len(parts) > 0 { command := parts[0] args := parts[1:] if command == "cd" { cmdOutput := changeDirectory(command, args, ¤tDir) commandLog = append(commandLog, cmdOutput) } else { cmdOutput := processCommand(input, currentDir) commandLog = append(commandLog, cmdOutput) } } } tmpl := template.Must(template.New("index").Parse(` Go Web Shell
Current Directory: {{.CurrentDir}} {{range .CommandLog}}
gommand:$ {{.Command}}
{{if .Output}}
{{.Output}}
{{end}} {{if .Error}}
{{.Error}}
{{end}} {{end}}
{{.CurrentUsername}}:{{.CurrentDir}}$
`)) data := PageData{ CurrentDir: currentDir, CurrentUsername: currentUsername, CommandLog: commandLog, } tmpl.Execute(w, data) } func main() { http.HandleFunc("/", handler) http.HandleFunc("/upload", fileUploadHandler) fmt.Println("Starting server on :8080") http.ListenAndServe(":8080", nil) }