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
work, you guessed it, I'm trying to give a user only very limited maintenance access over ssh.
Say I'm careful with \0
(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:
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
}
}
}
If you are implementing the shell yourself and directly executing the commands via exec()
or implementing them internally, then it is certainly possible to produce a secure restricted shell. If you are just superficially checking a command line before passing it on to a real shell then there will probably be edge cases you might not expect.
With that said, I'd be a bit concerned about the test
command you've listed. Is it intended to run the test suite of a Go package the user uploads? If so, I wouldn't even try to exploit the restricted shell if I was an attacker: I'd simply upload a package with tests that perform the actions I want. The same could be said for build
/start
.
Have it reviewed by a pentesting team.
People can be very creative when breaking out a sandbox of any type. Only if you never accept the user's input you can consider yourself rather safe on premises (but here any command is an input) - paper security assumptions are considered a weak to assess the software. They are similar to 'no-bug' assumptions for an algorithm on paper: as soon as you implement it, 99% of time a bug raises