mirror of https://github.com/gophish/gophish
Merge branch '124-capture-passwords'
commit
4810222404
|
@ -334,16 +334,11 @@ func API_Pages_Id(w http.ResponseWriter, r *http.Request) {
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "/:id and /:page_id mismatch"}, http.StatusBadRequest)
|
JSONResponse(w, models.Response{Success: false, Message: "/:id and /:page_id mismatch"}, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = p.Validate()
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Invalid attributes given"}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.ModifiedDate = time.Now()
|
p.ModifiedDate = time.Now()
|
||||||
p.UserId = ctx.Get(r, "user_id").(int64)
|
p.UserId = ctx.Get(r, "user_id").(int64)
|
||||||
err = models.PutPage(&p)
|
err = models.PutPage(&p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Error updating page"}, http.StatusInternalServerError)
|
JSONResponse(w, models.Response{Success: false, Message: "Error updating page: " + err.Error()}, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
JSONResponse(w, p, http.StatusOK)
|
JSONResponse(w, p, http.StatusOK)
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
-- +goose Up
|
||||||
|
-- SQL in section 'Up' is executed when this migration is applied
|
||||||
|
ALTER TABLE pages ADD COLUMN capture_credentials BOOLEAN;
|
||||||
|
ALTER TABLE pages ADD COLUMN capture_passwords BOOLEAN;
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- SQL section 'Down' is executed when this migration is rolled back
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
"github.com/gophish/gophish/config"
|
"github.com/gophish/gophish/config"
|
||||||
"gopkg.in/check.v1"
|
"gopkg.in/check.v1"
|
||||||
)
|
)
|
||||||
|
@ -61,6 +63,79 @@ func (s *ModelsSuite) TestPostGroupNoTargets(c *check.C) {
|
||||||
c.Assert(err, check.Equals, ErrNoTargetsSpecified)
|
c.Assert(err, check.Equals, ErrNoTargetsSpecified)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ModelsSuite) TestPostPage(c *check.C) {
|
||||||
|
html := `<html>
|
||||||
|
<head></head>
|
||||||
|
<body><form action="example.com">
|
||||||
|
<input name="username"/>
|
||||||
|
<input name="password" type="password"/>
|
||||||
|
</form></body>
|
||||||
|
</html>`
|
||||||
|
p := Page{
|
||||||
|
Name: "Test Page",
|
||||||
|
HTML: html,
|
||||||
|
}
|
||||||
|
// Check the capturing credentials and passwords
|
||||||
|
p.CaptureCredentials = true
|
||||||
|
p.CapturePasswords = true
|
||||||
|
err := PostPage(&p)
|
||||||
|
c.Assert(err, check.Equals, nil)
|
||||||
|
d, err := goquery.NewDocumentFromReader(strings.NewReader(p.HTML))
|
||||||
|
c.Assert(err, check.Equals, nil)
|
||||||
|
forms := d.Find("form")
|
||||||
|
forms.Each(func(i int, f *goquery.Selection) {
|
||||||
|
// Check the action has been set
|
||||||
|
a, _ := f.Attr("action")
|
||||||
|
c.Assert(a, check.Equals, "")
|
||||||
|
// Check the password still has a name
|
||||||
|
_, ok := f.Find("input[type=\"password\"]").Attr("name")
|
||||||
|
c.Assert(ok, check.Equals, true)
|
||||||
|
// Check the username is still correct
|
||||||
|
u, ok := f.Find("input").Attr("name")
|
||||||
|
c.Assert(ok, check.Equals, true)
|
||||||
|
c.Assert(u, check.Equals, "username")
|
||||||
|
})
|
||||||
|
// Check what happens when we don't capture passwords
|
||||||
|
p.CapturePasswords = false
|
||||||
|
p.HTML = html
|
||||||
|
err = PutPage(&p)
|
||||||
|
c.Assert(err, check.Equals, nil)
|
||||||
|
d, err = goquery.NewDocumentFromReader(strings.NewReader(p.HTML))
|
||||||
|
c.Assert(err, check.Equals, nil)
|
||||||
|
forms = d.Find("form")
|
||||||
|
forms.Each(func(i int, f *goquery.Selection) {
|
||||||
|
// Check the action has been set
|
||||||
|
a, _ := f.Attr("action")
|
||||||
|
c.Assert(a, check.Equals, "")
|
||||||
|
// Check the password still has a name
|
||||||
|
_, ok := f.Find("input[type=\"password\"]").Attr("name")
|
||||||
|
c.Assert(ok, check.Equals, false)
|
||||||
|
// Check the username is still correct
|
||||||
|
u, ok := f.Find("input").Attr("name")
|
||||||
|
c.Assert(ok, check.Equals, true)
|
||||||
|
c.Assert(u, check.Equals, "username")
|
||||||
|
})
|
||||||
|
// Finally, check when we don't capture credentials
|
||||||
|
p.CaptureCredentials = false
|
||||||
|
p.HTML = html
|
||||||
|
err = PutPage(&p)
|
||||||
|
c.Assert(err, check.Equals, nil)
|
||||||
|
d, err = goquery.NewDocumentFromReader(strings.NewReader(p.HTML))
|
||||||
|
c.Assert(err, check.Equals, nil)
|
||||||
|
forms = d.Find("form")
|
||||||
|
forms.Each(func(i int, f *goquery.Selection) {
|
||||||
|
// Check the action has been set
|
||||||
|
a, _ := f.Attr("action")
|
||||||
|
c.Assert(a, check.Equals, "")
|
||||||
|
// Check the password still has a name
|
||||||
|
_, ok := f.Find("input[type=\"password\"]").Attr("name")
|
||||||
|
c.Assert(ok, check.Equals, false)
|
||||||
|
// Check the username is still correct
|
||||||
|
_, ok = f.Find("input").Attr("name")
|
||||||
|
c.Assert(ok, check.Equals, false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ModelsSuite) TestPutUser(c *check.C) {
|
func (s *ModelsSuite) TestPutUser(c *check.C) {
|
||||||
u, err := GetUser(1)
|
u, err := GetUser(1)
|
||||||
u.Username = "admin_changed"
|
u.Username = "admin_changed"
|
||||||
|
|
|
@ -2,27 +2,71 @@ package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Page contains the fields used for a Page model
|
// Page contains the fields used for a Page model
|
||||||
type Page struct {
|
type Page struct {
|
||||||
Id int64 `json:"id" gorm:"column:id; primary_key:yes"`
|
Id int64 `json:"id" gorm:"column:id; primary_key:yes"`
|
||||||
UserId int64 `json:"-" gorm:"column:user_id"`
|
UserId int64 `json:"-" gorm:"column:user_id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
HTML string `json:"html" gorm:"column:html"`
|
HTML string `json:"html" gorm:"column:html"`
|
||||||
ModifiedDate time.Time `json:"modified_date"`
|
CaptureCredentials bool `json:"capture_credentials" gorm:"column:capture_credentials"`
|
||||||
|
CapturePasswords bool `json:"capture_passwords" gorm:"column:capture_passwords"`
|
||||||
|
ModifiedDate time.Time `json:"modified_date"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrPageNameNotSpecified is thrown if the name of the landing page is blank.
|
// ErrPageNameNotSpecified is thrown if the name of the landing page is blank.
|
||||||
var ErrPageNameNotSpecified = errors.New("Page Name not specified")
|
var ErrPageNameNotSpecified = errors.New("Page Name not specified")
|
||||||
|
|
||||||
|
// parseHTML parses the page HTML on save to handle the
|
||||||
|
// capturing (or lack thereof!) of credentials and passwords
|
||||||
|
func (p *Page) parseHTML() error {
|
||||||
|
d, err := goquery.NewDocumentFromReader(strings.NewReader(p.HTML))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
forms := d.Find("form")
|
||||||
|
forms.Each(func(i int, f *goquery.Selection) {
|
||||||
|
// We always want the submitted events to be
|
||||||
|
// sent to our server
|
||||||
|
f.SetAttr("action", "")
|
||||||
|
if p.CaptureCredentials {
|
||||||
|
// If we don't want to capture passwords,
|
||||||
|
// find all the password fields and remove the "name" attribute.
|
||||||
|
if !p.CapturePasswords {
|
||||||
|
passwordFields := f.Find("input[type=\"password\"]")
|
||||||
|
passwordFields.Each(func(j int, pass *goquery.Selection) {
|
||||||
|
pass.RemoveAttr("name")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise, remove the name from all
|
||||||
|
// inputs.
|
||||||
|
inputFields := f.Find("input")
|
||||||
|
inputFields.Each(func(j int, input *goquery.Selection) {
|
||||||
|
input.RemoveAttr("name")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
p.HTML, err = d.Html()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Validate ensures that a page contains the appropriate details
|
// Validate ensures that a page contains the appropriate details
|
||||||
func (p *Page) Validate() error {
|
func (p *Page) Validate() error {
|
||||||
if p.Name == "" {
|
if p.Name == "" {
|
||||||
return ErrPageNameNotSpecified
|
return ErrPageNameNotSpecified
|
||||||
}
|
}
|
||||||
return nil
|
// If the user specifies to capture passwords,
|
||||||
|
// we automatically capture credentials
|
||||||
|
if p.CapturePasswords && !p.CaptureCredentials {
|
||||||
|
p.CaptureCredentials = true
|
||||||
|
}
|
||||||
|
return p.parseHTML()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPages returns the pages owned by the given user.
|
// GetPages returns the pages owned by the given user.
|
||||||
|
@ -74,7 +118,8 @@ func PostPage(p *Page) error {
|
||||||
// PutPage edits an existing Page in the database.
|
// PutPage edits an existing Page in the database.
|
||||||
// Per the PUT Method RFC, it presumes all data for a page is provided.
|
// Per the PUT Method RFC, it presumes all data for a page is provided.
|
||||||
func PutPage(p *Page) error {
|
func PutPage(p *Page) error {
|
||||||
err := db.Where("id=?", p.Id).Save(p).Error
|
err := p.Validate()
|
||||||
|
err = db.Where("id=?", p.Id).Save(p).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Logger.Println(err)
|
Logger.Println(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -458,3 +458,6 @@ td.details-control{
|
||||||
#refresh_message{
|
#refresh_message{
|
||||||
display:none;
|
display:none;
|
||||||
}
|
}
|
||||||
|
#capture_passwords {
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,10 @@ var pages = []
|
||||||
function save(idx) {
|
function save(idx) {
|
||||||
var page = {}
|
var page = {}
|
||||||
page.name = $("#name").val()
|
page.name = $("#name").val()
|
||||||
page.html = CKEDITOR.instances["html_editor"].getData();
|
editor = CKEDITOR.instances["html_editor"]
|
||||||
|
page.html = editor.getData()
|
||||||
|
page.capture_credentials = $("#capture_credentials_checkbox").prop("checked")
|
||||||
|
page.capture_passwords = $("#capture_passwords_checkbox").prop("checked")
|
||||||
if (idx != -1) {
|
if (idx != -1) {
|
||||||
page.id = pages[idx].id
|
page.id = pages[idx].id
|
||||||
api.pageId.put(page)
|
api.pageId.put(page)
|
||||||
|
@ -36,6 +39,8 @@ function dismiss() {
|
||||||
$("#modal\\.flashes").empty()
|
$("#modal\\.flashes").empty()
|
||||||
$("#name").val("")
|
$("#name").val("")
|
||||||
$("#html_editor").val("")
|
$("#html_editor").val("")
|
||||||
|
$("#newLandingPageModal").find("input[type='checkbox']").prop("checked", false)
|
||||||
|
$("#capture_passwords").hide()
|
||||||
$("#newLandingPageModal").modal('hide')
|
$("#newLandingPageModal").modal('hide')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +84,11 @@ function edit(idx) {
|
||||||
page = pages[idx]
|
page = pages[idx]
|
||||||
$("#name").val(page.name)
|
$("#name").val(page.name)
|
||||||
$("#html_editor").val(page.html)
|
$("#html_editor").val(page.html)
|
||||||
|
$("#capture_credentials_checkbox").prop("checked", page.capture_credentials)
|
||||||
|
$("#capture_passwords_checkbox").prop("checked", page.capture_passwords)
|
||||||
|
if (page.capture_credentials){
|
||||||
|
$("#capture_passwords").show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,5 +187,8 @@ $(document).ready(function() {
|
||||||
}
|
}
|
||||||
}, this));
|
}, this));
|
||||||
};
|
};
|
||||||
|
$("#capture_credentials_checkbox").change(function(){
|
||||||
|
$("#capture_passwords").toggle()
|
||||||
|
})
|
||||||
load()
|
load()
|
||||||
})
|
})
|
||||||
|
|
|
@ -80,6 +80,17 @@
|
||||||
<textarea id="html_editor"></textarea>
|
<textarea id="html_editor"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="checkbox checkbox-primary">
|
||||||
|
<input id="capture_credentials_checkbox" type="checkbox">
|
||||||
|
<label for="capture_credentials_checkbox">Capture Submitted Data <i class="fa fa-question-circle" data-toggle="tooltip" data-placement="right" title="If the landing page contains a form, submitted input (except passwords!) will be captured."></i></label>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox checkbox-primary" id="capture_passwords">
|
||||||
|
<input id="capture_passwords_checkbox" type="checkbox">
|
||||||
|
<label for="capture_passwords_checkbox">Capture Passwords</label>
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<i class="fa fa-exclamation-circle"></i> <b>Warning:</b> Credentials are currently <b>not encrypted</b>. This means that captured passwords are stored in the database as cleartext. Be careful with this!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" data-dismiss="modal" class="btn btn-default" onclick="dismiss()">Cancel</button>
|
<button type="button" data-dismiss="modal" class="btn btn-default" onclick="dismiss()">Cancel</button>
|
||||||
|
|
Loading…
Reference in New Issue