Files
jpillora-chisel/server/server.go
T
Eric Sunshine a11a3dd2dd server: add reverse port forwarding restrictions
Although reverse port forwarding (sharing client ports with the server)
should not generally leak any resources from the server to the client,
the facility may nevertheless be abused if the client is able to open a
server port which is otherwise meant for some other purpose on the
server. (This might happen, for instance, if a service on the server has
crashed or becomes somehow disabled, thus freeing the port which would
otherwise be occupied by the service.)

To mitigate such potential abuse, disable reverse port forwarding by
default and introduce server option --reverse to enable it explicitly.
Additionally, subject reverse port forwarding remotes to server-side
--authfile restrictions (for instance, "^R:0.0.0.0:7000$").
2018-12-23 16:25:45 -05:00

181 lines
4.4 KiB
Go

package chserver
import (
"errors"
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"regexp"
socks5 "github.com/armon/go-socks5"
"github.com/gorilla/websocket"
"github.com/jpillora/requestlog"
"golang.org/x/crypto/ssh"
"github.com/jpillora/chisel/share"
)
// Config is the configuration for the chisel service
type Config struct {
KeySeed string
AuthFile string
Auth string
Proxy string
Socks5 bool
Reverse bool
}
// Server respresent a chisel service
type Server struct {
*chshare.Logger
connStats chshare.ConnStats
fingerprint string
httpServer *chshare.HTTPServer
reverseProxy *httputil.ReverseProxy
sessCount int32
sessions chshare.Users
socksServer *socks5.Server
sshConfig *ssh.ServerConfig
users *chshare.UserIndex
reverseOk bool
}
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool { return true },
}
// NewServer creates and returns a new chisel server
func NewServer(config *Config) (*Server, error) {
s := &Server{
httpServer: chshare.NewHTTPServer(),
Logger: chshare.NewLogger("server"),
sessions: chshare.Users{},
reverseOk: config.Reverse,
}
s.Info = true
s.users = chshare.NewUserIndex(s.Logger)
if config.AuthFile != "" {
if err := s.users.LoadUsers(config.AuthFile); err != nil {
return nil, err
}
}
if config.Auth != "" {
u := &chshare.User{Addrs: []*regexp.Regexp{chshare.UserAllowAll}}
u.Name, u.Pass = chshare.ParseAuth(config.Auth)
if u.Name != "" {
s.users.AddUser(u)
}
}
//generate private key (optionally using seed)
key, _ := chshare.GenerateKey(config.KeySeed)
//convert into ssh.PrivateKey
private, err := ssh.ParsePrivateKey(key)
if err != nil {
log.Fatal("Failed to parse key")
}
//fingerprint this key
s.fingerprint = chshare.FingerprintKey(private.PublicKey())
//create ssh config
s.sshConfig = &ssh.ServerConfig{
ServerVersion: "SSH-" + chshare.ProtocolVersion + "-server",
PasswordCallback: s.authUser,
}
s.sshConfig.AddHostKey(private)
//setup reverse proxy
if config.Proxy != "" {
u, err := url.Parse(config.Proxy)
if err != nil {
return nil, err
}
if u.Host == "" {
return nil, s.Errorf("Missing protocol (%s)", u)
}
s.reverseProxy = httputil.NewSingleHostReverseProxy(u)
//always use proxy host
s.reverseProxy.Director = func(r *http.Request) {
r.URL.Scheme = u.Scheme
r.URL.Host = u.Host
r.Host = u.Host
}
}
//setup socks server (not listening on any port!)
if config.Socks5 {
socksConfig := &socks5.Config{}
if s.Debug {
socksConfig.Logger = log.New(os.Stdout, "[socks]", log.Ldate|log.Ltime)
} else {
socksConfig.Logger = log.New(ioutil.Discard, "", 0)
}
s.socksServer, err = socks5.New(socksConfig)
if err != nil {
return nil, err
}
s.Infof("SOCKS5 Enabled")
}
return s, nil
}
// Run is responsible for starting the chisel service
func (s *Server) Run(host, port string) error {
if err := s.Start(host, port); err != nil {
return err
}
return s.Wait()
}
// Start is responsible for kicking off the http server
func (s *Server) Start(host, port string) error {
s.Infof("Fingerprint %s", s.fingerprint)
if s.users.Len() > 0 {
s.Infof("User authenication enabled")
}
if s.reverseProxy != nil {
s.Infof("Reverse proxy enabled")
}
s.Infof("Listening on %s:%s...", host, port)
h := http.Handler(http.HandlerFunc(s.handleClientHandler))
if s.Debug {
h = requestlog.Wrap(h)
}
return s.httpServer.GoListenAndServe(host+":"+port, h)
}
// Wait waits for the http server to close
func (s *Server) Wait() error {
return s.httpServer.Wait()
}
// Close forcibly closes the http server
func (s *Server) Close() error {
return s.httpServer.Close()
}
// authUser is responsible for validating the ssh user / password combination
func (s *Server) authUser(c ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
// check if user authenication is enable and it not allow all
if s.users.Len() == 0 {
return nil, nil
}
// check the user exists and has matching password
n := c.User()
user, found := s.users.GetUser(n)
if !found || user.Pass != string(password) {
s.Debugf("Login failed for user: %s", n)
return nil, errors.New("Invalid authentication for username: %s")
}
// insert the user session map
// @note: this should probably have a lock on it given the map isn't thread-safe??
s.sessions[string(c.SessionID())] = user
return nil, nil
}