Compare commits

..
Author SHA1 Message Date
moloch-- b3429f5f88 Fix err nil deref 2023-11-18 13:31:47 -07:00
moloch-- 04d8d2aac7 Add missing builder permissions 2023-11-18 12:39:34 -07:00
moloch-- b71759f3b2 Add basic methods 2023-11-18 12:33:06 -07:00
moloch-- 13c68a7f50 Implement method checks 2023-11-18 12:27:39 -07:00
moloch-- 1f894f50ff Add permissions to operator config 2023-11-18 12:14:33 -07:00
moloch-- 33ea66de58 Add production 'slim' Docker stage 2023-11-18 10:19:14 -07:00
moloch-- 9a432f583d Add missing libs to docker test 2023-11-18 10:09:32 -07:00
moloch-- cbcd948c83 Add comments to Dockerfile 2023-11-18 09:24:21 -07:00
Joe 2ada8bf8ed Merge pull request #1465 from BishopFox/vendor/ssh
Update ssh and garble
2023-11-18 08:16:16 -08:00
13 changed files with 210 additions and 44 deletions

No files matched your search

+44 -1
View File
@@ -1,3 +1,12 @@
#
# For production:
# docker build --target production -t sliver .
# docker run -it --rm -v $HOME/.sliver:/home/sliver/.sliver sliver
#
# For unit testing:
# docker build --target test .
#
# STAGE: base
## Compiles Sliver for use
FROM --platform=linux/amd64 golang:1.21.4 as base
@@ -25,7 +34,7 @@ FROM --platform=linux/amd64 base as test
RUN apt-get update --fix-missing \
&& apt-get -y upgrade \
&& apt-get -y install \
curl mingw-w64 binutils-mingw-w64 g++-mingw-w64
curl gcc-multilib build-essential mingw-w64 binutils-mingw-w64 g++-mingw-w64
RUN /opt/sliver-server unpack --force
@@ -80,3 +89,37 @@ RUN /opt/sliver-server unpack --force
WORKDIR /home/sliver/
VOLUME [ "/home/sliver/.sliver" ]
ENTRYPOINT [ "/opt/sliver-server" ]
# STAGE: production-slim (about 1Gb smaller)
### Slim production image, i.e. without MSF and assoicated libraries
### Still include GCC and MinGW for cross-platform generation
FROM --platform=linux/amd64 debian:bookworm-slim as production-slim
### Install production packages
RUN apt-get update --fix-missing \
&& apt-get -y upgrade \
&& apt-get -y install \
build-essential mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
### Cleanup unneeded packages
RUN apt-get autoremove -y \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
### Add sliver user
RUN groupadd -g 999 sliver \
&& useradd -r -u 999 -g sliver sliver \
&& mkdir -p /home/sliver/ \
&& chown -R sliver:sliver /home/sliver
### Copy compiled binary
COPY --from=base /opt/sliver-server /opt/sliver-server
### Unpack Sliver:
USER sliver
RUN /opt/sliver-server unpack --force
WORKDIR /home/sliver/
VOLUME [ "/home/sliver/.sliver" ]
ENTRYPOINT [ "/opt/sliver-server" ]
+1 -1
View File
@@ -272,7 +272,7 @@ func listenAndServeTLS(srv *http.Server, certPEMBlock, keyPEMBlock []byte) error
}
config := &tls.Config{}
if srv.TLSConfig != nil {
*config = *srv.TLSConfig
config = srv.TLSConfig
}
if certs.TLSKeyLogger != nil {
config.KeyLogWriter = certs.TLSKeyLogger
+5 -4
View File
@@ -44,10 +44,11 @@ const (
forceFlagStr = "force"
// Operator flags
nameFlagStr = "name"
lhostFlagStr = "lhost"
lportFlagStr = "lport"
saveFlagStr = "save"
nameFlagStr = "name"
lhostFlagStr = "lhost"
lportFlagStr = "lport"
saveFlagStr = "save"
permissionsFlagStr = "permissions"
// Cert flags
caTypeFlagStr = "type"
+4 -4
View File
@@ -71,10 +71,10 @@ var daemonCmd = &cobra.Command{
},
}
func StartPersistentJobs(listenerJobs *[]clientpb.ListenerJob) error {
if len(*listenerJobs) > 0 {
func StartPersistentJobs(listenerJobs []*clientpb.ListenerJob) error {
if len(listenerJobs) > 0 {
// StartPersistentJobs - Start persistent jobs
for _, j := range *listenerJobs {
for _, j := range listenerJobs {
listenerJob, err := db.ListenerByJobID(j.JobID)
if err != nil {
return err
@@ -117,7 +117,7 @@ func StartPersistentJobs(listenerJobs *[]clientpb.ListenerJob) error {
}
j.JobID = uint32(id)
}
db.UpdateHTTPC2Listener(&j)
db.UpdateHTTPC2Listener(j)
}
}
+20 -11
View File
@@ -33,54 +33,63 @@ var operatorCmd = &cobra.Command{
Short: "Generate operator configuration files",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
name, err := cmd.Flags().GetString(nameFlagStr)
if err != nil {
fmt.Printf("Failed to parse --%s flag %s", nameFlagStr, err)
os.Exit(1)
return
}
if name == "" {
fmt.Printf("Must specify --%s", nameFlagStr)
os.Exit(1)
return
}
lhost, err := cmd.Flags().GetString(lhostFlagStr)
if err != nil {
fmt.Printf("Failed to parse --%s flag %s", lhostFlagStr, err)
os.Exit(1)
return
}
if lhost == "" {
fmt.Printf("Must specify --%s", lhostFlagStr)
os.Exit(1)
return
}
lport, err := cmd.Flags().GetUint16(lportFlagStr)
if err != nil {
fmt.Printf("Failed to parse --%s flag %s", lportFlagStr, err)
os.Exit(1)
return
}
save, err := cmd.Flags().GetString(saveFlagStr)
if err != nil {
fmt.Printf("Failed to parse --%s flag %s", saveFlagStr, err)
os.Exit(1)
return
}
if save == "" {
save, _ = os.Getwd()
}
permissions, err := cmd.Flags().GetStringSlice(permissionsFlagStr)
if err != nil {
fmt.Printf("Failed to parse --%s flag %s", permissionsFlagStr, err)
return
}
if len(permissions) == 0 {
fmt.Printf("Must specify --%s", permissionsFlagStr)
return
}
certs.SetupCAs()
configJSON, err := console.NewOperatorConfig(name, lhost, lport)
configJSON, err := console.NewOperatorConfig(name, lhost, lport, permissions)
if err != nil {
fmt.Printf("Failed: %s\n", err)
os.Exit(1)
return
}
saveTo, _ := filepath.Abs(save)
fi, err := os.Stat(saveTo)
if !os.IsNotExist(err) && !fi.IsDir() {
fmt.Printf("File already exists: %s\n", err)
os.Exit(1)
return
}
if !os.IsNotExist(err) && fi.IsDir() {
filename := fmt.Sprintf("%s_%s.cfg", filepath.Base(name), filepath.Base(lhost))
@@ -89,7 +98,7 @@ var operatorCmd = &cobra.Command{
err = os.WriteFile(saveTo, configJSON, 0600)
if err != nil {
fmt.Printf("Write failed: %s (%s)\n", saveTo, err)
os.Exit(1)
return
}
},
}
+20 -4
View File
@@ -24,7 +24,6 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
@@ -86,13 +85,14 @@ func newOperatorCmd(cmd *cobra.Command, _ []string) {
lhost, _ := cmd.Flags().GetString("lhost")
lport, _ := cmd.Flags().GetUint16("lport")
save, _ := cmd.Flags().GetString("save")
permissions, _ := cmd.Flags().GetStringSlice("permissions")
if save == "" {
save, _ = os.Getwd()
}
fmt.Printf(Info + "Generating new client certificate, please wait ... \n")
configJSON, err := NewOperatorConfig(name, lhost, lport)
configJSON, err := NewOperatorConfig(name, lhost, lport, permissions)
if err != nil {
fmt.Printf(Warn+"%s\n", err)
return
@@ -108,7 +108,7 @@ func newOperatorCmd(cmd *cobra.Command, _ []string) {
filename := fmt.Sprintf("%s_%s.cfg", filepath.Base(name), filepath.Base(lhost))
saveTo = filepath.Join(saveTo, filename)
}
err = ioutil.WriteFile(saveTo, configJSON, 0o600)
err = os.WriteFile(saveTo, configJSON, 0o600)
if err != nil {
fmt.Printf(Warn+"Failed to write config to: %s (%s) \n", saveTo, err)
return
@@ -117,7 +117,7 @@ func newOperatorCmd(cmd *cobra.Command, _ []string) {
}
// NewOperatorConfig - Generate a new player/client/operator configuration
func NewOperatorConfig(operatorName string, lhost string, lport uint16) ([]byte, error) {
func NewOperatorConfig(operatorName string, lhost string, lport uint16, permissions []string) ([]byte, error) {
if !namePattern.MatchString(operatorName) {
return nil, errors.New("invalid operator name (alphanumerics only)")
}
@@ -127,6 +127,9 @@ func NewOperatorConfig(operatorName string, lhost string, lport uint16) ([]byte,
if lhost == "" {
return nil, errors.New("invalid lhost")
}
if len(permissions) == 0 {
return nil, errors.New("must specify at least one permission")
}
rawToken := models.GenerateOperatorToken()
digest := sha256.Sum256([]byte(rawToken))
@@ -134,6 +137,19 @@ func NewOperatorConfig(operatorName string, lhost string, lport uint16) ([]byte,
Name: operatorName,
Token: hex.EncodeToString(digest[:]),
}
for _, permission := range permissions {
switch permission {
case "all":
dbOperator.PermissionAll = true
break
case "builder":
dbOperator.PermissionBuilder = true
case "crackstation":
dbOperator.PermissionCrackstation = true
default:
return nil, fmt.Errorf("invalid permission: %s", permission)
}
}
err := db.Session().Save(dbOperator).Error
if err != nil {
return nil, err
+1 -1
View File
@@ -12,7 +12,7 @@ import (
func TestRootOnlyVerifyCertificate(t *testing.T) {
certs.SetupCAs()
data, err := NewOperatorConfig("zerocool", "localhost", uint16(1337))
data, err := NewOperatorConfig("zerocool", "localhost", uint16(1337), []string{"all"})
if err != nil {
t.Fatalf("failed to generate test player profile %s", err)
}
+22 -1
View File
@@ -85,7 +85,7 @@ func serverOnlyCmds() (commands []*cobra.Command) {
newOperator := &cobra.Command{
Use: consts.NewOperatorStr,
Short: "Create a new operator config file",
Long: help.GetHelpFor([]string{consts.NewOperatorStr}),
Long: newOperatorLongHelp,
Run: newOperatorCmd,
GroupID: consts.MultiplayerHelpGroup,
}
@@ -94,6 +94,7 @@ func serverOnlyCmds() (commands []*cobra.Command) {
f.Uint16P("lport", "p", 31337, "listen port")
f.StringP("save", "s", "", "directory/file in which to save config")
f.StringP("name", "n", "", "operator name")
f.StringSliceP("permissions", "P", []string{}, "grant permissions to the operator profile (all, builder, crackstation)")
})
command.FlagComps(newOperator, func(comp *carapace.ActionMap) {
(*comp)["save"] = carapace.ActionDirectories()
@@ -115,3 +116,23 @@ func serverOnlyCmds() (commands []*cobra.Command) {
return
}
const newOperatorLongHelp = `
Create a new operator config file, operator configuration files allow
remote machines to connect to the Sliver server. They are most commonly
used for allowing remote operators to connect in "Multiplayer Mode."
To generate a profile for a remote operator, you need to specify the
the "all" permission to grant the profile access to all gRPC APIs:
new-operator --name <operator name> --lhost <sliver server> --permissions all
Operator profiles can also be used to allow remote machines to connect to
the Sliver server for other purposes, such as a "Remote Builder" or a
"Crackstation."
You can restrict profiles' permissions by using the --permissions flag, for
example, to create a profile that can only be used as a "Remote Builder":
new-operator --name <operator name> --lhost <sliver server> --permissions builder
`
+1 -1
View File
@@ -31,11 +31,11 @@ type ImplantConnection struct {
ID string
Send chan *sliverpb.Envelope
RespMutex *sync.RWMutex
LastMessageMutex *sync.RWMutex
Resp map[int64]chan *sliverpb.Envelope
Transport string
RemoteAddress string
LastMessage time.Time
LastMessageMutex *sync.RWMutex
Cleanup func()
}
+4 -4
View File
@@ -525,15 +525,15 @@ func ListenerByJobID(JobID uint32) (*clientpb.ListenerJob, error) {
return listenerJob.ToProtobuf(), err
}
func ListenerJobs() (*[]clientpb.ListenerJob, error) {
func ListenerJobs() ([]*clientpb.ListenerJob, error) {
listenerJobs := []models.ListenerJob{}
err := Session().Where(&models.ListenerJob{}).Find(&listenerJobs).Error
pbListenerJobs := []clientpb.ListenerJob{}
pbListenerJobs := []*clientpb.ListenerJob{}
for _, listenerJob := range listenerJobs {
pbListenerJobs = append(pbListenerJobs, *listenerJob.ToProtobuf())
pbListenerJobs = append(pbListenerJobs, listenerJob.ToProtobuf())
}
return &pbListenerJobs, err
return pbListenerJobs, err
}
func DeleteListener(JobID uint32) error {
+8 -5
View File
@@ -28,12 +28,15 @@ import (
"gorm.io/gorm"
)
// Operator - Colletions of content to serve from HTTP(S)
// Operator - Collections of content to serve from HTTP(S)
type Operator struct {
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
CreatedAt time.Time `gorm:"->;<-:create;"`
Name string
Token string `gorm:"uniqueIndex"`
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
CreatedAt time.Time `gorm:"->;<-:create;"`
Name string
Token string `gorm:"uniqueIndex"` // NOTE: This is the SHA256 of the token
PermissionAll bool `gorm:"default:false"` // Access all gRPC APIs
PermissionBuilder bool `gorm:"default:false"` // Access Builder gRPC APIs
PermissionCrackstation bool `gorm:"default:false"` // Access Crackstation gRPC APIs
}
// BeforeCreate - GORM hook
+1 -1
View File
@@ -283,7 +283,7 @@ func PortInUse(newPort uint32) error {
return err
}
var port uint32
for _, job := range *listenerJobs {
for _, job := range listenerJobs {
listener, err := db.ListenerByJobID(job.JobID)
if err != nil {
return err
+79 -6
View File
@@ -29,6 +29,7 @@ import (
"github.com/bishopfox/sliver/server/configs"
"github.com/bishopfox/sliver/server/core"
"github.com/bishopfox/sliver/server/db"
"github.com/bishopfox/sliver/server/db/models"
"github.com/bishopfox/sliver/server/log"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
@@ -64,6 +65,7 @@ func initMiddleware(remoteAuth bool) []grpc.ServerOption {
return []grpc.ServerOption{
grpc.ChainUnaryInterceptor(
grpc_auth.UnaryServerInterceptor(tokenAuthFunc),
permissionsUnaryServerInterceptor(),
auditLogUnaryServerInterceptor(),
grpc_tags.UnaryServerInterceptor(grpc_tags.WithFieldExtractor(grpc_tags.CodeGenRequestFieldExtractor)),
grpc_logrus.UnaryServerInterceptor(logrusEntry, logrusOpts...),
@@ -71,6 +73,7 @@ func initMiddleware(remoteAuth bool) []grpc.ServerOption {
),
grpc.ChainStreamInterceptor(
grpc_auth.StreamServerInterceptor(tokenAuthFunc),
permissionsStreamServerInterceptor(),
grpc_tags.StreamServerInterceptor(grpc_tags.WithFieldExtractor(grpc_tags.CodeGenRequestFieldExtractor)),
grpc_logrus.StreamServerInterceptor(logrusEntry, logrusOpts...),
grpc_logrus.PayloadStreamServerInterceptor(logrusEntry, deciderStream),
@@ -123,23 +126,93 @@ func tokenAuthFunc(ctx context.Context) (context.Context, error) {
digest := sha256.Sum256([]byte(rawToken))
token := hex.EncodeToString(digest[:])
newCtx := context.WithValue(ctx, Transport, "mtls")
if name, ok := tokenCache.Load(token); ok {
if op, ok := tokenCache.Load(token); ok {
mtlsLog.Debugf("Token in cache!")
newCtx = context.WithValue(newCtx, Operator, name.(string))
newCtx = context.WithValue(newCtx, Operator, op.(*models.Operator))
return newCtx, nil
}
operator, err := db.OperatorByToken(token)
if err != nil || operator == nil {
mtlsLog.Errorf("Authentication failure: %s", err)
mtlsLog.Errorf("Authentication failure: %v", err)
return nil, status.Error(codes.Unauthenticated, "Authentication failure")
}
mtlsLog.Debugf("Valid user token for %s", operator.Name)
tokenCache.Store(token, operator.Name)
mtlsLog.Debugf("Valid token for %s", operator.Name)
tokenCache.Store(token, operator)
newCtx = context.WithValue(newCtx, Operator, operator.Name)
newCtx = context.WithValue(newCtx, Operator, operator)
return newCtx, nil
}
var (
// Builder - Allowed methods
builderMethods = map[string]bool{
"/rpcpb.SliverRPC/GetVersion": true,
"/rpcpb.SliverRPC/GenerateExternalGetBuildConfig": true,
"/rpcpb.SliverRPC/GenerateExternalSaveBuild": true,
"/rpcpb.SliverRPC/BuilderRegister": true,
"/rpcpb.SliverRPC/BuilderTrigger": true,
"/rpcpb.SliverRPC/Builders": true,
}
// Crackstation - Allowed methods
crackstationMethods = map[string]bool{
"/rpcpb.SliverRPC/GetVersion": true,
"/rpcpb.SliverRPC/CrackstationRegister": true,
"/rpcpb.SliverRPC/CrackstationTrigger": true,
"/rpcpb.SliverRPC/CrackstationBenchmark": true,
"/rpcpb.SliverRPC/Crackstations": true,
"/rpcpb.SliverRPC/CrackTaskByID": true,
"/rpcpb.SliverRPC/CrackTaskUpdate": true,
"/rpcpb.SliverRPC/CrackFilesList": true,
"/rpcpb.SliverRPC/CrackFileCreate": true,
"/rpcpb.SliverRPC/CrackFileChunkUpload": true,
"/rpcpb.SliverRPC/CrackFileChunkDownload": true,
"/rpcpb.SliverRPC/CrackFileComplete": true,
"/rpcpb.SliverRPC/CrackFileDelete": true,
}
)
func permissionsUnaryServerInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (_ interface{}, err error) {
operator := ctx.Value(Operator).(*models.Operator)
if operator.PermissionAll {
return handler(ctx, req)
}
if operator.PermissionBuilder {
if ok, _ := builderMethods[info.FullMethod]; ok {
return handler(ctx, req)
}
}
if operator.PermissionCrackstation {
if ok, _ := crackstationMethods[info.FullMethod]; ok {
return handler(ctx, req)
}
}
mtlsLog.Warnf("Permission denied for %s attempting to access %s", operator.Name, info.FullMethod)
return nil, status.Error(codes.PermissionDenied, "Permission denied")
}
}
func permissionsStreamServerInterceptor() grpc.StreamServerInterceptor {
return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
operator := ss.Context().Value(Operator).(*models.Operator)
if operator.PermissionAll {
return handler(srv, ss)
}
if operator.PermissionBuilder {
if ok, _ := builderMethods[info.FullMethod]; ok {
return handler(srv, ss)
}
}
if operator.PermissionCrackstation {
if ok, _ := crackstationMethods[info.FullMethod]; ok {
return handler(srv, ss)
}
}
mtlsLog.Warnf("Permission denied for %s attempting to access %s", operator.Name, info.FullMethod)
return status.Error(codes.PermissionDenied, "Permission denied")
}
}
func deciderUnary(_ context.Context, _ string, _ interface{}) bool {
return serverConfig.Logs.GRPCUnaryPayloads
}