11package burp ;
22
33import java .util .List ;
4- import java .util .Optional ;
54import burp .api .montoya .http .message .HttpHeader ;
65import burp .api .montoya .http .message .HttpRequestResponse ;
76import 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