From 9ff35a69fe9a9fb1dd7a7bb1f93f74e92e389def Mon Sep 17 00:00:00 2001 From: Russ Magee Date: Thu, 6 Sep 2018 13:50:56 -0700 Subject: [PATCH] -Converted exit status to uint32 (0-255: UNIX exit codes), above for OOB (out-of-band) status -Failed auth for shell logins now returns extended code CSEBadAuth to client --- hkexnet/hkexnet.go | 34 +++++++++++++++++++++++----------- hkexsh/hkexsh.go | 31 +++++++++++++++++++++---------- hkexshd/hkexshd.go | 42 +++++++++++++++++++++++++++--------------- 3 files changed, 71 insertions(+), 36 deletions(-) diff --git a/hkexnet/hkexnet.go b/hkexnet/hkexnet.go index c5c7269..f88e5d8 100644 --- a/hkexnet/hkexnet.go +++ b/hkexnet/hkexnet.go @@ -34,6 +34,16 @@ import ( "blitter.com/go/hkexsh/herradurakex" ) +// const CSExtendedCode - extended (>255 UNIX exit status) codes +// This indicate channel-related or internal errors +const ( + CSEBadAuth = 1024 // failed login + CSETruncCSO // No CSOExitStatus in payload + CSEStillOpen // Channel closed unexpectedly + CSEExecFail // cmd.Start() (exec) failed + CSEPtyExecFail // pty.Start() (exec w/pty) failed +) + const ( CSONone = iota // No error, normal packet CSOHmacInvalid // HMAC mismatch detected on remote end @@ -77,7 +87,7 @@ type ( chaff ChaffConfig - closeStat *uint8 // close status + closeStat *uint32 // close status (CSOExitStatus) r cipher.Stream //read cipherStream rm hash.Hash w cipher.Stream //write cipherStream @@ -86,11 +96,11 @@ type ( } ) -func (hc Conn) GetStatus() uint8 { +func (hc Conn) GetStatus() uint32 { return *hc.closeStat } -func (hc *Conn) SetStatus(stat uint8) { +func (hc *Conn) SetStatus(stat uint32) { *hc.closeStat = stat //fmt.Println("closeStat:", *hc.closeStat) log.Println("closeStat:", *hc.closeStat) @@ -177,7 +187,7 @@ func Dial(protocol string, ipport string, extensions ...string) (hc *Conn, err e return nil, err } // Init hkexnet.Conn hc over net.Conn c - hc = &Conn{m: &sync.Mutex{}, c: c, closeStat: new(uint8), h: hkex.New(0, 0), dBuf: new(bytes.Buffer)} + hc = &Conn{m: &sync.Mutex{}, c: c, closeStat: new(uint32), h: hkex.New(0, 0), dBuf: new(bytes.Buffer)} hc.applyConnExtensions(extensions...) // Send hkexnet.Conn parameters to remote side @@ -205,14 +215,16 @@ func Dial(protocol string, ipport string, extensions ...string) (hc *Conn, err e hc.r, hc.rm, err = hc.getStream(hc.h.FA()) hc.w, hc.wm, err = hc.getStream(hc.h.FA()) - *hc.closeStat = 99 // open or prematurely-closed status + *hc.closeStat = CSEStillOpen // open or prematurely-closed status return } // Close a hkex.Conn func (hc *Conn) Close() (err error) { hc.DisableChaff() - hc.WritePacket([]byte{byte(*hc.closeStat)}, CSOExitStatus) + s := make([]byte, 4) + binary.BigEndian.PutUint32(s, *hc.closeStat) + hc.WritePacket(s, CSOExitStatus) err = hc.c.Close() log.Println("[Conn Closing]") return @@ -308,13 +320,13 @@ func (hl *HKExListener) Accept() (hc Conn, err error) { // Open raw Conn c c, err := hl.l.Accept() if err != nil { - hc := Conn{m: &sync.Mutex{}, c: nil, h: nil, closeStat: new(uint8), cipheropts: 0, opts: 0, + hc := Conn{m: &sync.Mutex{}, c: nil, h: nil, closeStat: new(uint32), cipheropts: 0, opts: 0, r: nil, w: nil} return hc, err } log.Println("[Accepted]") - hc = Conn{m: &sync.Mutex{}, c: c, h: hkex.New(0, 0), closeStat: new(uint8), WinCh: make(chan WinSize, 1), + hc = Conn{m: &sync.Mutex{}, c: c, h: hkex.New(0, 0), closeStat: new(uint32), WinCh: make(chan WinSize, 1), dBuf: new(bytes.Buffer)} // Read in hkexnet.Conn parameters over raw Conn c @@ -438,7 +450,7 @@ func (hc Conn) Read(b []byte) (n int, err error) { hc.WinCh <- WinSize{hc.Rows, hc.Cols} } else if ctrlStatOp == CSOExitStatus { if len(payloadBytes) > 0 { - hc.SetStatus(payloadBytes[0]) + hc.SetStatus(binary.BigEndian.Uint32(payloadBytes)) //!// If remote end is closing with an error, reply we're closing ours //!if hc.GetStatus() != 0 { //! log.Print("CSOExitStatus:", hc.GetStatus()) @@ -446,7 +458,7 @@ func (hc Conn) Read(b []byte) (n int, err error) { //!} } else { log.Println("[truncated payload, cannot determine CSOExitStatus]") - *hc.closeStat = 98 + *hc.closeStat = CSETruncCSO } } else { hc.dBuf.Write(payloadBytes) @@ -458,7 +470,7 @@ func (hc Conn) Read(b []byte) (n int, err error) { hTmp := hc.rm.Sum(nil)[0:4] log.Printf("<%04x) HMAC:(i)%s (c)%02x\r\n", decryptN, hex.EncodeToString([]byte(hmacIn[0:])), hTmp) - if *hc.closeStat > 90 { + if *hc.closeStat == CSETruncCSO { log.Println("[cannot verify HMAC]") } else { // Log alert if hmac didn't match, corrupted channel diff --git a/hkexsh/hkexsh.go b/hkexsh/hkexsh.go index 80c9e56..1e04624 100755 --- a/hkexsh/hkexsh.go +++ b/hkexsh/hkexsh.go @@ -9,6 +9,7 @@ package main import ( "bytes" + "encoding/binary" "flag" "fmt" "io" @@ -34,7 +35,7 @@ type cmdSpec struct { who []byte cmd []byte authCookie []byte - status int // UNIX exit status is uint8, but os.Exit() wants int + status uint32 // exit status (0-255 is std UNIX status) } var ( @@ -98,7 +99,7 @@ func parseNonSwitchArgs(a []string) (user, host, path string, isDest bool, other } // doCopyMode begins a secure hkexsh local<->remote file copy operation. -func doCopyMode(conn *hkexnet.Conn, remoteDest bool, files string, rec *cmdSpec) (err error, exitStatus int) { +func doCopyMode(conn *hkexnet.Conn, remoteDest bool, files string, rec *cmdSpec) (err error, exitStatus uint32) { if remoteDest { fmt.Println("local files:", files, "remote filepath:", string(rec.cmd)) @@ -161,7 +162,7 @@ func doCopyMode(conn *hkexnet.Conn, remoteDest bool, files string, rec *cmdSpec) // defined for both Unix and Windows and in both cases has // an ExitStatus() method with the same signature. if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { - exitStatus = status.ExitStatus() + exitStatus = uint32(status.ExitStatus()) log.Printf("Exit Status: %d", exitStatus) fmt.Print(stdErrBuffer) } @@ -169,7 +170,9 @@ func doCopyMode(conn *hkexnet.Conn, remoteDest bool, files string, rec *cmdSpec) } //fmt.Println("*** client->server cp finished ***") // Signal other end transfer is complete - conn.WritePacket([]byte{byte( /*255*/ rec.status)}, hkexnet.CSOExitStatus) + s := make([]byte, 4) + binary.BigEndian.PutUint32(s, rec.status) + conn.WritePacket(s, hkexnet.CSOExitStatus) _, _ = conn.Read(nil /*ackByte*/) } } else { @@ -209,7 +212,7 @@ func doCopyMode(conn *hkexnet.Conn, remoteDest bool, files string, rec *cmdSpec) // defined for both Unix and Windows and in both cases has // an ExitStatus() method with the same signature. if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { - exitStatus = status.ExitStatus() + exitStatus = uint32(status.ExitStatus()) log.Printf("Exit Status: %d", exitStatus) } } @@ -217,7 +220,7 @@ func doCopyMode(conn *hkexnet.Conn, remoteDest bool, files string, rec *cmdSpec) // return local status, if nonzero; // otherwise, return remote status if nonzero if exitStatus == 0 { - exitStatus = int(conn.GetStatus()) + exitStatus = uint32(conn.GetStatus()) } //fmt.Println("*** server->client cp finished ***") } @@ -250,7 +253,7 @@ func doShellMode(isInteractive bool, conn *hkexnet.Conn, oldState *hkexsh.State, } } - rec.status = int(conn.GetStatus()) + rec.status = uint32(conn.GetStatus()) log.Println("rec.status:", rec.status) if isInteractive { @@ -282,7 +285,7 @@ func doShellMode(isInteractive bool, conn *hkexnet.Conn, oldState *hkexsh.State, log.Println(outerr) fmt.Println(outerr) _ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort. - os.Exit(255) + os.Exit(254) } log.Println("[Sent EOF]") }() @@ -549,7 +552,15 @@ func main() { } if rec.status != 0 { - fmt.Fprintln(os.Stderr, "Remote end exited with status:", rec.status) + fmt.Fprint(os.Stderr, "Remote end ") + if rec.status == hkexnet.CSEBadAuth { + // shell exit status can't hold CSEBadAuth (uint32) + rec.status = 255 + fmt.Fprintln(os.Stderr, "replied: bad auth") + } else { + fmt.Fprintln(os.Stderr, "exited with status:", rec.status) + } + } - os.Exit(rec.status) + os.Exit(int(rec.status)) } diff --git a/hkexshd/hkexshd.go b/hkexshd/hkexshd.go index 6255c00..b9c8000 100755 --- a/hkexshd/hkexshd.go +++ b/hkexshd/hkexshd.go @@ -9,6 +9,7 @@ package main import ( "bytes" + "encoding/binary" "flag" "fmt" "io" @@ -40,7 +41,7 @@ type cmdSpec struct { /* -------------------------------------------------------------- */ // Perform a client->server copy -func runClientToServerCopyAs(who string, conn hkexnet.Conn, fpath string, chaffing bool) (err error, exitStatus int) { +func runClientToServerCopyAs(who string, conn hkexnet.Conn, fpath string, chaffing bool) (err error, exitStatus uint32) { u, _ := user.Lookup(who) var uid, gid uint32 fmt.Sscanf(u.Uid, "%d", &uid) @@ -99,7 +100,7 @@ func runClientToServerCopyAs(who string, conn hkexnet.Conn, fpath string, chaffi err = c.Start() // returns immediately if err != nil { log.Printf("Command finished with error: %v", err) - return err, 253 // !? + return err, hkexnet.CSEExecFail // !? } else { if err := c.Wait(); err != nil { //fmt.Println("*** c.Wait() done ***") @@ -111,7 +112,7 @@ func runClientToServerCopyAs(who string, conn hkexnet.Conn, fpath string, chaffi // defined for both Unix and Windows and in both cases has // an ExitStatus() method with the same signature. if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { - exitStatus = status.ExitStatus() + exitStatus = uint32(status.ExitStatus()) log.Printf("Exit Status: %d", exitStatus) } } @@ -122,7 +123,7 @@ func runClientToServerCopyAs(who string, conn hkexnet.Conn, fpath string, chaffi } // Perform a server->client copy -func runServerToClientCopyAs(who string, conn hkexnet.Conn, srcPath string, chaffing bool) (err error, exitStatus int) { +func runServerToClientCopyAs(who string, conn hkexnet.Conn, srcPath string, chaffing bool) (err error, exitStatus uint32) { u, _ := user.Lookup(who) var uid, gid uint32 fmt.Sscanf(u.Uid, "%d", &uid) @@ -179,7 +180,7 @@ func runServerToClientCopyAs(who string, conn hkexnet.Conn, srcPath string, chaf err = c.Start() // returns immediately if err != nil { log.Printf("Command finished with error: %v", err) - return err, 253 // !? + return err, hkexnet.CSEExecFail // !? } else { if err := c.Wait(); err != nil { //fmt.Println("*** c.Wait() done ***") @@ -191,7 +192,7 @@ func runServerToClientCopyAs(who string, conn hkexnet.Conn, srcPath string, chaf // defined for both Unix and Windows and in both cases has // an ExitStatus() method with the same signature. if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { - exitStatus = status.ExitStatus() + exitStatus = uint32(status.ExitStatus()) log.Printf("Exit Status: %d", exitStatus) // TODO: send stdErrBuffer to client via specific packet // type so it can inform user @@ -209,7 +210,7 @@ func runServerToClientCopyAs(who string, conn hkexnet.Conn, srcPath string, chaf // Run a command (via default shell) as a specific user // // Uses ptys to support commands which expect a terminal. -func runShellAs(who string, cmd string, interactive bool, conn hkexnet.Conn, chaffing bool) (err error, exitStatus int) { +func runShellAs(who string, cmd string, interactive bool, conn hkexnet.Conn, chaffing bool) (err error, exitStatus uint32) { var wg sync.WaitGroup u, _ := user.Lookup(who) var uid, gid uint32 @@ -248,7 +249,7 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexnet.Conn, cha // Start the command with a pty. ptmx, err := pty.Start(c) // returns immediately with ptmx file if err != nil { - return err, 0 + return err, hkexnet.CSEPtyExecFail } // Make sure to close the pty at the end. defer func() { _ = ptmx.Close() }() // Best effort. @@ -312,11 +313,11 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexnet.Conn, cha // defined for both Unix and Windows and in both cases has // an ExitStatus() method with the same signature. if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { - exitStatus = status.ExitStatus() + exitStatus = uint32(status.ExitStatus()) log.Printf("Exit Status: %d", exitStatus) } } - conn.SetStatus(uint8(exitStatus)) + conn.SetStatus(exitStatus) } wg.Wait() // Wait on pty->stdout completion to client } @@ -446,6 +447,7 @@ func main() { rec.op[0], string(rec.who), string(rec.cmd)) valid, allowedCmds := hkexsh.AuthUser(string(rec.who), string(rec.authCookie), "/etc/hkexsh.passwd") + // Security scrub for i := range rec.authCookie { rec.authCookie[i] = 0 @@ -454,6 +456,14 @@ func main() { if !valid { log.Println("Invalid user", string(rec.who)) + + // Signal other end auth failed + rec.status = hkexnet.CSEBadAuth + hc.SetStatus(hkexnet.CSEBadAuth) + s := make([]byte, 4) + binary.BigEndian.PutUint32(s, hkexnet.CSEBadAuth) + hc.WritePacket(s, hkexnet.CSOExitStatus) + hc.Write([]byte(rejectUserMsg())) return } @@ -474,7 +484,7 @@ func main() { log.Printf("[Error spawning cmd for %s@%s]\n", rec.who, hname) } else { log.Printf("[Command completed for %s@%s, status %d]\n", rec.who, hname, cmdStatus) - hc.SetStatus(uint8(cmdStatus)) + hc.SetStatus(cmdStatus) } } else if rec.op[0] == 's' { // Interactive session @@ -494,7 +504,7 @@ func main() { log.Printf("[Error spawning shell for %s@%s]\n", rec.who, hname) } else { log.Printf("[Shell completed for %s@%s, status %d]\n", rec.who, hname, cmdStatus) - hc.SetStatus(uint8(cmdStatus)) + hc.SetStatus(cmdStatus) } } else if rec.op[0] == 'D' { // File copy (destination) operation - client copy to server @@ -511,7 +521,7 @@ func main() { } else { log.Printf("[Command completed for %s@%s, status %d]\n", rec.who, hname, cmdStatus) } - hc.SetStatus(uint8(cmdStatus)) + hc.SetStatus(cmdStatus) } else if rec.op[0] == 'S' { // File copy (src) operation - server copy to client log.Printf("[Server->Client copy]\n") @@ -528,9 +538,11 @@ func main() { } else { log.Printf("[Command completed for %s@%s, status %d]\n", rec.who, hname, cmdStatus) } - hc.SetStatus(uint8(cmdStatus)) + hc.SetStatus(cmdStatus) // Signal other end transfer is complete - hc.WritePacket([]byte{byte( /*255*/ cmdStatus)}, hkexnet.CSOExitStatus) + s := make([]byte, 4) + binary.BigEndian.PutUint32(s, cmdStatus) + hc.WritePacket(s, hkexnet.CSOExitStatus) //fmt.Println("Waiting for EOF from other end.") _, _ = hc.Read(nil /*ackByte*/) //fmt.Println("Got remote end ack.")