Skip to content

Commit 88845d4

Browse files
committed
Add feature to generate password spraying template
1 parent 9e23850 commit 88845d4

File tree

4 files changed

+210
-27
lines changed

4 files changed

+210
-27
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ A Burp extension to generate async Python code from HTTP requests.
3737
This extension generates different flavors of scripts (*e.g.* with/without
3838
session, with/without main function).
3939

40-
The resulting codes have been tested with `Python 3.11.2` but should work with
40+
The resulting codes have been tested with `Python 3.11.2` but they should work with
4141
other versions as well.
4242

4343
## :sparkles: Features ##

copy-as-aiohttp-extension/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version = "0.1.0"
1+
version = "1.0.0"
22

33
plugins {
44
// Apply the java-library plugin for API and implementation separation.

copy-as-aiohttp-extension/src/main/java/burp/MyContextMenuItemsProvider.java

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@
1515

1616
public class MyContextMenuItemsProvider implements ContextMenuItemsProvider {
1717

18-
public MyContextMenuItemsProvider(MontoyaApi api) {
19-
}
18+
public MyContextMenuItemsProvider(MontoyaApi api) {}
2019

2120
@Override
2221
public List<Component> provideMenuItems(ContextMenuEvent event) {
@@ -62,7 +61,9 @@ public List<Component> provideMenuItems(ContextMenuEvent event) {
6261
copyRequestsItem.addActionListener(e -> copyRequests(messages, false));
6362
copyRequestsWithSessionItem.addActionListener(e -> copyRequests(messages, true));
6463
copyRequestsOnScriptItem.addActionListener(e -> copyRequestsOnScript(messages, false));
65-
copyRequestsWithSessionOnScriptItem.addActionListener(e -> copyRequestsOnScript(messages, true));
64+
copyRequestsWithSessionOnScriptItem
65+
.addActionListener(e -> copyRequestsOnScript(messages, true));
66+
passwdSprayTemplateItem.addActionListener(e -> generatePasswdSprayTemplate(messages));
6667

6768
return menuItemList;
6869
}
@@ -74,27 +75,38 @@ public List<Component> provideMenuItems(ContextMenuEvent event) {
7475
* Copy the selected requests to the clipboard as Python functions
7576
*
7677
* @param messages The selected requests
78+
* @param session Whether to include a session object
7779
* @return void
7880
*/
7981
private void copyRequests(List<HttpRequestResponse> messages, boolean session) {
80-
StringBuilder sb = new StringBuilder();
8182
ReqParser parser = new ReqParser(messages);
82-
83-
sb.append("# Generated by \"Copy as Python aiohttp\" Burp extension\n");
84-
sb.append(parser.asPythonScript(session, false, false));
85-
86-
Utility.copyToClipboard(sb);
83+
String output = parser.asPythonScript(session, false, false);
84+
Utility.copyToClipboard(output);
8785
}
8886

87+
/**
88+
* Generate a Python script with the selected requests
89+
*
90+
* @param messages The selected requests
91+
* @param session Whether to include a session object
92+
* @return void
93+
*/
8994
private void copyRequestsOnScript(List<HttpRequestResponse> messages, boolean session) {
90-
StringBuilder sb = new StringBuilder();
9195
ReqParser parser = new ReqParser(messages);
96+
String output = parser.asPythonScript(session, true, true);
97+
Utility.copyToClipboard(output);
98+
}
9299

93-
sb.append("#!/usr/bin/env python3\n");
94-
sb.append("# Generated by \"Copy as Python aiohttp\" Burp extension\n");
95-
sb.append(parser.asPythonScript(session, true, true));
96-
97-
Utility.copyToClipboard(sb);
100+
/**
101+
* Generate a password spraying template
102+
*
103+
* @param messages The selected requests
104+
* @return void
105+
*/
106+
private void generatePasswdSprayTemplate(List<HttpRequestResponse> messages) {
107+
ReqParser parser = new ReqParser(messages);
108+
String script = parser.asPasswordSprayingTemplate();
109+
Utility.copyToClipboard(script);
98110
}
99111

100112
}

copy-as-aiohttp-extension/src/main/java/burp/ReqParser.java

Lines changed: 181 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package burp;
22

33
import java.util.List;
4-
import java.util.Optional;
54
import burp.api.montoya.http.message.HttpHeader;
65
import burp.api.montoya.http.message.HttpRequestResponse;
76
import burp.api.montoya.http.message.requests.HttpRequest;
@@ -26,6 +25,7 @@ public String asPythonScript(boolean session, boolean imports, boolean main) {
2625

2726
if (imports) {
2827
if (main) {
28+
sb.append(buildPreamble());
2929
sb.append(buildImports("aiohttp", "asyncio"));
3030
} else {
3131
sb.append(buildImports("aiohttp"));
@@ -42,13 +42,15 @@ public String asPythonScript(boolean session, boolean imports, boolean main) {
4242
if (session) {
4343
sb.append(Utility.indent(1) + "async with aiohttp.ClientSession() as client:\n");
4444
for (int i = 0; i < this.messages.size(); i++) {
45-
sb.append(Utility.indent(2) + "(status, headers, cookies, length, body) = await req" + i
45+
sb.append(Utility.indent(2)
46+
+ "(status, headers, cookies, length, body) = await req" + i
4647
+ "_fetch(client)\n");
4748
sb.append(Utility.indent(2) + "# TODO: add your logic here\n");
4849
}
4950
} else {
5051
for (int i = 0; i < messages.size(); i++) {
51-
sb.append(Utility.indent(1) + "(status, headers, cookies, length, body) = await req" + i
52+
sb.append(Utility.indent(1)
53+
+ "(status, headers, cookies, length, body) = await req" + i
5254
+ "_fetch()\n");
5355
sb.append(Utility.indent(1) + "# TODO: add your logic here\n");
5456
}
@@ -62,6 +64,30 @@ public String asPythonScript(boolean session, boolean imports, boolean main) {
6264
return sb.toString();
6365
}
6466

67+
/**
68+
* Convert the requests to a Python script for password spraying
69+
*
70+
* @return The Python script as a string
71+
*/
72+
public String asPasswordSprayingTemplate() {
73+
StringBuilder sb = new StringBuilder();
74+
sb.append(buildPreamble());
75+
sb.append(buildImports("aiohttp", "aiofiles", "aiocsv", "argparse", "asyncio", "logging"));
76+
sb.append("\n\n");
77+
sb.append(buildGlobals());
78+
sb.append("\n\n");
79+
sb.append(parseRequests(true, 0));
80+
sb.append("\n\n");
81+
sb.append(buildSprayFunction(this.messages.size()));
82+
sb.append("\n\n");
83+
sb.append(buildMainFunction());
84+
sb.append("\n\n");
85+
sb.append("if __name__ == '__main__':\n");
86+
sb.append(Utility.indent(1) + "loop = asyncio.get_event_loop()\n");
87+
sb.append(Utility.indent(1) + "loop.run_until_complete(main())\n");
88+
return sb.toString();
89+
}
90+
6591
/**
6692
* Build the imports for the Python script
6793
*
@@ -88,7 +114,7 @@ private String parseRequests(boolean session, int baseIndent) {
88114

89115
for (HttpRequestResponse message : this.messages) {
90116
HttpRequest request = message.request();
91-
sb.append(requestToFunction(request, baseIndent, session, Optional.of(reqIdx)));
117+
sb.append(requestToFunction(request, baseIndent, session, reqIdx));
92118
// Add 2 new lines between requests
93119
if (reqIdx < messages.size() - 1) {
94120
sb.append("\n\n");
@@ -108,14 +134,16 @@ private String parseRequests(boolean session, int baseIndent) {
108134
* @return The Python function
109135
*/
110136
private String requestToFunction(HttpRequest request, int baseIndent, boolean session,
111-
Optional<Integer> requestIndex) {
137+
int requestIndex) {
112138
StringBuilder sb = new StringBuilder();
113-
String prefix = requestIndex.isPresent() ? "req" + requestIndex.get() + "_" : "req_";
139+
String prefix = "req" + requestIndex + "_";
114140

115141
if (session) {
116-
sb.append(Utility.indent(baseIndent) + "async def " + prefix + "fetch(client):\n");
142+
sb.append(Utility.indent(baseIndent) + "async def " + prefix
143+
+ "fetch(client, ssl=True, proxy=None):\n");
117144
} else {
118-
sb.append(Utility.indent(baseIndent) + "async def " + prefix + "fetch():\n");
145+
sb.append(Utility.indent(baseIndent) + "async def " + prefix
146+
+ "fetch(ssl=True, proxy=None):\n");
119147
}
120148
sb.append(Utility.indent(baseIndent + 1) + "url = '" + request.url() + "'\n");
121149
sb.append(Utility.indent(baseIndent + 1) + "method = '" + request.method() + "'\n");
@@ -133,10 +161,10 @@ private String requestToFunction(HttpRequest request, int baseIndent, boolean se
133161
// Make the request
134162
if (session) {
135163
sb.append(Utility.indent(baseIndent + 1)
136-
+ "async with client.request(method, url, headers=headers, cookies=cookies, data=data, allow_redirects=False) as response:\n");
164+
+ "async with client.request(method, url, headers=headers, cookies=cookies, data=data, allow_redirects=False, ssl=ssl, proxy=proxy) as response:\n");
137165
} else {
138166
sb.append(Utility.indent(baseIndent + 1)
139-
+ "async with aiohttp.request(method, url, headers=headers, cookies=cookies, data=data, allow_redirects=False) as response:\n");
167+
+ "async with aiohttp.request(method, url, headers=headers, cookies=cookies, data=data, allow_redirects=False, ssl=ssl, proxy=proxy) as response:\n");
140168
}
141169
sb.append(Utility.indent(baseIndent + 2)
142170
+ "return (response.status, response.headers, response.cookies, response.headers.get('content-length', 0), await response.text())\n");
@@ -204,4 +232,147 @@ private String parseHeaders(HttpRequest request, int baseIndent) {
204232
}
205233
return sb.toString();
206234
}
235+
236+
/**
237+
* Build the preamble for the Python script
238+
*
239+
* @return The preamble as a string
240+
*/
241+
private String buildPreamble() {
242+
StringBuilder sb = new StringBuilder();
243+
sb.append("#!/usr/bin/env python3\n");
244+
sb.append("# -*- coding: utf-8 -*-\n");
245+
sb.append("\"\"\"\n");
246+
sb.append("Template generated by \"Copy as Python aiohttp\" Burp extension.\n");
247+
sb.append("See: https://github.com/y0k4i-1337/copy-as-python-aiohttp\n");
248+
sb.append("\"\"\"\n");
249+
return sb.toString();
250+
}
251+
252+
/**
253+
* Build the globals for the Python script
254+
*
255+
* @return The globals as a string
256+
*/
257+
private String buildGlobals() {
258+
StringBuilder sb = new StringBuilder();
259+
sb.append("JAR_UNSAFE = True\n");
260+
sb.append("CLIENT_TOTAL_TIMEOUT = 60\n");
261+
sb.append("CONN_POOL_SIZE = 10\n");
262+
sb.append("CONN_TTL_DNS_CACHE = 300\n");
263+
sb.append("SSL_VERIFY = False\n");
264+
sb.append("LOG_LEVEL = logging.INFO\n\n");
265+
sb.append(
266+
"logging.basicConfig(level=LOG_LEVEL, format=\"%(asctime)s - %(levelname)s - %(message)s\")\n");
267+
sb.append("timeout = aiohttp.ClientTimeout(total=CLIENT_TOTAL_TIMEOUT)\n");
268+
sb.append("conn = aiohttp.TCPConnector(\n");
269+
sb.append(Utility.indent(1)
270+
+ "limit_per_host=CONN_POOL_SIZE, ttl_dns_cache=CONN_TTL_DNS_CACHE\n");
271+
sb.append(")\n");
272+
return sb.toString();
273+
}
274+
275+
/**
276+
* Build the spray function for the Python script
277+
*
278+
* @param messageCount The number of messages
279+
* @return The spray function as a string
280+
*/
281+
private String buildSprayFunction(int messageCount) {
282+
StringBuilder sb = new StringBuilder();
283+
sb.append("async def spray(username, password, proxy=None):\n");
284+
sb.append(Utility.indent(1) + "jar = aiohttp.CookieJar(unsafe=JAR_UNSAFE)\n");
285+
sb.append(Utility.indent(1) + "try:\n");
286+
sb.append(Utility.indent(2) + "async with aiohttp.ClientSession(\n");
287+
sb.append(Utility.indent(3)
288+
+ "timeout=timeout, cookie_jar=jar, connector=conn, connector_owner=False\n");
289+
sb.append(Utility.indent(2) + ") as client:\n");
290+
for (int i = 0; i < messageCount; i++) {
291+
sb.append(Utility.indent(3) + "(status, headers, cookies, length, body) = await req" + i
292+
+ "_fetch(client, ssl=SSL_VERIFY, proxy=proxy)\n");
293+
sb.append(Utility.indent(3) + "# TODO: add your logic here\n");
294+
}
295+
sb.append(Utility.indent(3) + "result = {\n");
296+
sb.append(Utility.indent(4) + "'username': username,\n");
297+
sb.append(Utility.indent(4) + "'password': password,\n");
298+
sb.append(Utility.indent(4) + "'status': status,\n");
299+
sb.append(Utility.indent(4) + "'content-length': length,\n");
300+
sb.append(Utility.indent(3) + "}\n");
301+
sb.append(Utility.indent(1) + "except Exception as e:\n");
302+
sb.append(Utility.indent(2) + "result = {\n");
303+
sb.append(Utility.indent(3) + "'username': username,\n");
304+
sb.append(Utility.indent(3) + "'password': password,\n");
305+
sb.append(Utility.indent(3) + "'error': str(e),\n");
306+
sb.append(Utility.indent(2) + "}\n");
307+
sb.append(Utility.indent(1) + "return result\n");
308+
return sb.toString();
309+
}
310+
311+
/**
312+
* Build the main function for the Python script
313+
*
314+
* @return The main function as a string
315+
*/
316+
private String buildMainFunction() {
317+
StringBuilder sb = new StringBuilder();
318+
sb.append("async def main():\n");
319+
sb.append(Utility.indent(1)
320+
+ "parser = argparse.ArgumentParser(description='Password spraying script')\n");
321+
sb.append(Utility.indent(1)
322+
+ "parser.add_argument('-x', '--proxy', help='Proxy URL', default=None)\n");
323+
sb.append(Utility.indent(1) + "parser.add_argument(\n");
324+
sb.append(Utility.indent(2)
325+
+ "'-e', '--errors', help='File to save errors', default='errors.txt'\n");
326+
sb.append(Utility.indent(1) + ")\n");
327+
sb.append(Utility.indent(1) + "parser.add_argument(\n");
328+
sb.append(Utility.indent(2)
329+
+ "'-o', '--output', help='File to save successful requests', default='results.csv'\n");
330+
sb.append(Utility.indent(1) + ")\n");
331+
sb.append(Utility.indent(1)
332+
+ "parser.add_argument('usernames', help='File containing usernames')\n");
333+
sb.append(Utility.indent(1)
334+
+ "parser.add_argument('passwords', help='File containing passwords')\n");
335+
sb.append(Utility.indent(1) + "args = parser.parse_args()\n");
336+
sb.append(Utility.indent(1) + "usernames = []\n");
337+
sb.append(Utility.indent(1) + "passwords = []\n");
338+
sb.append(Utility.indent(1) + "requested = []\n");
339+
sb.append(Utility.indent(1) + "errors = []\n\n");
340+
sb.append(Utility.indent(1) + "async with aiofiles.open(args.usernames, mode='r') as f:\n");
341+
sb.append(Utility.indent(2) + "async for line in f:\n");
342+
sb.append(Utility.indent(3) + "usernames.append(line.strip())\n\n");
343+
sb.append(Utility.indent(1) + "async with aiofiles.open(args.passwords, mode='r') as f:\n");
344+
sb.append(Utility.indent(2) + "async for line in f:\n");
345+
sb.append(Utility.indent(3) + "passwords.append(line.strip())\n\n");
346+
sb.append(Utility.indent(1) + "tasks = []\n");
347+
sb.append(Utility.indent(1) + "for password in passwords:\n");
348+
sb.append(Utility.indent(2) + "for username in usernames:\n");
349+
sb.append(Utility.indent(3) + "tasks.append(spray(username, password, args.proxy))\n");
350+
sb.append(Utility.indent(1) + "logging.info('Spraying %d combinations', len(tasks))\n\n");
351+
sb.append(Utility.indent(1) + "results = await asyncio.gather(*tasks)\n");
352+
sb.append(Utility.indent(1) + "for result in results:\n");
353+
sb.append(Utility.indent(2) + "if 'error' in result.keys():\n");
354+
sb.append(Utility.indent(3) + "errors.append(result)\n");
355+
sb.append(Utility.indent(2) + "else:\n");
356+
sb.append(Utility.indent(3) + "requested.append(result)\n\n");
357+
sb.append(Utility.indent(1) + "if len(requested) > 0:\n");
358+
sb.append(Utility.indent(2) + "async with aiofiles.open(args.output, 'w') as f:\n");
359+
sb.append(Utility.indent(3) + "fieldnames = requested[0].keys()\n");
360+
sb.append(Utility.indent(3)
361+
+ "writer = aiocsv.AsyncDictWriter(f, fieldnames=fieldnames, dialect='excel')\n");
362+
sb.append(Utility.indent(3) + "await writer.writeheader()\n");
363+
sb.append(Utility.indent(3) + "await writer.writerows(requested)\n");
364+
sb.append(Utility.indent(2)
365+
+ "logging.info('Saved %d successful requests to %s', len(requested), args.output)\n");
366+
sb.append(Utility.indent(1) + "if len(errors) > 0:\n");
367+
sb.append(Utility.indent(2) + "async with aiofiles.open(args.errors, 'w') as f:\n");
368+
sb.append(Utility.indent(3) + "for error in errors:\n");
369+
sb.append(Utility.indent(4)
370+
+ "await f.write(f\"{error['username']}:{error['password']} - {error['error']}\")\n");
371+
sb.append(Utility.indent(2)
372+
+ "logging.info('Saved %d errors to %s', len(errors), args.errors)\n\n");
373+
sb.append(Utility.indent(1) + "logging.info('Completed tasks: %d', len(requested))\n");
374+
sb.append(Utility.indent(1) + "logging.info('Errors: %d', len(errors))\n");
375+
376+
return sb.toString();
377+
}
207378
}

0 commit comments

Comments
 (0)