mirror of
https://github.com/muraenateam/muraena.git
synced 2026-07-23 10:07:55 +00:00
371 lines
10 KiB
Go
371 lines
10 KiB
Go
package proxy
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/evilsocket/islazy/tui"
|
|
|
|
"github.com/muraenateam/muraena/log"
|
|
)
|
|
|
|
var (
|
|
Wildcards = false
|
|
)
|
|
|
|
const (
|
|
// Base64Padding is the padding to use within base64 operations
|
|
Base64Padding = '='
|
|
|
|
// Wildcard key
|
|
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
|
|
Padding []string
|
|
}
|
|
|
|
//
|
|
// If used with forward=true, Transform uses Replacer to replace all occurrences of the phishing origin, the external domains defined,
|
|
// as well as the rest of the data to be replaced defined in MakeReplacements(), with the target real origin.
|
|
// If used with forward=false, Transform will replace data coming from the targeted origin
|
|
// with the real proxied origin (target).
|
|
// Forward:
|
|
// - true > change requests, i.e. phishing > target origin
|
|
// - false > change response, i.e. target origin > phishing
|
|
// Base64:
|
|
// Since some request parameter values can be base64 encoded, we need to decode first,
|
|
// 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
|
|
}
|
|
|
|
var replacements []string
|
|
var lastReplacements []string
|
|
if forward { // used in Requests
|
|
replacements = r.ForwardReplacements
|
|
lastReplacements = r.LastForwardReplacements
|
|
} else { // used in Responses
|
|
replacements = r.BackwardReplacements
|
|
lastReplacements = r.LastBackwardReplacements
|
|
}
|
|
|
|
// Handling of base64 encoded data which should be decoded before transformation
|
|
input, base64Found := transformBase64(input, b64, true)
|
|
|
|
// Replace transformation
|
|
replacer := strings.NewReplacer(replacements...)
|
|
result = replacer.Replace(input)
|
|
|
|
// do last replacements
|
|
replacer = strings.NewReplacer(lastReplacements...)
|
|
result = replacer.Replace(result)
|
|
|
|
// Re-encode if base64 encoded data was found
|
|
if base64Found {
|
|
result, _ = transformBase64(result, b64, false)
|
|
}
|
|
|
|
if original != 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
|
|
urlEncoded := false
|
|
decodedValue, err := url.QueryUnescape(result)
|
|
if err == nil && result != decodedValue {
|
|
urlEncoded = true
|
|
result = decodedValue
|
|
}
|
|
|
|
domain := regexp.QuoteMeta(r.Phishing)
|
|
re := regexp.MustCompile(fmt.Sprintf(`[a-zA-Z0-9.-]+%s\d+.%s`, WildcardPrefix, domain))
|
|
matchSubdomains := re.FindAllString(result, -1)
|
|
matchSubdomains = ArmorDomain(matchSubdomains)
|
|
if len(matchSubdomains) > 0 {
|
|
log.Debug("Wildcard pattern: %v match %d!", re.String(), len(matchSubdomains))
|
|
}
|
|
|
|
for _, element := range matchSubdomains {
|
|
if contains(rep, element) {
|
|
continue
|
|
}
|
|
|
|
if strings.HasPrefix(element, ".") {
|
|
continue
|
|
}
|
|
|
|
if strings.HasPrefix(element, wldPrefix) {
|
|
// log.Warning("Do you want to kill me?! [%s]", element)
|
|
continue
|
|
}
|
|
|
|
// Patch the wildcard
|
|
element = strings.ReplaceAll(element, "."+wldPrefix, "-"+wldPrefix)
|
|
rep = append(rep, element)
|
|
// log.Info("[*] New wildcard %s", tui.Bold(tui.Red(element)))
|
|
}
|
|
|
|
if urlEncoded {
|
|
encodedValue, err := url.QueryUnescape(result)
|
|
if err != nil {
|
|
log.Error(err.Error())
|
|
} else {
|
|
result = encodedValue
|
|
}
|
|
}
|
|
|
|
if len(rep) > 0 {
|
|
rep = ArmorDomain(rep)
|
|
|
|
// Fix the domains
|
|
patched := r.patchWildcard(rep)
|
|
// Re-do domain mapping
|
|
r.ExternalOrigin = ArmorDomain(append(r.ExternalOrigin, patched...))
|
|
|
|
if err := r.DomainMapping(); err != nil {
|
|
log.Error(err.Error())
|
|
return
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func transformBase64(input string, b64 Base64, decode bool) (output string, base64Found bool) {
|
|
// Handling of base64 encoded data, that should be decoded/transformed/re-encoded
|
|
base64Found = false
|
|
padding := Base64Padding
|
|
if b64.Enabled { // decode
|
|
if decode {
|
|
var decoded string
|
|
if len(b64.Padding) > 1 {
|
|
for _, p := range b64.Padding {
|
|
padding = getPadding(p)
|
|
if decoded, base64Found = base64Decode(input, padding); base64Found {
|
|
input = decoded
|
|
base64Found = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
//encode
|
|
return base64Encode(input, padding), base64Found
|
|
}
|
|
}
|
|
|
|
return input, base64Found
|
|
}
|
|
|
|
func (r *Replacer) patchWildcard(rep []string) (prep []string) {
|
|
|
|
rep = ArmorDomain(rep)
|
|
for _, s := range rep {
|
|
found := false
|
|
newDomain := strings.TrimSuffix(s, fmt.Sprintf(".%s", r.Phishing))
|
|
for w, d := range r.WildcardMapping {
|
|
if strings.HasSuffix(newDomain, d) {
|
|
newDomain = strings.TrimSuffix(newDomain, d)
|
|
newDomain = strings.TrimSuffix(newDomain, "-")
|
|
if newDomain != "" {
|
|
newDomain = newDomain + "."
|
|
}
|
|
newDomain = newDomain + w
|
|
|
|
//log.Info("[*] New wildcard %s (%s)", tui.Bold(tui.Red(s)), tui.Green(newDomain))
|
|
prep = append(prep, newDomain)
|
|
found = true
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
log.Error("Unknown wildcard domain: %s within %s", tui.Bold(tui.Red(s)), rep)
|
|
}
|
|
}
|
|
|
|
return prep
|
|
}
|
|
|
|
// MakeReplacements prepares the forward and backward replacements to be used in the proxy
|
|
func (r *Replacer) MakeReplacements() {
|
|
|
|
//
|
|
// Requests
|
|
//
|
|
r.ForwardReplacements = []string{}
|
|
r.ForwardReplacements = append(r.ForwardReplacements, []string{r.Phishing, r.Target}...)
|
|
|
|
log.Debug("[Forward | origins]: %d", len(r.OriginsMapping))
|
|
count := len(r.ForwardReplacements)
|
|
for extOrigin, subMapping := range r.OriginsMapping { // changes resource-1.phishing.
|
|
|
|
if strings.HasPrefix(subMapping, WildcardPrefix) {
|
|
// Ignoring wildcard at this stage
|
|
log.Debug("[Wildcard] %s - %s", tui.Yellow(subMapping), tui.Green(extOrigin))
|
|
continue
|
|
}
|
|
|
|
from := fmt.Sprintf("%s.%s", subMapping, r.Phishing)
|
|
to := extOrigin
|
|
rep := []string{from, to}
|
|
r.ForwardReplacements = append(r.ForwardReplacements, rep...)
|
|
|
|
count++
|
|
log.Debug("[Forward | replacements #%d]: %s > %s", count, tui.Yellow(rep[0]), tui.Green(to))
|
|
}
|
|
|
|
// Append wildcards at the end
|
|
for extOrigin, subMapping := range r.WildcardMapping {
|
|
from := fmt.Sprintf("%s.%s", subMapping, r.Phishing)
|
|
to := extOrigin
|
|
rep := []string{from, to}
|
|
r.ForwardReplacements = append(r.ForwardReplacements, rep...)
|
|
|
|
count++
|
|
log.Debug("[Wild Forward | replacements #%d]: %s > %s", count, tui.Yellow(rep[0]), tui.Green(to))
|
|
}
|
|
|
|
//
|
|
// Responses
|
|
//
|
|
r.BackwardReplacements = []string{}
|
|
r.BackwardReplacements = append(r.BackwardReplacements, []string{r.Target, r.Phishing}...)
|
|
|
|
count = 0
|
|
for include, subMapping := range r.OriginsMapping {
|
|
|
|
if strings.HasPrefix(subMapping, WildcardPrefix) {
|
|
// Ignoring wildcard at this stage
|
|
log.Debug("[Wildcard] %s - %s", tui.Yellow(subMapping), tui.Green(include))
|
|
continue
|
|
}
|
|
|
|
from := include
|
|
to := fmt.Sprintf("%s.%s", subMapping, r.Phishing)
|
|
rep := []string{from, to}
|
|
r.BackwardReplacements = append(r.BackwardReplacements, rep...)
|
|
|
|
count++
|
|
log.Debug("[Backward | replacements #%d]: %s < %s", count, tui.Green(rep[0]), tui.Yellow(to))
|
|
}
|
|
|
|
// Append wildcards at the end
|
|
for include, subMapping := range r.WildcardMapping {
|
|
from := include
|
|
to := fmt.Sprintf("%s.%s", subMapping, r.Phishing)
|
|
rep := []string{from, to}
|
|
r.BackwardReplacements = append(r.BackwardReplacements, rep...)
|
|
|
|
count++
|
|
log.Debug("[Wild Backward | replacements #%d]: %s < %s", count, tui.Green(rep[0]), tui.Yellow(to))
|
|
}
|
|
|
|
//
|
|
// These should be done as Final replacements
|
|
r.LastBackwardReplacements = []string{}
|
|
|
|
// Custom HTTP response replacements
|
|
for _, tr := range r.CustomResponseTransformations {
|
|
r.LastBackwardReplacements = append(r.LastBackwardReplacements, tr...)
|
|
log.Debug("[Custom Replacements] %+v", tr)
|
|
}
|
|
|
|
r.BackwardReplacements = append(r.BackwardReplacements, r.LastBackwardReplacements...)
|
|
|
|
}
|
|
|
|
func (r *Replacer) DomainMapping() (err error) {
|
|
|
|
d := strings.Split(r.Target, ".")
|
|
baseDom := fmt.Sprintf("%s.%s", d[len(d)-2], d[len(d)-1])
|
|
log.Info("Proxy destination: %s", tui.Bold(tui.Green("*."+baseDom)))
|
|
|
|
r.ExternalOrigin = ArmorDomain(r.ExternalOrigin)
|
|
r.OriginsMapping = make(map[string]string)
|
|
r.WildcardMapping = make(map[string]string)
|
|
|
|
count, wildcards := 0, 0
|
|
for _, domain := range r.ExternalOrigin {
|
|
if IsSubdomain(baseDom, domain) {
|
|
trim := strings.TrimSuffix(domain, baseDom)
|
|
|
|
// We don't map 1-level subdomains ..
|
|
if strings.Count(trim, ".") < 2 {
|
|
log.Debug("Ignore: %s [%s]", domain, trim)
|
|
continue
|
|
}
|
|
}
|
|
|
|
if isWildcard(domain) {
|
|
// Wildcard domains
|
|
wildcards++
|
|
|
|
// Fix domain by removing the prefix *.
|
|
domain = strings.TrimPrefix(domain, "*.")
|
|
|
|
// Update the wildcard map
|
|
prefix := fmt.Sprintf("%s%s", r.ExternalOriginPrefix, WildcardPrefix)
|
|
o := fmt.Sprintf("%s%d", prefix, wildcards)
|
|
r.WildcardDomain = o
|
|
r.WildcardMapping[domain] = o
|
|
//log.Info("Wild Including [%s]=%s", domain, o)
|
|
log.Info(fmt.Sprintf("Wild Including [%s]=%s", domain, o))
|
|
|
|
} else {
|
|
count++
|
|
// Extra domains or nested subdomains
|
|
o := fmt.Sprintf("%s%d", r.ExternalOriginPrefix, count)
|
|
r.OriginsMapping[domain] = o
|
|
//log.Info("Including [%s]=%s", domain, o)
|
|
log.Info(fmt.Sprintf("Including [%s]=%s", domain, o))
|
|
}
|
|
|
|
}
|
|
|
|
if wildcards > 0 {
|
|
Wildcards = true
|
|
}
|
|
|
|
log.Info("Processed %d domains to transform, %d are wildcards", count, wildcards)
|
|
|
|
return
|
|
}
|