Misc. fixes to end-of-session conn handling. Outstanding bug w/client chaff enabled & truncated client data

This commit is contained in:
Russ Magee 2018-08-05 21:43:21 -07:00
parent 5920e06748
commit 00e03c1d54
3 changed files with 102 additions and 69 deletions

View file

@ -439,7 +439,12 @@ func (hc Conn) Read(b []byte) (n int, err error) {
log.Printf("[TermSize pkt: rows %v cols %v]\n", hc.Rows, hc.Cols) log.Printf("[TermSize pkt: rows %v cols %v]\n", hc.Rows, hc.Cols)
hc.WinCh <- WinSize{hc.Rows, hc.Cols} hc.WinCh <- WinSize{hc.Rows, hc.Cols}
} else if ctrlStatOp == CSOExitStatus { } else if ctrlStatOp == CSOExitStatus {
if len(payloadBytes) > 0 {
*hc.closeStat = uint8(payloadBytes[0]) *hc.closeStat = uint8(payloadBytes[0])
} else {
log.Println("[truncated payload, cannot determine CSOExitStatus]")
*hc.closeStat = 99
}
} else { } else {
hc.dBuf.Write(payloadBytes) hc.dBuf.Write(payloadBytes)
//log.Printf("hc.dBuf: %s\n", hex.Dump(hc.dBuf.Bytes())) //log.Printf("hc.dBuf: %s\n", hex.Dump(hc.dBuf.Bytes()))
@ -450,12 +455,16 @@ func (hc Conn) Read(b []byte) (n int, err error) {
hTmp := hc.rm.Sum(nil)[0:4] hTmp := hc.rm.Sum(nil)[0:4]
log.Printf("<%04x) HMAC:(i)%s (c)%02x\r\n", decryptN, hex.EncodeToString([]byte(hmacIn[0:])), hTmp) log.Printf("<%04x) HMAC:(i)%s (c)%02x\r\n", decryptN, hex.EncodeToString([]byte(hmacIn[0:])), hTmp)
if *hc.closeStat == 99 {
log.Println("[cannot verify HMAC]")
} else {
// Log alert if hmac didn't match, corrupted channel // Log alert if hmac didn't match, corrupted channel
if !bytes.Equal(hTmp, []byte(hmacIn[0:])) /*|| hmacIn[0] > 0xf8*/ { if !bytes.Equal(hTmp, []byte(hmacIn[0:])) /*|| hmacIn[0] > 0xf8*/ {
fmt.Println("** ALERT - detected HMAC mismatch, possible channel tampering **") fmt.Println("** ALERT - detected HMAC mismatch, possible channel tampering **")
_, _ = hc.c.Write([]byte{CSOHmacInvalid}) _, _ = hc.c.Write([]byte{CSOHmacInvalid})
} }
} }
}
retN := hc.dBuf.Len() retN := hc.dBuf.Len()
if retN > len(b) { if retN > len(b) {

View file

@ -30,7 +30,7 @@ type cmdSpec struct {
who []byte who []byte
cmd []byte cmd []byte
authCookie []byte authCookie []byte
status int // though UNIX shell exit status is uint8, os.Exit() wants int status int // UNIX exit status is uint8, but os.Exit() wants int
} }
var ( var (
@ -86,13 +86,13 @@ func parseNonSwitchArgs(a []string, dp string) (user, host, port, path string, i
fancyPort = dp fancyPort = dp
} }
if fancyPath == "" { //if fancyPath == "" {
fancyPath = "." // fancyPath = "."
} //}
if i == len(a)-1 { if i == len(a)-1 {
isDest = true isDest = true
fmt.Println("isDest") fmt.Println("remote path isDest")
} }
fmt.Println("fancyArgs: user:", fancyUser, "host:", fancyHost, "port:", fancyPort, "path:", fancyPath) fmt.Println("fancyArgs: user:", fancyUser, "host:", fancyHost, "port:", fancyPort, "path:", fancyPath)
} else { } else {
@ -118,6 +118,7 @@ func main() {
version := "0.1pre (NO WARRANTY)" version := "0.1pre (NO WARRANTY)"
var vopt bool var vopt bool
var dbg bool var dbg bool
var shellMode bool // if true act as shell, else file copier
var cAlg string var cAlg string
var hAlg string var hAlg string
var server string var server string
@ -154,6 +155,7 @@ func main() {
// hkexsh accepts a command (-x) but not // hkexsh accepts a command (-x) but not
// a srcpath (-r) or dstpath (-t) // a srcpath (-r) or dstpath (-t)
flag.StringVar(&cmdStr, "x", "", "command to run (default empty - interactive shell)") flag.StringVar(&cmdStr, "x", "", "command to run (default empty - interactive shell)")
shellMode = true
} // else { } // else {
//// hkexcp accepts srcpath (-r) and dstpath (-t), but not //// hkexcp accepts srcpath (-r) and dstpath (-t), but not
//// a command (-x) //// a command (-x)
@ -162,19 +164,19 @@ func main() {
//} //}
flag.Parse() flag.Parse()
fancyUser, fancyHost, fancyPort, fancyPath, pathIsDest, otherArgs := tmpUser, tmpHost, tmpPort, tmpPath, pathIsDest, otherArgs :=
parseNonSwitchArgs(flag.Args(), defPort /* defPort */) parseNonSwitchArgs(flag.Args(), defPort /* defPort */)
fmt.Println("otherArgs:", otherArgs) fmt.Println("otherArgs:", otherArgs)
//fmt.Println("fancyHost:", fancyHost) //fmt.Println("tmpHost:", tmpHost)
fmt.Println("fancyPath:", fancyPath) //fmt.Println("tmpPath:", tmpPath)
if fancyUser != "" { if tmpUser != "" {
altUser = fancyUser altUser = tmpUser
} }
if fancyHost != "" { if tmpHost != "" {
server = fancyHost + ":" + fancyPort server = tmpHost + ":" + tmpPort
//fmt.Println("fancyHost sets server to", server) //fmt.Println("tmpHost sets server to", server)
} }
if fancyPath != "" { if tmpPath != "" {
// -if pathIsSrc && len(otherArgs) > 1 ERROR // -if pathIsSrc && len(otherArgs) > 1 ERROR
// -else flatten otherArgs into space-delim list => copySrc // -else flatten otherArgs into space-delim list => copySrc
if pathIsDest { if pathIsDest {
@ -183,17 +185,18 @@ func main() {
copySrc = append(copySrc, v...) copySrc = append(copySrc, v...)
} }
fmt.Println(">> copySrc:", string(copySrc)) fmt.Println(">> copySrc:", string(copySrc))
copyDst = fancyPath copyDst = tmpPath
} else { } else {
if len(otherArgs) > 1 { if len(otherArgs) > 1 {
log.Fatal("ERROR: cannot specify more than one dest path for copy") log.Fatal("ERROR: cannot specify more than one dest path for copy")
} }
copySrc = []byte(fancyPath) copySrc = []byte(tmpPath)
} }
} }
// Do some more option consistency checks
//fmt.Println("server finally is:", server) //fmt.Println("server finally is:", server)
if flag.NFlag() == 0 && server == "" { if flag.NFlag() == 0 && server == "" {
flag.Usage() flag.Usage()
os.Exit(0) os.Exit(0)
@ -208,12 +211,33 @@ func main() {
log.Fatal("incompatible options -- either cmd (-x) or copy ops but not both") log.Fatal("incompatible options -- either cmd (-x) or copy ops but not both")
} }
//-------------------------------------------------------------------
// Here we have parsed all options and can now carry out
// either the shell session or copy operation.
_ = shellMode
if dbg { if dbg {
log.SetOutput(os.Stdout) log.SetOutput(os.Stdout)
} else { } else {
log.SetOutput(ioutil.Discard) log.SetOutput(ioutil.Discard)
} }
// We must make the decision about interactivity before Dial()
// as it affects chaffing behaviour. 20180805
if len(cmdStr) == 0 {
op = []byte{'s'}
isInteractive = true
} else {
op = []byte{'c'}
// non-interactive cmds may complete quickly, so chaff earlier/faster
// to help ensure there's some cover to the brief traffic.
// (ignoring cmdline values)
//!DEBUG
//chaffEnabled = false
chaffFreqMin = 2
chaffFreqMax = 10
}
conn, err := hkexnet.Dial("tcp", server, cAlg, hAlg) conn, err := hkexnet.Dial("tcp", server, cAlg, hAlg)
if err != nil { if err != nil {
fmt.Println("Err!") fmt.Println("Err!")
@ -226,6 +250,7 @@ func main() {
// TODO: send flag to server side indicating this // TODO: send flag to server side indicating this
// affects shell command used // affects shell command used
var oldState *hkexsh.State var oldState *hkexsh.State
if shellMode {
if isatty.IsTerminal(os.Stdin.Fd()) { if isatty.IsTerminal(os.Stdin.Fd()) {
oldState, err = hkexsh.MakeRaw(int(os.Stdin.Fd())) oldState, err = hkexsh.MakeRaw(int(os.Stdin.Fd()))
if err != nil { if err != nil {
@ -235,6 +260,7 @@ func main() {
} else { } else {
log.Println("NOT A TTY") log.Println("NOT A TTY")
} }
}
var uname string var uname string
if len(altUser) == 0 { if len(altUser) == 0 {
@ -244,18 +270,6 @@ func main() {
uname = altUser uname = altUser
} }
if len(cmdStr) == 0 {
op = []byte{'s'}
isInteractive = true
} else {
op = []byte{'c'}
// non-interactive cmds may complete quickly, so chaff earlier/faster
// to help ensure there's some cover to the brief traffic.
// (ignoring cmdline values)
chaffFreqMin = 2
chaffFreqMax = 10
}
if len(authCookie) == 0 { if len(authCookie) == 0 {
fmt.Printf("Gimme cookie:") fmt.Printf("Gimme cookie:")
ab, err := hkexsh.ReadPassword(int(os.Stdin.Fd())) ab, err := hkexsh.ReadPassword(int(os.Stdin.Fd()))
@ -288,33 +302,26 @@ func main() {
conn.SetupChaff(chaffFreqMin, chaffFreqMax, chaffBytesMax) // enable client->server chaffing conn.SetupChaff(chaffFreqMin, chaffFreqMax, chaffBytesMax) // enable client->server chaffing
if chaffEnabled { if chaffEnabled {
conn.EnableChaff() conn.EnableChaff()
//defer conn.DisableChaff()
//defer conn.ShutdownChaff()
} }
defer conn.DisableChaff()
defer conn.ShutdownChaff()
//client reader (from server) goroutine //client reader (from server) goroutine
//Read remote end's stdout
wg.Add(1) wg.Add(1)
go func() { go func() {
// By deferring a call to wg.Done(), // By deferring a call to wg.Done(),
// each goroutine guarantees that it marks // each goroutine guarantees that it marks
// its direction's stream as finished. // its direction's stream as finished.
//
// Whichever direction's goroutine finishes first
// will call wg.Done() once more, explicitly, to
// hang up on the other side, so that this client
// exits immediately on an EOF from either side.
defer wg.Done()
// io.Copy() expects EOF so this will // io.Copy() expects EOF so normally this will
// exit with inerr == nil // exit with inerr == nil
_, inerr := io.Copy(os.Stdout, conn) _, inerr := io.Copy(os.Stdout, conn)
if inerr != nil { if inerr != nil {
if inerr.Error() != "EOF" {
fmt.Println(inerr) fmt.Println(inerr)
_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort. _ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
os.Exit(1) os.Exit(1)
} }
}
rec.status = int(conn.GetStatus()) rec.status = int(conn.GetStatus())
log.Println("rec.status:", rec.status) log.Println("rec.status:", rec.status)
@ -322,41 +329,43 @@ func main() {
if isInteractive { if isInteractive {
log.Println("[* Got EOF *]") log.Println("[* Got EOF *]")
_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort. _ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
wg.Done()
//os.Exit(rec.status)
} }
wg.Done()
}() }()
// Only look for data from stdin to send to remote end
// for interactive sessions.
if isInteractive { if isInteractive {
handleTermResizes(conn) handleTermResizes(conn)
// client writer (to server) goroutine // client writer (to server) goroutine
// Write local stdin to remote end
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
// Copy() expects EOF so this will // Copy() expects EOF so this will
// exit with outerr == nil // exit with outerr == nil
//!_, outerr := io.Copy(conn, os.Stdin) //!_, outerr := io.Copy(conn, os.Stdin)
_, outerr := func(conn *hkexnet.Conn, r io.Reader) (w int64, e error) { _, outerr := func(conn *hkexnet.Conn, r io.Reader) (w int64, e error) {
return io.Copy(conn, r) w, e = io.Copy(conn, r)
return w, e
}(conn, os.Stdin) }(conn, os.Stdin)
if outerr != nil { if outerr != nil {
log.Println(outerr) log.Println(outerr)
if outerr.Error() != "EOF" {
fmt.Println(outerr) fmt.Println(outerr)
_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort. _ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
os.Exit(255) os.Exit(255)
} }
}
log.Println("[Sent EOF]") log.Println("[Sent EOF]")
wg.Done() // client hung up, close WaitGroup to exit client wg.Done()
}() }()
} }
// Wait until both stdin and stdout goroutines finish // Wait until both stdin and stdout goroutines finish
wg.Wait() wg.Wait()
conn.DisableChaff()
conn.ShutdownChaff()
_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort. _ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
os.Exit(rec.status) os.Exit(rec.status)

View file

@ -18,6 +18,7 @@ import (
"os/user" "os/user"
"runtime" "runtime"
"strings" "strings"
"sync"
"syscall" "syscall"
"blitter.com/go/goutmp" "blitter.com/go/goutmp"
@ -83,6 +84,7 @@ func runCmdAs(who string, cmd string, conn hkex.Conn) (err error) {
// //
// Uses ptys to support commands which expect a terminal. // 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 int) {
var wg sync.WaitGroup
u, _ := user.Lookup(who) u, _ := user.Lookup(who)
var uid, gid uint32 var uid, gid uint32
fmt.Sscanf(u.Uid, "%d", &uid) fmt.Sscanf(u.Uid, "%d", &uid)
@ -135,15 +137,16 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexnet.Conn, cha
log.Printf("[Setting term size to: %v %v]\n", sz.Rows, sz.Cols) log.Printf("[Setting term size to: %v %v]\n", sz.Rows, sz.Cols)
pty.Setsize(ptmx, &pty.Winsize{Rows: sz.Rows, Cols: sz.Cols}) pty.Setsize(ptmx, &pty.Winsize{Rows: sz.Rows, Cols: sz.Cols})
} }
fmt.Println("*** WinCh goroutine done ***")
}() }()
// Copy stdin to the pty.. (bgnd goroutine) // Copy stdin to the pty.. (bgnd goroutine)
go func() { go func() {
_, e := io.Copy(ptmx, conn) _, e := io.Copy(ptmx, conn)
if e != nil { if e != nil {
log.Printf("** std->pty ended **\n") log.Println("** stdin->pty ended **:", e.Error())
return
} }
fmt.Println("*** stdin->pty goroutine done ***")
}() }()
if chaffing { if chaffing {
@ -153,17 +156,26 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexnet.Conn, cha
defer conn.ShutdownChaff() defer conn.ShutdownChaff()
// ..and the pty to stdout. // ..and the pty to stdout.
// This may take some time exceeding that of the
// actual command's lifetime, so the c.Wait() below
// must synchronize with the completion of this goroutine
// to ensure all stdout data gets to the client before
// connection is closed.
wg.Add(1)
go func() { go func() {
defer wg.Done()
_, e := io.Copy(conn, ptmx) _, e := io.Copy(conn, ptmx)
if e != nil { if e != nil {
log.Printf("** pty->stdout ended **\n") log.Println("** pty->stdout ended **:", e.Error())
return //wg.Done() //!return
} }
// The above io.Copy() will exit when the command attached // The above io.Copy() will exit when the command attached
// to the pty exits // to the pty exits
fmt.Println("*** pty->stdout goroutine done ***")
}() }()
if err := c.Wait(); err != nil { if err := c.Wait(); err != nil {
fmt.Println("*** c.Wait() done ***")
if exiterr, ok := err.(*exec.ExitError); ok { if exiterr, ok := err.(*exec.ExitError); ok {
// The program has exited with an exit code != 0 // The program has exited with an exit code != 0
@ -177,6 +189,9 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexnet.Conn, cha
} }
} }
} }
wg.Wait() // Wait on pty->stdout completion to client
//conn.DisableChaff()
//conn.ShutdownChaff()
} }
return return
} }