Burp Suite Collaborator Recovery
Background
I recently worked on a phishing engagement involving a malicious email attachment. The final payload was an executable file that exfiltrated some data back to a server under my control. I’d normally use my attack server for this, but my attack server was already in use, so I opted to use Burp Suite’s Collaborator to catch my exfiltration payloads. My employer has their own collaborator instance, so we don’t have to rely on Portswigger’s and we can keep our clients’ data that much more secure.
After building multiple payloads and meticulously testing everything, I was finally ready to launch the campaign. The first day, everything was working great. The collaborator instance was catching a bunch of hits from the client’s mail filtering system, and a few hits from actual target employees executing the payload. I went to bed that night feeling good about the experience.
Unfortunately when I woke up the next day, I checked the status of the phishing campaign and found that Burp Suite had crashed. I had lost all of my collaborator interactions! This was a big problem because collecting this data was the whole point of the phishing exercise.
Problems with Burp Suite Collaborator
In my experience, Burp Suite collaborator has some big problems. First, I haven’t found any built-in way to export the interaction data. My instance caught about 1500 interactions before it crashed. There was no way for me to export that data for automated processing. Instead I had to manually go through each entry and check which ones were from legitimate users and which ones were from the client’s email firewall detonating the payload. It was a frustrating, but surmountable problem.
Second, once your collaborator client window closes, that’s it. You lose all of that data. There’s no built-in way to get it back. What’s more, you can’t tell Burp to continue collecting data for that specific collaborator hostname either. So even if my client’s employees were continuing to execute the payload, I’d have to way to talk to the collaborator server to find out about it.
Example
As an example, I’ll create a Burp project called “Collaborator Test Project”.
Now if I open up the collaborator client tool, I get a fresh instance with nothing in it.
If I leave all of the default settings and click “Copy to clipboard”, I’ll get a collaborator instance hostname to use. In my case, I got:
9mwybk1vfyd9ix69ct06dlu5cwim6b.oastify.com
Now I can force an interaction with this host to test it out and make sure Collaborator is working. I’ll use curl to do this.
$ curl https://9mwybk1vfyd9ix69ct06dlu5cwim6b.oastify.com/test?a=1 1 ⨯
<html><body>zzei36vq3xgg4ycg5579cazjigz</body></html>
After hitting the web server, and either waiting for Collaborator to poll or manually clicking “Poll now”, I see the interaction in the interactions table, including the GET parameters. This is important because for my phishing campaign I was exfiltrating data using GET parameters.
Now what happens if Burp Suite crashes (as it seems to enjoy doing somewhat regularly)? I simulate this by killing the process.
$ ps aux | grep burp
kali 203512 15.6 9.4 10171100 1175200 ? Sl 16:05 1:15 /home/kali/BurpSuitePro/jre/bin/java -splash:/home/kali/BurpSuitePro/.install4j/s_sl5vaz.png --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/javax.crypto=ALL-UNNAMED --add-opens java.desktop/javax.swing=ALL-UNNAMED --add-opens java.desktop/java.awt=ALL-UNNAMED --add-opens java.desktop/java.awt.color=ALL-UNNAMED --add-opens jdk.crypto.cryptoki/sun.security.pkcs11=ALL-UNNAMED -XX:MaxRAMPercentage=50 -classpath /home/kali/BurpSuitePro/.install4j/i4jruntime.jar:/home/kali/BurpSuitePro/.install4j/launcherccf7dac9.jar:/home/kali/BurpSuitePro/burpsuite_pro.jar install4j.burp.StartBurp
$ kill -9 203512
Burp Suite is now closed and my Collaborator client window is closed with it. How do I get those interactions back? How can I continue polling for new interactions after such a failure? If I were to launch Burp Suite again and try to open the Collaborator client, I’d just get a new empty instance. All of my results would be gone.
With this in mind, I had two problems to overcome:
- Was there any way to recovery my lost collaborator data?
- Was there a way to continue polling the collaborator server for more interactions in case some target users executed the payload the next day?
Burp Temporary Files to the Rescue
When Burp Suite is running, it stores data in temporary files which are located in /tmp by default. If Burp Suite crashes, those tmp files stick around at least until the operating system cleans them out. For my example project, I can look in /tmp and find the temporary files are still there even though the Burp process is dead.
$ ls -l /tmp | grep burp
drwx------ 2 kali kali 4096 Jul 22 16:05 burp15072491843655237708.tmp
drwx------ 2 kali kali 4096 Jul 22 16:06 burp16165429084369965144.tmp
One of those directories should contain a list of binary files with numbers for names like this:
$ ls
1 1 2 3 4 5 6
I copied those files to a safe location so they wouldn’t be lost.
$ cp -R /tmp/burp16165429084369965144.tmp ~/Desktop/
As it turns out, the Collaborator interactions are stored in these files! At least some of the interaction data is stored there. And some is better than nothing. I found I was able to recover the data I needed using grep to search for my specific Collaborator hostname.
$ grep -a 9mwybk1vfyd9ix69ct06dlu5cwim6b * -B1
0-f����Ryt���� ▒aAbBeEgGiIoOsStTzZ▒448833661100557722 A,$abcdefghijklmnopqrstuvwxyz0123456789zrGET /test?a=1 HTTP/1.1
0:Host: 9mwybk1vfyd9ix69ct06dlu5cwim6b.oastify.com
--
0-<html><body>zzei36vq3xgg4ycg5579cazjigz</body></html>zrGET /test?b=2 HTTP/1.1
0:Host: 9mwybk1vfyd9ix69ct06dlu5cwim6b.oastify.com
If you look through the output you can see the important URL query data:
GET /test?a=1
GET /test?b=2
So it was possible to recover the GET data using Burp’s temporary files. Interestingly, this data did not appear to be stored in the Burp project file. At least, if it is in there then I couldn’t find it using this same grep method.
Polling for New Interactions
Once I had recovered my lost interaction data I breathed a sigh of relief. However, I wasn’t fully out of the woods yet. It was possible that there had been new interactions since Burp Suite crashed, and I had an entire work day left for new interactions to come in. With Burp dead in the water there was no way to force it to continue polling for new interactions on the existing Collaborator hostname.
I actually got really lucky here. When I originally was building this phishing campaign I was worried about this exact scenario happening because I’ve had Burp Suite crash on me plenty of times. Lately I had been having really good luck though, so I thought I’d be safe for 48 hours. How wrong I was. I was still concerned, though. So I read up about how Collaborator works from Burp Suite’s website. The “Security of Collaborator data” section was particularly interesting. It explains that each Collaborator Client instance generates a securely random secret. It then makes an HTTPS request to the Collaborator polling server, providing the secret to the server. The server then returns the relevant data to the client and deletes the data from the server’s memory. Supposedly client interactions are not stored on the server in any log files or anywhere on disk.
Before I launched my campaign, I was curious about how this worked so I configured Burp Suite to poll over HTTP temporarily so I could see the interaction with Wireshark.
I then triggered the client polling and saw the plaintext HTTP traffic in Wireshark.
I used the “Follow HTTP Stream” function of Wireshark to view the request in readable text.
You’ll notice the very first line is the GET request including a “biid” GET parameter. This appeared to be the securely random secret the PortSwigger blog referred to. I performed another manual interaction with the collaborator host URL using curl.
$ curl https://9mwybk1vfyd9ix69ct06dlu5cwim6b.oastify.com/test?new=true
I then used curl to make a GET request to the polling server using the secret recovered with Wireshark.
$ curl https://polling.oastify.com/burpresults?biid=FEXEGNoFL53ytiJ3I6cHTpzirPuoQrj%2fWnxkRgGhLYQ%3d
{"responses":[{"protocol":"https","opCode":"1","interactionString":"9mwybk1vfyd9ix69ct06dlu5cwim6b","clientPart":"0y","data":{"request":"R0VUIC90ZXN0P25ldz10cnVlIEhUVFAvMS4xDQpIb3N0OiA5bXd5YmsxdmZ5ZDlpeDY5Y3QwNmRsdTVjd2ltNmIub2FzdGlmeS5jb20NClVzZXItQWdlbnQ6IGN1cmwvNy43OS4xDQpBY2NlcHQ6ICovKg0KDQo=", "response":"SFRUUC8xLjEgMjAwIE9LDQpTZXJ2ZXI6IEJ1cnAgQ29sbGFib3JhdG9yIGh0dHBzOi8vYnVycGNvbGxhYm9yYXRvci5uZXQvDQpYLUNvbGxhYm9yYXRvci1WZXJzaW9uOiA0DQpDb250ZW50LVR5cGU6IHRleHQvaHRtbA0KQ29udGVudC1MZW5ndGg6IDUzDQoNCjxodG1sPjxib2R5Pnp6ZWkzNnZxM3hnZzR5Y2c1NTc5Y2F6amlnejwvYm9keT48L2h0bWw+"},"time":"1658521197595","client":"x.x.x.x"}]}
It worked as expected. I was able to manually connect to the polling server and grab the data myself, ignoring the Burp collaborator client. It’s worth noting again that once the polling server returns it’s queued interactions, those interactions are deleted from the server’s memory. So if you make another request it will not contain previously collected interactions. For example:
$ curl https://polling.oastify.com/burpresults?biid=FEXEGNoFL53ytiJ3I6cHTpzirPuoQrj%2fWnxkRgGhLYQ%3d
{}
Luckily for me, I had performed this experiment before the phishing campaign started and I continued using the same Collaborator host the entire time. I still had the curl command in my bash history, so I was able to recover the secret biid value from there and continue polling for new interactions manually. With the URL in hand, I wrote a simple bash script to poll the server every 60 seconds and append the results to a json log file.
while true
do
curl 'https://polling.oastify.com/burpresults?biid=FEXEGNoFL53ytiJ3I6cHTpzirPuoQrj%2fWnxkRgGhLYQ%3d' --output >(cat >> /home/kali/Desktop/polling_log.json)
sleep 60s
done
When the script is executed, it makes the GET request every 60 seconds and appends them to a file called “polling_log.json”.
$ bash poll.sh
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2 100 2 0 0 2 0 0:00:01 --:--:-- 0:00:01 2
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 641 100 641 0 0 790 0 --:--:-- --:--:-- --:--:-- 789
I made another manual interaction with the Collaborator host using curl while my script was running to test it out.
$ curl https://9mwybk1vfyd9ix69ct06dlu5cwim6b.oastify.com/test?scriptpoll=true
<html><body>zzei36vq3xgg4ycg5579cazjigz</body></html>
I waited 60 seconds and then checked the log file:
$ cat polling_log.json |jq
{}
{
"responses": [
{
"protocol": "https",
"opCode": "1",
"interactionString": "9mwybk1vfyd9ix69ct06dlu5cwim6b",
"clientPart": "0y",
"data": {
"request": "R0VUIC90ZXN0P3NjcmlwdHBvbGw9dHJ1ZSBIVFRQLzEuMQ0KSG9zdDogOW13eWJrMXZmeWQ5aXg2OWN0MDZkbHU1Y3dpbTZiLm9hc3RpZnkuY29tDQpVc2VyLUFnZW50OiBjdXJsLzcuNzkuMQ0KQWNjZXB0OiAqLyoNCg0K",
"response": "SFRUUC8xLjEgMjAwIE9LDQpTZXJ2ZXI6IEJ1cnAgQ29sbGFib3JhdG9yIGh0dHBzOi8vYnVycGNvbGxhYm9yYXRvci5uZXQvDQpYLUNvbGxhYm9yYXRvci1WZXJzaW9uOiA0DQpDb250ZW50LVR5cGU6IHRleHQvaHRtbA0KQ29udGVudC1MZW5ndGg6IDUzDQoNCjxodG1sPjxib2R5Pnp6ZWkzNnZxM3hnZzR5Y2c1NTc5Y2F6amlnejwvYm9keT48L2h0bWw+"
},
"time": "1658521346518",
"client": "x.x.x.x"
}
]
}
Success! It worked. I now had a bash script that could continue polling for new interactions even though Burp Suite had crashed and had no built-in way to continue polling from a previous session.
Future
I actually am considering trying to build a burp extension that can save this information for me so I can more easily resume after an unexpected interruption. The extension could also have a built-in export function so I could process interactions with scripting later if desired.
I’ve started looking into this and it seems that the Burp extender API includes some methods to interact with the collaborator. However, it hides all of the interesting bits that do the secret generation.
It seems like I can start with the IBurpCollaboratorClientContext interface, which represents a single instance of the Collaborator client. It looks like I could then use getCollaboratorServerLocation() to get the hostname of the generated Collaborator server. Unfortunately, there’s no built-in method to obtain the biid secret that’s used to actually poll for interactions. Instead, there are two methods called fetchAllCollaboratorInteractions() and fetchCollaboratorInteractionsFor() that abstract that information away and allow you to force a polling event. This is fine and dandy when Burp is still running, but what about if it crashes?
I tried running jd-gui to decompile the Burp Suite Pro jar file but much of it is obfuscated or represented as Java bytecode.
class mh5 implements IBurpCollaboratorClientContext {
private final kpb s;
private final x2c H;
private final wiy j;
private static final String a;
public static IBurpCollaboratorClientContext S(kpb paramkpb, x2c paramx2c, wiy paramwiy) {
return new ztp(new mh5(paramkpb, paramx2c, paramwiy));
}
private mh5(kpb paramkpb, x2c paramx2c, wiy paramwiy) {
this.s = paramkpb;
this.H = paramx2c;
this.j = paramwiy;
}
public String generatePayload(boolean paramBoolean) {
I();
try {
return this.H.c(1L, paramBoolean);
} catch (Exception exception) {
le.W(exception, ev.IGNORED);
return null;
}
}
...
It would take a lot more effort than I expectd to figure out the secret hashing function. I guess it shouldn’t surprise me that this information is protected. Burp’s Collaborator tool is a paid feature and based on the description on PortSwigger’s website, it seems the only thing securing it from abuse is that the hashing function is secret.
I’m wondering if maybe it would be enough to create an IBurpCollaboratorClientContext and then serialize it to a file? Then perhaps it could be deserialized and the entire context would be saved in the event taht Burp crashes. I’ll have to play around with that and see if it works.
Lessons Learned
If I learned anything from this ordeal, it was that I really can’t rely on Burp Suite for long-term scenarios. It just has a tendency to crash too often. I also know that if I need long-term access to a Collaborator instance, I should be proactive and grab the biid secret ahead of time, just in case.