Skip to content
Advertisement

Is it possible to break out of a restricted (custom) shell?

Not sure if this is the right place to ask.

Say I write a shell that takes stdin input, filters this input so let’s say only certain commands like

  • ls (list contents of binary directory and subdirectory)
  • update (git clone)
  • build (go build)
  • test (go test)
  • start (systemctl start this.service only)
  • stop (systemctl stop this.service only)
  • running (is the binary being executed and with how many GOMAXPROCS?)
  • usage (memory, cpu usage)
  • gensvc (generate .service file)
  • exit (leave shell/logout)

work, you guessed it, I’m trying to give a user only very limited maintenance access over ssh.

Say I’m careful with (I’d write it in Go anyway using bufio.Scanner)

Is there any way to stop the running shell and execute /bin/sh or similar or any way to get around this shell?

The idea is a user should push their stuff via git to a bare repo, this repo is cloned to the filesystem to a certain directory, then go build is called and the binary is ran with a systemd .service file that is generated previously.

Thinking logically, if the user is only able to write certain strings that are accepted, no there is no way. But maybe you know of one, some ctrl+z witchcraft 😉 or whatever.

The only attack surface is the input string or rather bytes. Of course the user could git push a program that builds its own shell or runs certain commands, but that’s out of scope (I would remove capabilities with systemd and restrict device access and forbid anything but the connection to the database server, private tmp and all, namespace and subnamespace it TODO)

The only problem I see is git pushing but I’m sure I could work around that in a git only mode argv and adding it to ~/.ssh/authorized_keys. something like lish gitmode and execute stdin commands if they start with git or something like it.

Example:

https://gist.github.com/dalu/ce2ef43a2ef5c390a819

Advertisement

Answer

If you’re only allowed certain commands, your “shell” will read the command, parse it and then execute it then you should be fine, unless I misunderstood it.

Go “memory” can’t be executed, not without you doing some nasty hacks with assembly anyway, so you don’t have to worry about shell injection.

Something along these lines should be safe:

func getAction() (name string, args []string) {
    // read stdin to get the command of the user
}

func doAction() {
    for {
        action, args := getAction()
        switch action {
            case "update": //let's assume the full command is: update https://repo/path.git
                if len(args) != 1 {
                    //error
                }
                out, err := exec.Command("/usr/bin/git", "clone", "--recursive", args[0]).CombinedOutput()
                // do stuff with out and err
        }
    }
} 
User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement