mirror of
https://github.com/jpillora/chisel
synced 2026-06-08 15:07:02 +00:00
a11a3dd2dd
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$").
181 lines
4.4 KiB
Go
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
|
|
}
|