Merge pull request #1 from gophish/master

Catching up to the main branch from gophish/master
pull/91/head
Justin Gray 2016-01-20 22:27:58 -06:00
commit cf97520ddf
29 changed files with 1291 additions and 948 deletions

View File

@ -3,11 +3,11 @@
gophish
=======
[![Build Status](https://travis-ci.org/gophish/gophish.svg?branch=master)](https://travis-ci.org/gophish/gophish)
[![Build Status](https://travis-ci.org/gophish/gophish.svg?branch=master)](https://travis-ci.org/gophish/gophish) [![GoDoc](https://godoc.org/github.com/gophish/gophish?status.svg)](https://godoc.org/github.com/gophish/gophish)
Open-Source Phishing Toolkit
Gophish is an open-source phishing toolkit designed for businesses and penetration testers. It provides the ability to quickly and easily setup and execute phishing engagements and security awareness training.
[Gophish](https://getgophish.com) is an open-source phishing toolkit designed for businesses and penetration testers. It provides the ability to quickly and easily setup and execute phishing engagements and security awareness training.
###Current Status
**Update 01/12/2016**

View File

@ -1,10 +1,21 @@
{
"admin_url" : "127.0.0.1:3333",
"phish_url" : "0.0.0.0:80",
"admin_server" : {
"listen_url" : "127.0.0.1:3333",
"use_tls" : false,
"cert_path" : "example.crt",
"key_path" : "example.key"
},
"phish_server" : {
"listen_url" : "0.0.0.0:80",
"use_tls" : false,
"cert_path" : "example.crt",
"key_path": "example.key"
},
"smtp" : {
"host" : "smtp.example.com:25",
"user" : "username",
"pass" : "password"
},
"dbpath" : "gophish.db"
"db_path" : "gophish.db",
"migrations_path" : "db/migrations/"
}

View File

@ -13,12 +13,29 @@ type SMTPServer struct {
Password string `json:"password"`
}
// AdminServer represents the Admin server configuration details
type AdminServer struct {
ListenURL string `json:"listen_url"`
UseTLS bool `json:"use_tls"`
CertPath string `json:"cert_path"`
KeyPath string `json:"key_path"`
}
// PhishServer represents the Phish server configuration details
type PhishServer struct {
ListenURL string `json:"listen_url"`
UseTLS bool `json:"use_tls"`
CertPath string `json:"cert_path"`
KeyPath string `json:"key_path"`
}
// Config represents the configuration information.
type Config struct {
AdminURL string `json:"admin_url"`
PhishURL string `json:"phish_url"`
SMTP SMTPServer `json:"smtp"`
DBPath string `json:"dbpath"`
AdminConf AdminServer `json:"admin_server"`
PhishConf PhishServer `json:"phish_server"`
SMTPConf SMTPServer `json:"smtp"`
DBPath string `json:"db_path"`
MigrationsPath string `json:"migrations_path"`
}
var Conf Config

View File

@ -10,14 +10,14 @@ import (
"time"
"github.com/PuerkitoBio/goquery"
ctx "github.com/gorilla/context"
"github.com/gorilla/mux"
"github.com/jinzhu/gorm"
"github.com/jordan-wright/email"
"github.com/gophish/gophish/auth"
"github.com/gophish/gophish/models"
"github.com/gophish/gophish/util"
"github.com/gophish/gophish/worker"
ctx "github.com/gorilla/context"
"github.com/gorilla/mux"
"github.com/jinzhu/gorm"
"github.com/jordan-wright/email"
)
// Worker is the worker that processes phishing events and updates campaigns.
@ -92,6 +92,7 @@ func API_Campaigns_Id(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.ParseInt(vars["id"], 0, 64)
c, err := models.GetCampaign(id, ctx.Get(r, "user_id").(int64))
if err != nil {
Logger.Println(err)
JSONResponse(w, models.Response{Success: false, Message: "Campaign not found"}, http.StatusNotFound)
return
}

View File

@ -9,9 +9,9 @@ import (
"os"
"testing"
"github.com/gorilla/handlers"
"github.com/gophish/gophish/config"
"github.com/gophish/gophish/models"
"github.com/gorilla/handlers"
"github.com/stretchr/testify/suite"
)
@ -26,13 +26,14 @@ var as *httptest.Server = httptest.NewUnstartedServer(handlers.CombinedLoggingHa
func (s *ControllersSuite) SetupSuite() {
config.Conf.DBPath = ":memory:"
config.Conf.MigrationsPath = "../db/migrations/"
err := models.Setup()
if err != nil {
s.T().Fatalf("Failed creating database: %v", err)
}
s.Nil(err)
// Setup the admin server for use in testing
as.Config.Addr = config.Conf.AdminURL
as.Config.Addr = config.Conf.AdminConf.ListenURL
as.Start()
// Get the API key to use for these tests
u, err := models.GetUser(1)

View File

@ -0,0 +1,28 @@
-- +goose Up
-- SQL in section 'Up' is executed when this migration is applied
CREATE TABLE IF NOT EXISTS "users" ("id" integer primary key autoincrement,"username" varchar(255) NOT NULL UNIQUE,"hash" varchar(255),"api_key" varchar(255) NOT NULL UNIQUE );
CREATE TABLE IF NOT EXISTS "templates" ("id" integer primary key autoincrement,"user_id" bigint,"name" varchar(255),"subject" varchar(255),"text" varchar(255),"html" varchar(255),"modified_date" datetime );
CREATE TABLE IF NOT EXISTS "targets" ("id" integer primary key autoincrement,"first_name" varchar(255),"last_name" varchar(255),"email" varchar(255),"position" varchar(255) );
CREATE TABLE IF NOT EXISTS "smtp" ("smtp_id" integer primary key autoincrement,"campaign_id" bigint,"host" varchar(255),"username" varchar(255),"from_address" varchar(255) );
CREATE TABLE IF NOT EXISTS "results" ("id" integer primary key autoincrement,"campaign_id" bigint,"user_id" bigint,"r_id" varchar(255),"email" varchar(255),"first_name" varchar(255),"last_name" varchar(255),"status" varchar(255) NOT NULL ,"ip" varchar(255),"latitude" real,"longitude" real );
CREATE TABLE IF NOT EXISTS "pages" ("id" integer primary key autoincrement,"user_id" bigint,"name" varchar(255),"html" varchar(255),"modified_date" datetime );
CREATE TABLE IF NOT EXISTS "groups" ("id" integer primary key autoincrement,"user_id" bigint,"name" varchar(255),"modified_date" datetime );
CREATE TABLE IF NOT EXISTS "group_targets" ("group_id" bigint,"target_id" bigint );
CREATE TABLE IF NOT EXISTS "events" ("id" integer primary key autoincrement,"campaign_id" bigint,"email" varchar(255),"time" datetime,"message" varchar(255) );
CREATE TABLE IF NOT EXISTS "campaigns" ("id" integer primary key autoincrement,"user_id" bigint,"name" varchar(255) NOT NULL ,"created_date" datetime,"completed_date" datetime,"template_id" bigint,"page_id" bigint,"status" varchar(255),"url" varchar(255) );
CREATE TABLE IF NOT EXISTS "attachments" ("id" integer primary key autoincrement,"template_id" bigint,"content" varchar(255),"type" varchar(255),"name" varchar(255) );
-- +goose Down
-- SQL section 'Down' is executed when this migration is rolled back
DROP TABLE "attachments";
DROP TABLE "campaigns";
DROP TABLE "events";
DROP TABLE "group_targets";
DROP TABLE "groups";
DROP TABLE "pages";
DROP TABLE "results";
DROP TABLE "smtp";
DROP TABLE "targets";
DROP TABLE "templates";
DROP TABLE "users";

View File

@ -30,11 +30,12 @@ import (
"log"
"net/http"
"os"
"sync"
"github.com/gorilla/handlers"
"github.com/gophish/gophish/config"
"github.com/gophish/gophish/controllers"
"github.com/gophish/gophish/models"
"github.com/gorilla/handlers"
)
var Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile)
@ -45,9 +46,31 @@ func main() {
if err != nil {
fmt.Println(err)
}
wg := &sync.WaitGroup{}
wg.Add(1)
// Start the web servers
Logger.Printf("Admin server started at http://%s\n", config.Conf.AdminURL)
go http.ListenAndServe(config.Conf.AdminURL, handlers.CombinedLoggingHandler(os.Stdout, controllers.CreateAdminRouter()))
Logger.Printf("Phishing server started at http://%s\n", config.Conf.PhishURL)
http.ListenAndServe(config.Conf.PhishURL, handlers.CombinedLoggingHandler(os.Stdout, controllers.CreatePhishingRouter()))
go func() {
defer wg.Done()
if config.Conf.AdminConf.UseTLS { // use TLS for Admin web server if available
Logger.Printf("Starting admin server at https://%s\n", config.Conf.AdminConf.ListenURL)
Logger.Fatal(http.ListenAndServeTLS(config.Conf.AdminConf.ListenURL, config.Conf.AdminConf.CertPath, config.Conf.AdminConf.KeyPath,
handlers.CombinedLoggingHandler(os.Stdout, controllers.CreateAdminRouter())))
} else {
Logger.Printf("Starting admin server at http://%s\n", config.Conf.AdminConf.ListenURL)
Logger.Fatal(http.ListenAndServe(config.Conf.AdminConf.ListenURL, handlers.CombinedLoggingHandler(os.Stdout, controllers.CreateAdminRouter())))
}
}()
wg.Add(1)
go func() {
defer wg.Done()
if config.Conf.PhishConf.UseTLS { // use TLS for Phish web server if available
Logger.Printf("Starting phishing server at https://%s\n", config.Conf.PhishConf.ListenURL)
Logger.Fatal(http.ListenAndServeTLS(config.Conf.PhishConf.ListenURL, config.Conf.PhishConf.CertPath, config.Conf.PhishConf.KeyPath,
handlers.CombinedLoggingHandler(os.Stdout, controllers.CreatePhishingRouter())))
} else {
Logger.Printf("Starting phishing server at http://%s\n", config.Conf.PhishConf.ListenURL)
Logger.Fatal(http.ListenAndServe(config.Conf.PhishConf.ListenURL, handlers.CombinedLoggingHandler(os.Stdout, controllers.CreatePhishingRouter())))
}
}()
wg.Wait()
}

View File

@ -117,21 +117,28 @@ func GetCampaign(id int64, uid int64) (Campaign, error) {
c := Campaign{}
err := db.Where("id = ?", id).Where("user_id = ?", uid).Find(&c).Error
if err != nil {
Logger.Printf("%s: campaign not found\n", err)
return c, err
}
err = db.Model(&c).Related(&c.Results).Error
if err != nil {
Logger.Printf("%s: results not found for campaign\n", err)
return c, err
}
err = db.Model(&c).Related(&c.Events).Error
if err != nil {
Logger.Printf("%s: events not found for campaign\n", err)
return c, err
}
err = db.Table("templates").Where("id=?", c.TemplateId).Find(&c.Template).Error
if err != nil {
Logger.Printf("%s: template not found for campaign\n", err)
return c, err
}
err = db.Table("pages").Where("id=?", c.PageId).Find(&c.Page).Error
if err != nil {
Logger.Printf("%s: page not found for campaign\n", err)
}
return c, err
}
@ -207,6 +214,7 @@ func PostCampaign(c *Campaign, uid int64) error {
//DeleteCampaign deletes the specified campaign
func DeleteCampaign(id int64) error {
Logger.Printf("Deleting campaign %d\n", id)
// Delete all the campaign results
err := db.Where("campaign_id=?", id).Delete(&Result{}).Error
if err != nil {

View File

@ -8,6 +8,8 @@ import (
"log"
"os"
"bitbucket.org/liamstask/goose/lib/goose"
"github.com/gophish/gophish/config"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3" // Blank import needed to import sqlite3
@ -62,6 +64,24 @@ func Setup() error {
if _, err = os.Stat(config.Conf.DBPath); err != nil || config.Conf.DBPath == ":memory:" {
create_db = true
}
// Setup the goose configuration
migrateConf := &goose.DBConf{
MigrationsDir: config.Conf.MigrationsPath,
Env: "production",
Driver: goose.DBDriver{
Name: "sqlite3",
OpenStr: config.Conf.DBPath,
Import: "github.com/mattn/go-sqlite3",
Dialect: &goose.Sqlite3Dialect{},
},
}
// Get the latest possible migration
latest, err := goose.GetMostRecentDBVersion(migrateConf.MigrationsDir)
if err != nil {
Logger.Println(err)
return err
}
// Open our database connection
db, err = gorm.Open("sqlite3", config.Conf.DBPath)
db.LogMode(false)
db.SetLogger(Logger)
@ -69,20 +89,14 @@ func Setup() error {
Logger.Println(err)
return err
}
//If the file already exists, delete it and recreate it
// Migrate up to the latest version
err = goose.RunMigrationsOnDb(migrateConf, migrateConf.MigrationsDir, latest, db.DB())
if err != nil {
Logger.Println(err)
return err
}
//If the database didn't exist, we need to create the admin user
if create_db {
Logger.Printf("Database not found... creating db at %s\n", config.Conf.DBPath)
db.CreateTable(User{})
db.CreateTable(Target{})
db.CreateTable(Result{})
db.CreateTable(Group{})
db.CreateTable(GroupTarget{})
db.CreateTable(Template{})
db.CreateTable(Attachment{})
db.CreateTable(Page{})
db.CreateTable(SMTP{})
db.CreateTable(Event{})
db.CreateTable(Campaign{})
//Create the default user
initUser := User{
Username: "admin",
@ -92,6 +106,7 @@ func Setup() error {
err = db.Save(&initUser).Error
if err != nil {
Logger.Println(err)
return err
}
}
return nil

View File

@ -16,6 +16,7 @@ var _ = check.Suite(&ModelsSuite{})
func (s *ModelsSuite) SetUpSuite(c *check.C) {
config.Conf.DBPath = ":memory:"
config.Conf.MigrationsPath = "../db/migrations/"
err := Setup()
if err != nil {
c.Fatalf("Failed creating database: %v", err)

30
static/css/main.css vendored
View File

@ -341,3 +341,33 @@
float:none !important;
}
}
/* Table Styling */
.modal-content .dataTable tbody td {
font-size: 16px;/* Smaller font on modal tables */
}
.dataTables_info{
font-size: 15px;
}
/* Sort Icons */
table.dataTable thead .sorting:after, table.dataTable thead .sorting_asc:after, table.dataTable thead .sorting_desc:after {
font-family: 'FontAwesome' !important;
position: relative !important;
display: initial !important;
top: initial!important;
right: initial!important;
left: 6px;
color: #1abc9c;
}
table.dataTable thead .sorting:after{
content: "\f0dc" !important;
color: initial;
}
table.dataTable thead .sorting_asc:after {
content: "\f0de" !important;
opacity: .8 !important;
}
table.dataTable thead .sorting_desc:after {
content: "\f0dd" !important;
opacity: .8 !important;
}

View File

@ -47,7 +47,7 @@ function deleteCampaign(){
if (confirm("Are you sure you want to delete: " + campaign.name + "?")) {
api.campaignId.delete(campaign.id)
.success(function(msg) {
console.log(msg)
location.href = '/campaigns'
})
.error(function(e) {
$("#modal\\.flashes").empty().append("<div style=\"text-align:center\" class=\"alert alert-danger\">\
@ -56,6 +56,26 @@ function deleteCampaign(){
}
}
// Exports campaign results as a CSV file
function exportAsCSV() {
exportHTML = $("#exportButton").html()
$("#exportButton").html('<i class="fa fa-spinner fa-spin"></i>')
var csvString = Papa.unparse(campaign.results, {})
var csvData = new Blob([csvString], {
type: 'text/csv;charset=utf-8;'
});
if (navigator.msSaveBlob) {
navigator.msSaveBlob(csvData, 'results.csv');
} else {
var csvURL = window.URL.createObjectURL(csvData);
var dlLink = document.createElement('a');
dlLink.href = csvURL;
dlLink.setAttribute('download', 'results.csv');
dlLink.click();
}
$("#exportButton").html(exportHTML)
}
$(document).ready(function() {
campaign.id = window.location.pathname.split('/').slice(-1)[0]
api.campaignId.get(campaign.id)
@ -67,11 +87,15 @@ $(document).ready(function(){
// Setup tooltips
$('[data-toggle="tooltip"]').tooltip()
// Setup our graphs
var timeline_data = {series:[{
var timeline_data = {
series: [{
name: "Events",
data: []
}]}
var email_data = {series:[]}
}]
}
var email_data = {
series: []
}
var email_legend = {}
var email_series_data = {}
var timeline_opts = {
@ -117,10 +141,17 @@ $(document).ready(function(){
})
// Setup the graphs
$.each(campaign.timeline, function(i, event) {
timeline_data.series[0].data.push({meta : i, x: new Date(event.time), y:1})
timeline_data.series[0].data.push({
meta: i,
x: new Date(event.time),
y: 1
})
})
$.each(email_series_data, function(status, count) {
email_data.series.push({meta: status, value: count})
email_data.series.push({
meta: status,
value: count
})
})
var timeline_chart = new Chartist.Line('#timeline_chart', timeline_data, timeline_opts)
// Setup the overview chart listeners
@ -201,7 +232,9 @@ $(document).ready(function(){
bubbles = []
$.each(campaign.results, function(i, result) {
// Check that it wasn't an internal IP
if (result.latitude == 0 && result.longitude == 0) { return true; }
if (result.latitude == 0 && result.longitude == 0) {
return true;
}
newIP = true
$.each(bubbles, function(i, bubble) {
if (bubble.ip == result.ip) {

View File

@ -8,13 +8,19 @@ var labels = {
"Error": "label-danger"
}
var campaigns = []
// Save attempts to POST to /campaigns/
function save(){
function launch() {
if (!confirm("This will launch the campaign. Are you sure?")) {
return false;
}
groups = []
$.each($("#groupTable").DataTable().rows().data(), function(i, group) {
groups.push({name: group[0]})
groups.push({
name: group[0]
})
})
console.log(groups)
var campaign = {
name: $("#name").val(),
template: {
@ -36,7 +42,7 @@ function save(){
api.campaigns.post(campaign)
.success(function(data) {
successFlash("Campaign successfully launched!")
window.location = "/campaigns/" + campaign.id.toString()
window.location = "/campaigns/" + data.id.toString()
})
.error(function(data) {
$("#modal\\.flashes").empty().append("<div style=\"text-align:center\" class=\"alert alert-danger\">\
@ -50,6 +56,16 @@ function dismiss(){
$("#groupTable").dataTable().DataTable().clear().draw()
}
function deleteCampaign(idx) {
if (confirm("Delete " + campaigns[idx].name + "?")) {
api.campaignId.delete(campaigns[idx].id)
.success(function(data) {
successFlash(data.message)
location.reload()
})
}
}
function edit(campaign) {
// Clear the bloodhound instance
group_bh.clear();
@ -61,8 +77,7 @@ function edit(campaign){
if (groups.length == 0) {
modalError("No groups found!")
return false;
}
else {
} else {
group_bh.add(groups)
}
})
@ -71,8 +86,7 @@ function edit(campaign){
if (templates.length == 0) {
modalError("No templates found!")
return false
}
else {
} else {
template_bh.add(templates)
}
})
@ -81,8 +95,7 @@ function edit(campaign){
if (pages.length == 0) {
modalError("No pages found!")
return false
}
else {
} else {
page_bh.add(pages)
}
})
@ -91,7 +104,14 @@ function edit(campaign){
$(document).ready(function() {
api.campaigns.get()
.success(function(campaigns){
.success(function(cs) {
campaigns = cs
campaignTable = $("#campaignTable").DataTable({
columnDefs: [{
orderable: false,
targets: "no-sort"
}]
});
$("#loading").hide()
if (campaigns.length > 0) {
$("#campaignTable").show()
@ -102,13 +122,14 @@ $(document).ready(function(){
campaign.name,
moment(campaign.created_date).format('MMMM Do YYYY, h:mm:ss a'),
"<span class=\"label " + label + "\">" + campaign.status + "</span>",
"<div class='pull-right'><a class='btn btn-primary' href='/campaigns/" + campaign.id + "'>\
"<div class='pull-right'><a class='btn btn-primary' href='/campaigns/" + campaign.id + "' data-toggle='tooltip' data-placement='left' title='View Results'>\
<i class='fa fa-bar-chart'></i>\
</a>\
<button class='btn btn-danger' onclick='alert(\"test\")'>\
<button class='btn btn-danger' onclick='deleteCampaign(" + i + ")' data-toggle='tooltip' data-placement='left' title='Delete Campaign'>\
<i class='fa fa-trash-o'></i>\
</button></div>"
]).draw()
$('[data-toggle="tooltip"]').tooltip()
})
} else {
$("#emptyMessage").show()
@ -131,9 +152,16 @@ $(document).ready(function(){
return false;
})
// Create the group typeahead objects
groupTable = $("#groupTable").DataTable()
groupTable = $("#groupTable").DataTable({
columnDefs: [{
orderable: false,
targets: "no-sort"
}]
})
group_bh = new Bloodhound({
datumTokenizer: function(g) { return Bloodhound.tokenizers.whitespace(g.name) },
datumTokenizer: function(g) {
return Bloodhound.tokenizers.whitespace(g.name)
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
local: []
})
@ -142,13 +170,16 @@ $(document).ready(function(){
hint: true,
highlight: true,
minLength: 1
},
{
}, {
name: "groups",
source: group_bh,
templates: {
empty: function(data) {return '<div class="tt-suggestion">No groups matched that query</div>' },
suggestion: function(data){ return '<div>' + data.name + '</div>' }
empty: function(data) {
return '<div class="tt-suggestion">No groups matched that query</div>'
},
suggestion: function(data) {
return '<div>' + data.name + '</div>'
}
}
})
.bind('typeahead:select', function(ev, group) {
@ -159,7 +190,9 @@ $(document).ready(function(){
});
// Create the template typeahead objects
template_bh = new Bloodhound({
datumTokenizer: function(t) { return Bloodhound.tokenizers.whitespace(t.name) },
datumTokenizer: function(t) {
return Bloodhound.tokenizers.whitespace(t.name)
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
local: []
})
@ -168,13 +201,16 @@ $(document).ready(function(){
hint: true,
highlight: true,
minLength: 1
},
{
}, {
name: "templates",
source: template_bh,
templates: {
empty: function(data) {return '<div class="tt-suggestion">No templates matched that query</div>' },
suggestion: function(data){ return '<div>' + data.name + '</div>' }
empty: function(data) {
return '<div class="tt-suggestion">No templates matched that query</div>'
},
suggestion: function(data) {
return '<div>' + data.name + '</div>'
}
}
})
.bind('typeahead:select', function(ev, template) {
@ -185,7 +221,9 @@ $(document).ready(function(){
});
// Create the landing page typeahead objects
page_bh = new Bloodhound({
datumTokenizer: function(p) { return Bloodhound.tokenizers.whitespace(p.name) },
datumTokenizer: function(p) {
return Bloodhound.tokenizers.whitespace(p.name)
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
local: []
})
@ -194,13 +232,16 @@ $(document).ready(function(){
hint: true,
highlight: true,
minLength: 1
},
{
}, {
name: "pages",
source: page_bh,
templates: {
empty: function(data) {return '<div class="tt-suggestion">No pages matched that query</div>' },
suggestion: function(data){ return '<div>' + data.name + '</div>' }
empty: function(data) {
return '<div class="tt-suggestion">No pages matched that query</div>'
},
suggestion: function(data) {
return '<div>' + data.name + '</div>'
}
}
})
.bind('typeahead:select', function(ev, page) {

View File

@ -9,6 +9,16 @@ var labels = {
"Error": "label-danger"
}
function deleteCampaign(idx) {
if (confirm("Delete " + campaigns[idx].name + "?")) {
api.campaignId.delete(campaigns[idx].id)
.success(function(data) {
successFlash(data.message)
location.reload()
})
}
}
$(document).ready(function() {
api.campaigns.get()
.success(function(cs) {
@ -17,8 +27,15 @@ $(document).ready(function(){
if (campaigns.length > 0) {
$("#dashboard").show()
// Create the overview chart data
var overview_data = {labels:[],series:[[]]}
var average_data = {series:[]}
var overview_data = {
labels: [],
series: [
[]
]
}
var average_data = {
series: []
}
var overview_opts = {
axisX: {
showGrid: false
@ -33,7 +50,11 @@ $(document).ready(function(){
showLabel: false
}
var average = 0
campaignTable = $("#campaignTable").DataTable();
campaignTable = $("#campaignTable").DataTable({
columnDefs: [
{ orderable: false, targets: "no-sort" }
]
});
$.each(campaigns, function(i, campaign) {
var campaign_date = moment(campaign.created_date).format('MMMM Do YYYY h:mm:ss a')
var label = labels[campaign.status] || "label-default";
@ -42,10 +63,10 @@ $(document).ready(function(){
campaign.name,
campaign_date,
"<span class=\"label " + label + "\">" + campaign.status + "</span>",
"<div class='pull-right'><a class='btn btn-primary' href='/campaigns/" + campaign.id + "'>\
"<div class='pull-right'><a class='btn btn-primary' href='/campaigns/" + campaign.id + "' data-toggle='tooltip' data-placement='right' title='View Results'>\
<i class='fa fa-bar-chart'></i>\
</a>\
<button class='btn btn-danger' onclick='deleteCampaign(" + i + ")'>\
<button class='btn btn-danger' onclick='deleteCampaign(" + i + ")' data-toggle='tooltip' data-placement='right' title='Delete Campaign'>\
<i class='fa fa-trash-o'></i>\
</button></div>"
]).draw()
@ -60,11 +81,20 @@ $(document).ready(function(){
average += campaign.y
// Add the data to the overview chart
overview_data.labels.push(campaign_date)
overview_data.series[0].push({meta : i, value: campaign.y})
overview_data.series[0].push({
meta: i,
value: campaign.y
})
})
average = Math.floor(average / campaigns.length);
average_data.series.push({meta: "Unsuccessful Phishes", value: 100 - average})
average_data.series.push({meta: "Successful Phishes", value: average})
average_data.series.push({
meta: "Unsuccessful Phishes",
value: 100 - average
})
average_data.series.push({
meta: "Successful Phishes",
value: average
})
// Build the charts
var average_chart = new Chartist.Pie("#average_chart", average_data, average_opts)
var overview_chart = new Chartist.Line('#overview_chart', overview_data, overview_opts)

View File

@ -4,6 +4,7 @@
Author: Jordan Wright <github.com/jordan-wright>
*/
var pages = []
// Save attempts to POST to /templates/
function save(idx) {
var page = {}
@ -69,7 +70,9 @@ function importSite(){
}
function edit(idx) {
$("#modalSubmit").unbind('click').click(function(){save(idx)})
$("#modalSubmit").unbind('click').click(function() {
save(idx)
})
$("#html_editor").ckeditor()
var page = {}
if (idx != -1) {
@ -92,7 +95,13 @@ function load(){
$("#loading").hide()
if (pages.length > 0) {
$("#pagesTable").show()
pagesTable = $("#pagesTable").DataTable();
pagesTable = $("#pagesTable").DataTable({
destroy: true,
columnDefs: [{
orderable: false,
targets: "no-sort"
}]
});
pagesTable.clear()
$.each(pages, function(i, page) {
pagesTable.row.add([
@ -125,13 +134,11 @@ $(document).ready(function(){
});
$('.modal').on('shown.bs.modal', function(event) {
// Keep track of the number of open modals
if ( typeof( $('body').data( 'fv_open_modals' ) ) == 'undefined' )
{
if (typeof($('body').data('fv_open_modals')) == 'undefined') {
$('body').data('fv_open_modals', 0);
}
// if the z-index of this modal has been set, ignore.
if ( $(this).hasClass( 'fv-modal-stack' ) )
{
if ($(this).hasClass('fv-modal-stack')) {
return;
}
$(this).addClass('fv-modal-stack');

View File

@ -15,7 +15,9 @@ var icons = {
// Save attempts to POST to /templates/
function save(idx) {
var template = {attachments:[]}
var template = {
attachments: []
}
template.name = $("#name").val()
template.subject = $("#subject").val()
template.html = CKEDITOR.instances["html_editor"].getData();
@ -78,7 +80,19 @@ function deleteTemplate(idx){
}
function attach(files) {
attachmentsTable = $("#attachmentsTable").DataTable();
attachmentsTable = $("#attachmentsTable").DataTable({
destroy: true,
"order": [
[1, "asc"]
],
columnDefs: [{
orderable: false,
targets: "no-sort"
}, {
sClass: "datatable_hidden",
targets: [3, 4]
}]
});
$.each(files, function(i, file) {
var reader = new FileReader();
/* Make this a datatable */
@ -101,23 +115,30 @@ function attach(files){
}
function edit(idx) {
$("#modalSubmit").unbind('click').click(function(){save(idx)})
$("#attachmentUpload").unbind('click').click(function(){this.value=null})
$("#modalSubmit").unbind('click').click(function() {
save(idx)
})
$("#attachmentUpload").unbind('click').click(function() {
this.value = null
})
$("#html_editor").ckeditor()
$("#attachmentsTable").show()
attachmentsTable = null
if ( $.fn.dataTable.isDataTable('#attachmentsTable') ) {
attachmentsTable = $('#attachmentsTable').DataTable();
}
else {
attachmentsTable = $("#attachmentsTable").DataTable({
"aoColumnDefs" : [{
"targets" : [3,4],
"sClass" : "datatable_hidden"
attachmentsTable = $('#attachmentsTable').DataTable({
destroy: true,
"order": [
[1, "asc"]
],
columnDefs: [{
orderable: false,
targets: "no-sort"
}, {
sClass: "datatable_hidden",
targets: [3, 4]
}]
});
var template = {
attachments: []
}
var template = {attachments:[]}
if (idx != -1) {
template = templates[idx]
$("#name").val(template.name)
@ -178,7 +199,13 @@ function load(){
$("#loading").hide()
if (templates.length > 0) {
$("#templateTable").show()
templateTable = $("#templateTable").DataTable();
templateTable = $("#templateTable").DataTable({
destroy: true,
columnDefs: [{
orderable: false,
targets: "no-sort"
}]
});
templateTable.clear()
$.each(templates, function(i, template) {
templateTable.row.add([
@ -211,13 +238,11 @@ $(document).ready(function(){
});
$('.modal').on('shown.bs.modal', function(event) {
// Keep track of the number of open modals
if ( typeof( $('body').data( 'fv_open_modals' ) ) == 'undefined' )
{
if (typeof($('body').data('fv_open_modals')) == 'undefined') {
$('body').data('fv_open_modals', 0);
}
// if the z-index of this modal has been set, ignore.
if ( $(this).hasClass( 'fv-modal-stack' ) )
{
if ($(this).hasClass('fv-modal-stack')) {
return;
}
$(this).addClass('fv-modal-stack');

View File

@ -25,6 +25,7 @@ function save(idx){
successFlash("Group updated successfully!")
load()
dismiss()
$("#modal").modal('hide')
})
.error(function(data) {
modalError(data.responseJSON.message)
@ -37,6 +38,7 @@ function save(idx){
successFlash("Group added successfully!")
load()
dismiss()
$("#modal").modal('hide')
})
.error(function(data) {
modalError(data.responseJSON.message)
@ -48,12 +50,19 @@ function dismiss(){
$("#targetsTable").dataTable().DataTable().clear().draw()
$("#name").val("")
$("#modal\\.flashes").empty()
$("#modal").modal('hide')
}
function edit(idx) {
targets = $("#targetsTable").dataTable()
$("#modalSubmit").unbind('click').click(function(){save(idx)})
targets = $("#targetsTable").dataTable({
destroy: true, // Destroy any other instantiated table - http://datatables.net/manual/tech-notes/3#destroy
columnDefs: [{
orderable: false,
targets: "no-sort"
}]
})
$("#modalSubmit").unbind('click').click(function() {
save(idx)
})
if (idx == -1) {
group = {}
} else {
@ -119,7 +128,13 @@ function load(){
groups = gs
$("#emptyMessage").hide()
$("#groupTable").show()
groupTable = $("#groupTable").DataTable();
groupTable = $("#groupTable").DataTable({
destroy: true,
columnDefs: [{
orderable: false,
targets: "no-sort"
}]
});
groupTable.clear();
$.each(groups, function(i, group) {
var targets = ""
@ -176,4 +191,7 @@ $(document).ready(function(){
.remove()
.draw();
})
$("#modal").on("hide.bs.modal", function() {
dismiss()
})
})

View File

@ -1,15 +1,13 @@
function errorFlash(message) {
$("#flashes").empty()
$("#flashes").append("<div style=\"text-align:center\" class=\"alert alert-danger\">\
<i class=\"fa fa-exclamation-circle\"></i> " + message + "</div>"
)
<i class=\"fa fa-exclamation-circle\"></i> " + message + "</div>")
}
function successFlash(message) {
$("#flashes").empty()
$("#flashes").append("<div style=\"text-align:center\" class=\"alert alert-success\">\
<i class=\"fa fa-check-circle\"></i> " + message + "</div>"
)
<i class=\"fa fa-check-circle\"></i> " + message + "</div>")
}
function modalError(message) {
@ -51,7 +49,7 @@ var api = {
},
// delete() - Deletes a campaign at DELETE /campaigns/:id
delete: function(id) {
return query("/campaigns/" + id, "DELETE", data)
return query("/campaigns/" + id, "DELETE", {})
}
},
// groups contains the endpoints for /groups

6
static/js/papaparse.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -30,22 +30,11 @@
<h1 class="page-header" id="page-title">Results for campaign.name</h1>
</div>
<div class="row">
<!--
<div class="btn-group">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-cogs fa-lg"></i>
<span class="caret"></span>
<button type="button" id="exportButton" class="btn btn-primary" onclick="exportAsCSV()">
<i class="fa fa-file-excel-o"></i> Export CSV
</button>
<ul class="dropdown-menu">
<li><a href="#">Export</a>
</li>
<li><a href="#">Relaunch</a>
</li>
</ul>
</div>
-->
<button type="button" class="btn btn-danger" data-toggle="tooltip" data-placement="right" title="Delete Campaign" onclick="deleteCampaign()">
<i class="fa fa-times fa-lg"></i>
<button type="button" class="btn btn-danger" data-toggle="tooltip" onclick="deleteCampaign()">
<i class="fa fa-trash-o fa-lg"></i> Delete
</button>
</div>
<br />
@ -114,5 +103,6 @@
<script src="/js/d3.min.js"></script>
<script src="/js/topojson.min.js"></script>
<script src="/js/datamaps.min.js"></script>
<script src="/js/papaparse.min.js"></script>
<script src="/js/app/campaign_results.js"></script>
{{end}}

View File

@ -47,7 +47,7 @@
<th>Name</th>
<th>Created Date</th>
<th>Status</th>
<th class="col-md-2"></th>
<th class="col-md-2 no-sort"></th>
</tr>
</thead>
<tbody>
@ -113,7 +113,7 @@
<table id="groupTable" class="table table-hover table-striped table-condensed">
<thead>
<th>Group Name</th>
<th></th>
<th class="no-sort"></th>
<tbody>
</tbody>
</table>
@ -121,7 +121,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" onclick="save()">Save changes</button>
<button type="button" class="btn btn-primary" onclick="launch()"><i class="fa fa-envelope"></i> Launch Campaign</button>
</div>
</div>
</div>

View File

@ -67,7 +67,7 @@
<th>Name</th>
<th>Created Date</th>
<th>Status</th>
<th class="col-md-2 col-sm-2"></th>
<th class="col-md-2 col-sm-2 no-sort"></th>
</tr>
</thead>
<tbody>

View File

@ -44,7 +44,7 @@
<tr>
<th>Name</th>
<th>Last Modified Date</th>
<th class="col-md-2"></th>
<th class="col-md-2 no-sort"></th>
</tr>
</thead>
<tbody>

View File

@ -46,7 +46,7 @@
<tr>
<th>Name</th>
<th>Modified Date</th>
<th class="col-md-2"></th>
<th class="col-md-2 no-sort"></th>
</tr>
</thead>
<tbody>
@ -102,11 +102,11 @@
<table id="attachmentsTable" class="table">
<thead>
<tr>
<th class="col-md-1"></th>
<th class="col-md-1 no-sort"></th>
<th class="col-md-10">Name</th>
<th class="col-md-1"></th>
<th class="datatable_hidden">Content</th>
<th class="datatable_hidden">Type</th>
<th class="col-md-1 no-sort"></th>
<th class="datatable_hidden no-sort">Content</th>
<th class="datatable_hidden no-sort">Type</th>
</tr>
</thead>
<tbody>

View File

@ -47,7 +47,7 @@
<th>Name</th>
<th>Members</th>
<th>Modified Date</th>
<th class="col-md-2"></th>
<th class="col-md-2 no-sort"></th>
</tr>
</thead>
<tbody>
@ -94,20 +94,20 @@
</form>
</div>
<br />
<table id="targetsTable" class="table table-hover table-striped">
<table id="targetsTable" class="table table-hover table-striped table-condensed">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Position</th>
<th></th>
<th class="no-sort"></th>
<tbody>
</tbody>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" onclick="dismiss()">Close</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="modalSubmit">Save changes</button>
</div>
</div>

28
util/doc.go Normal file
View File

@ -0,0 +1,28 @@
/*
gophish - Open-Source Phishing Framework
The MIT License (MIT)
Copyright (c) 2013 Jordan Wright
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// Package util provides misc. utility functions for gophish
package util

28
worker/doc.go Normal file
View File

@ -0,0 +1,28 @@
/*
gophish - Open-Source Phishing Framework
The MIT License (MIT)
Copyright (c) 2013 Jordan Wright
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// Package worker contains the functionality for the background worker process.
package worker

View File

@ -9,8 +9,8 @@ import (
"strings"
"text/template"
"github.com/jordan-wright/email"
"github.com/gophish/gophish/models"
"github.com/jordan-wright/email"
)
// Logger is the logger for the worker
@ -119,6 +119,10 @@ func processCampaign(c *models.Campaign) {
if err != nil {
Logger.Println(err)
}
err = c.AddEvent(models.Event{Email: t.Email, Message: models.EVENT_SENT})
if err != nil {
Logger.Println(err)
}
}
}
err = c.UpdateStatus(models.CAMPAIGN_EMAILS_SENT)