new: store session replacer on disk

This commit is contained in:
Ohpe 2023-11-09 15:05:16 +01:00
parent 54f3efbc15
commit e56d54b861
No known key found for this signature in database
6 changed files with 200 additions and 80 deletions

1
.gitignore vendored
View file

@ -1,5 +1,6 @@
*.pem
*.log
*.json
.DS_Store
muraena
build/

View file

@ -13,9 +13,10 @@ import (
"github.com/evilsocket/islazy/tui"
. "github.com/logrusorgru/aurora"
"github.com/pkg/errors"
"github.com/muraenateam/muraena/core"
"github.com/muraenateam/muraena/module/necrobrowser"
"github.com/pkg/errors"
"github.com/muraenateam/muraena/core/db"
"github.com/muraenateam/muraena/log"
@ -352,7 +353,6 @@ func (muraena *MuraenaProxy) ResponseProcessor(response *http.Response) (err err
}
}
} else {
if len(response.Cookies()) > 0 {
log.Debug("[TODO] Missing cookies to track: \n%s\n%+v", response.Request.URL, response.Cookies())
@ -458,14 +458,12 @@ func (init *MuraenaProxyInit) Spawn() *MuraenaProxy {
return muraena
}
func (st *SessionType) HandleFood(response http.ResponseWriter, request *http.Request) {
func (st SessionType) HandleFood(response http.ResponseWriter, request *http.Request) {
var destination string
sess := st.Session
replacer := st.Replacer
if sess.Config.StaticServer.Enabled {
m, err := sess.Module("static.http")
if st.Session.Config.StaticServer.Enabled {
m, err := st.Session.Module("static.http")
if err != nil {
log.Error("%s", err)
}
@ -477,33 +475,31 @@ func (st *SessionType) HandleFood(response http.ResponseWriter, request *http.Re
}
if destination == "" {
if strings.HasPrefix(request.Host, replacer.ExternalOriginPrefix) { //external domain mapping
for domain, subMapping := range replacer.OriginsMapping {
if strings.HasPrefix(request.Host, st.Replacer.ExternalOriginPrefix) { //external domain mapping
for domain, subMapping := range st.Replacer.GetOrigins() {
// even if the resource is aa.bb.cc.dom.tld, the mapping is always one level as in www--2.phishing.tld.
// This is specifically important since wildcard SSL certs do not handle N levels of nesting
// This is important since wildcard SSL certs do not handle N levels of nesting
if subMapping == strings.Split(request.Host, ".")[0] {
destination = fmt.Sprintf("%s%s", sess.Config.Protocol,
destination = fmt.Sprintf("%s%s", st.Session.Config.Protocol,
strings.Replace(request.Host,
fmt.Sprintf("%s.%s", subMapping, replacer.Phishing),
fmt.Sprintf("%s.%s", subMapping, st.Replacer.Phishing),
domain, -1))
break
}
}
} else {
destination = fmt.Sprintf("%s%s", sess.Config.Protocol,
strings.Replace(request.Host, replacer.Phishing, replacer.Target, -1))
destination = fmt.Sprintf("%s%s", st.Session.Config.Protocol,
strings.Replace(request.Host, st.Replacer.Phishing, st.Replacer.Target, -1))
}
}
// PortMapping
if sess.Config.Proxy.PortMap != "" {
if st.Session.Config.Proxy.PortMap != "" {
destURL, err := url.Parse(destination)
if err != nil {
log.Error("%s", err)
} else {
port := destURL.Port()
if port == "" && destURL.Scheme == "https" {
port = "443"
@ -513,8 +509,8 @@ func (st *SessionType) HandleFood(response http.ResponseWriter, request *http.Re
destination = fmt.Sprintf("%s:%s", destination, port)
}
if strings.HasPrefix(sess.Config.Proxy.PortMap, fmt.Sprintf("%s:", port)) {
newport := strings.Split(sess.Config.Proxy.PortMap, ":")[1]
if strings.HasPrefix(st.Session.Config.Proxy.PortMap, fmt.Sprintf("%s:", port)) {
newport := strings.Split(st.Session.Config.Proxy.PortMap, ":")[1]
destination = strings.Replace(destination, fmt.Sprintf(":%s", port), fmt.Sprintf(":%s", newport), 1)
}
}
@ -528,8 +524,8 @@ func (st *SessionType) HandleFood(response http.ResponseWriter, request *http.Re
muraena := &MuraenaProxyInit{
Origin: request.Host,
Target: destination,
Session: sess,
Replacer: replacer,
Session: st.Session,
Replacer: st.Replacer,
}
muraenaProxy := muraena.Spawn()

View file

@ -137,11 +137,12 @@ func isWildcard(s string) bool {
return strings.HasPrefix(s, "*.")
}
func IsSubdomain(domain string, toCheck string) bool {
if strings.HasSuffix(toCheck, domain) {
return true
}
return false
// IsSubdomain checks if a string is a subdomain of another string.
// It returns true if the given string is a subdomain of the root string.
func IsSubdomain(root string, subdomain string) bool {
// TODO: add support for wildcard domains
return strings.HasSuffix(subdomain, root)
}
func base64Decode(input string, padding int32) (string, bool) {

111
core/proxy/replacer.go Normal file
View file

@ -0,0 +1,111 @@
package proxy
import (
"encoding/json"
"io/ioutil"
"sync"
)
const ReplaceFile = "session.json"
// Replacer structure used to populate the transformation rules
type Replacer struct {
Phishing string
Target string
ExternalOrigin []string
ExternalOriginPrefix string
Origins map[string]string
WildcardMapping map[string]string
CustomResponseTransformations [][]string
ForwardReplacements []string `json:"-"`
BackwardReplacements []string `json:"-"`
LastForwardReplacements []string `json:"-"`
LastBackwardReplacements []string `json:"-"`
WildcardDomain string `json:"-"`
// Ignore from JSON export
loopCount int
mu sync.RWMutex
}
// GetOrigins returns the Origins mapping used in the transformation rules.
// It returns a copy of the internal map.
func (r *Replacer) GetOrigins() map[string]string {
r.mu.Lock()
// Make a copy of the Origins and return it
ret := make(map[string]string)
for k, v := range r.Origins {
ret[k] = v
}
r.mu.Unlock()
return ret
}
// SetOrigins sets the Origins mapping used in the transformation rules.
func (r *Replacer) SetOrigins(newOrigins map[string]string) {
if len(newOrigins) == 0 {
return
}
if r.Origins == nil {
r.Origins = make(map[string]string)
}
r.mu.Lock()
defer r.mu.Unlock()
// merge newOrigins to r.newOrigins and avoid duplicate
for k, v := range newOrigins {
r.Origins[k] = v
}
}
// Save saves the Replacer struct to a file as JSON.
func (r *Replacer) Save() error {
r.mu.Lock()
defer r.mu.Unlock()
return saveToJSON(ReplaceFile, r)
}
// saveToJSON saves the Replacer struct to a file as JSON.
func saveToJSON(filename string, replacer *Replacer) error {
data, err := json.MarshalIndent(replacer, "", "\t")
if err != nil {
return err
}
return ioutil.WriteFile(filename, data, 0644)
}
// Load loads the Replacer data from a JSON file.
func (r *Replacer) Load() error {
r.mu.Lock()
mutex := r.mu
defer mutex.Unlock()
rep, err := loadFromJSON(ReplaceFile)
if err != nil {
return err
}
// update the current replacer pointer
*r = *rep
return nil
}
// loadFromJSON loads the Replacer data from a JSON file.
func loadFromJSON(filename string) (*Replacer, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
var replacer Replacer
if err := json.Unmarshal(data, &replacer); err != nil {
return nil, err
}
return &replacer, nil
}

View file

@ -49,27 +49,51 @@ func (server *tlsServer) serveTLS() (err error) {
return server.Serve(tlsListener)
}
var replacer *Replacer
func Run(sess *session.Session) {
// Load replacer rules
var replacer = &Replacer{
Phishing: sess.Config.Proxy.Phishing,
Target: sess.Config.Proxy.Target,
ExternalOrigin: sess.Config.Crawler.ExternalOrigins,
ExternalOriginPrefix: sess.Config.Crawler.ExternalOriginPrefix,
OriginsMapping: sess.Config.Crawler.OriginsMapping,
CustomResponseTransformations: sess.Config.Transform.Response.Custom,
// Load the replacer
var err error
replacer = &Replacer{}
err = replacer.Load()
if err != nil {
log.Debug("Error loading replacer: %s", err)
log.Debug("Creating a new replacer")
replacer = &Replacer{
Phishing: sess.Config.Proxy.Phishing,
Target: sess.Config.Proxy.Target,
ExternalOrigin: sess.Config.Crawler.ExternalOrigins,
ExternalOriginPrefix: sess.Config.Crawler.ExternalOriginPrefix,
CustomResponseTransformations: sess.Config.Transform.Response.Custom,
}
replacer.SetOrigins(sess.Config.Crawler.OriginsMapping)
}
if err := replacer.DomainMapping(); err != nil {
if err = replacer.DomainMapping(); err != nil {
log.Fatal(err.Error())
}
err = replacer.Save()
if err != nil {
log.Error("Error saving replacer: %s", err)
}
replacer.MakeReplacements()
//
// Start the reverse proxy
//
http.HandleFunc("/", func(response http.ResponseWriter, request *http.Request) {
// Defer the recovery function in case of panic
defer func() {
if err := recover(); err != nil {
log.Error("Recovered from panic: %s", err)
}
}()
// TODO: Configure properly middlewares.
if sess.Config.Watchdog.Enabled {
m, err := sess.Module("watchdog")

View file

@ -20,27 +20,10 @@ const (
// Base64Padding is the padding to use within base64 operations
Base64Padding = '='
// Wildcard key
// WildcardPrefix is the prefix used to identify wildcard domains
WildcardPrefix = "wld"
)
// Replacer structure used to populate the transformation rules
type Replacer struct {
Phishing string
Target string
ExternalOrigin []string
ExternalOriginPrefix string
OriginsMapping map[string]string // The origin map who maps between external origins and internal origins
WildcardMapping map[string]string
CustomResponseTransformations [][]string
ForwardReplacements []string
BackwardReplacements []string
LastForwardReplacements []string
LastBackwardReplacements []string
WildcardDomain string
}
// Base64 identifies if the transformation should consider base-64 data and the related padding rules
type Base64 struct {
Enabled bool
@ -60,9 +43,9 @@ type Base64 struct {
// apply the transformation and re-encode (hello ReCaptcha)
func (r *Replacer) Transform(input string, forward bool, b64 Base64) (result string) {
original := input
if strings.TrimSpace(input) == "" {
return input
source := strings.TrimSpace(input)
if source == "" {
return source
}
var replacements []string
@ -76,29 +59,25 @@ func (r *Replacer) Transform(input string, forward bool, b64 Base64) (result str
}
// Handling of base64 encoded data which should be decoded before transformation
input, base64Found, padding := transformBase64(input, b64, true, Base64Padding)
source, base64Found, padding := transformBase64(source, b64, true, Base64Padding)
// Replace transformation
replacer := strings.NewReplacer(replacements...)
result = replacer.Replace(input)
result = strings.NewReplacer(replacements...).Replace(source)
// do last replacements
replacer = strings.NewReplacer(lastReplacements...)
result = replacer.Replace(result)
result = strings.NewReplacer(lastReplacements...).Replace(result)
// Re-encode if base64 encoded data was found
if base64Found {
result, _, _ = transformBase64(result, b64, false, padding)
}
if original != result {
// If the result is the same as the input, we don't need to do anything else
if source != result {
// Find wildcard matching
if Wildcards {
var rep []string
wldPrefix := fmt.Sprintf("%s%s", r.ExternalOriginPrefix, WildcardPrefix)
if strings.Contains(result, "."+wldPrefix) {
// URL encoding handling
@ -134,7 +113,7 @@ func (r *Replacer) Transform(input string, forward bool, b64 Base64) (result str
// Patch the wildcard
element = strings.ReplaceAll(element, "."+wldPrefix, "-"+wldPrefix)
rep = append(rep, element)
// log.Info("[*] New wildcard %s", tui.Bold(tui.Red(element)))
log.Info("[*] New wildcard %s", tui.Bold(tui.Red(element)))
}
if urlEncoded {
@ -159,10 +138,20 @@ func (r *Replacer) Transform(input string, forward bool, b64 Base64) (result str
return
}
if err = r.Save(); err != nil {
log.Error("Error saving replacer: %s", err)
}
r.MakeReplacements()
log.Debug("We need another transformation loop, because of this new domains: %s",
tui.Green(fmt.Sprintf("%v", rep)))
return r.Transform(input, forward, b64)
r.loopCount++
if r.loopCount > 10 {
log.Error("Too many transformation loops, aborting.")
return
}
return r.Transform(source, forward, b64)
}
}
}
@ -201,7 +190,7 @@ func (r *Replacer) transformUrl(URL string, base64 Base64) (result string, err e
result = r.Transform(URL, true, base64)
// After initial transformation round.
// If the input is a valid URL proceed by tranforming also the query string
// If the input is a valid URL proceed by transforming also the query string
hURL, err := url.Parse(result)
if err != nil || hURL.Scheme == "" || hURL.Host == "" {
@ -262,12 +251,14 @@ func (r *Replacer) MakeReplacements() {
//
// Requests
//
origins := r.GetOrigins()
r.ForwardReplacements = []string{}
r.ForwardReplacements = append(r.ForwardReplacements, []string{r.Phishing, r.Target}...)
log.Debug("[Forward | origins]: %d", len(r.OriginsMapping))
log.Debug("[Forward | Origins]: %d", len(origins))
count := len(r.ForwardReplacements)
for extOrigin, subMapping := range r.OriginsMapping { // changes resource-1.phishing.
for extOrigin, subMapping := range origins { // changes resource-1.phishing.
if strings.HasPrefix(subMapping, WildcardPrefix) {
// Ignoring wildcard at this stage
@ -302,7 +293,7 @@ func (r *Replacer) MakeReplacements() {
r.BackwardReplacements = append(r.BackwardReplacements, []string{r.Target, r.Phishing}...)
count = 0
for include, subMapping := range r.OriginsMapping {
for include, subMapping := range origins {
if strings.HasPrefix(subMapping, WildcardPrefix) {
// Ignoring wildcard at this stage
@ -345,16 +336,11 @@ func (r *Replacer) MakeReplacements() {
}
func (r *Replacer) DomainMapping() (err error) {
// d := strings.Split(r.Target, ".")
//baseDom := fmt.Sprintf("%s.%s", d[len(d)-2], d[len(d)-1])
// Changing baseDom to be the actual Target domain.
baseDom := r.Target
log.Debug("Proxy destination: %s", tui.Bold(tui.Green("*."+baseDom)))
r.ExternalOrigin = ArmorDomain(r.ExternalOrigin)
r.OriginsMapping = make(map[string]string)
origins := make(map[string]string)
r.WildcardMapping = make(map[string]string)
count, wildcards := 0, 0
@ -364,7 +350,7 @@ func (r *Replacer) DomainMapping() (err error) {
// We don't map 1-level subdomains ..
if strings.Count(trim, ".") < 2 {
log.Debug("Ignore: %s [%s]", domain, trim)
log.Warning("Ignore: %s [%s]", domain, trim)
continue
}
}
@ -388,7 +374,7 @@ func (r *Replacer) DomainMapping() (err error) {
count++
// Extra domains or nested subdomains
o := fmt.Sprintf("%s%d", r.ExternalOriginPrefix, count)
r.OriginsMapping[domain] = o
origins[domain] = o
//log.Info("Including [%s]=%s", domain, o)
log.Debug(fmt.Sprintf("Including [%s]=%s", domain, o))
}
@ -399,7 +385,8 @@ func (r *Replacer) DomainMapping() (err error) {
Wildcards = true
}
log.Debug("Processed %d domains to transform, %d are wildcards", count, wildcards)
r.SetOrigins(origins)
log.Debug("Processed %d domains to transform, %d are wildcards", count, wildcards)
return
}