├── examples ├── ss1.png └── ss2.png ├── README.md └── burp2api.py /examples/ss1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrTurvey/Burp2API/HEAD/examples/ss1.png -------------------------------------------------------------------------------- /examples/ss2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrTurvey/Burp2API/HEAD/examples/ss2.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Burp2API - Turn Burp Suite Projects Into JSON 2 | ## What is this project? 3 | 4 | Burp2API converts your Burp Suite project into a JSON for usage with POSTMAN or SWAGGER editor. 5 | 6 | There are currently two purposes for the creation of this tool: 7 | - To provide clients with documentation for their own API (Anyone that does API testing knows the struggles) 8 | - To save a history of the endpoints you used in previous testing which can be used for future testing. 9 | 10 | This project is maintained by [TurvSec](https://twitter.com/TurvSec) 11 | 12 | ## Output Examples 13 | Here's what your JSON will look like in Swagger: 14 | 15 | ![Alt text](/examples/ss1.png "Swagger Output") 16 | 17 | Here's what your JSON will look like in Postman: 18 | 19 | ![Alt text](/examples/ss2.png "Postman Output") 20 | 21 | ## Installation 22 | Simply git clone and you're away: 23 | ``` 24 | git clone https://github.com/MrTurvey/Burp2API.git 25 | cd Burp2API 26 | python Burp2API 27 | ``` 28 | 29 | ## Usage 30 | With your BurpSuite project loaded: 31 | - Click on the target tab, if you're not already there 32 | - Right click the target you want to export 33 | - Click "Save selected items" 34 | - Ensure the "Base64-encode" checkbox is checked 35 | - Save as burp_output.xml, or whatever you want 36 | 37 | Now you have a Burp Suite output, you can use Burp2API: 38 | 39 | ``` 40 | python Burp2API burp_output.xml 41 | ``` 42 | The output will be 43 | 44 | - filename.json 45 | - filename.xml 46 | 47 | You can view the JSON file with Swagger (link below) or you can import it into Postman using its import button. 48 | 49 | https://editor.swagger.io 50 | 51 | ## Limitations 52 | 53 | The tool is fresh, there are probably a lot of things that need fixing. -------------------------------------------------------------------------------- /burp2api.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import xml.etree.ElementTree as ET 3 | import json 4 | import base64 5 | import re 6 | import os 7 | import json 8 | 9 | # set output directory 10 | outputDirectory = "output" 11 | 12 | def process_tree(tree): 13 | # Extract root from XML, define the HTTP method order and use a set to track unique URL, method and paths. 14 | root = tree.getroot() 15 | methods_order = ["GET", "POST", "DELETE", "PUT", "PATCH", "OPTIONS"] 16 | seen_methods = set() 17 | new_items = [] 18 | 19 | # Interate over the XML items, sorted by the defined HTTP method order. 20 | for item in sorted(root.findall("item"), key=lambda x: methods_order.index(x.findtext("method"))): 21 | # Extracts URL, method, and path text from the current item. 22 | url, method, uPath = item.findtext("url"), item.findtext("method"), item.findtext("path") 23 | # Creates a unique key based on URL, method, and path. 24 | item_key = (url, method, uPath) 25 | 26 | # Skips processing for OPTIONS methods, root path, or already seen item keys. 27 | if method == "OPTIONS" or uPath == "/" or item_key in seen_methods: 28 | continue 29 | 30 | # Marks the current item's key as seen. 31 | seen_methods.add(item_key) 32 | 33 | # Removes unrequired sub-elements from the XML to clean it up. 34 | for entity in ["host", "port", "protocol", "extension", "responselength", "response", "comment", "time"]: 35 | element = item.find(entity) 36 | if element is not None: 37 | item.remove(element) 38 | 39 | # Processes and cleans the "request" element's text, if present. 40 | request_element = item.find("request") 41 | if request_element is not None and request_element.text: 42 | decoded_request = base64.b64decode(request_element.text).decode("utf-8", errors="ignore") 43 | decoded_request = re.sub(r"Connection: close.*?\n\s*", "", decoded_request, flags=re.DOTALL) 44 | request_element.text = decoded_request 45 | 46 | # Splits the path from it's parameters and adds a new "param" element with the parameters, if they exist. 47 | path_element = item.find("path") 48 | if path_element is not None and '?' in path_element.text: 49 | path, params = path_element.text.split('?', 1) 50 | path_element.text = path 51 | params_element = ET.SubElement(item, "param") 52 | params_element.text = params 53 | elif method != "GET" and request_element is not None: 54 | params = request_element.text.split('\n')[-1] 55 | params_element = ET.SubElement(item, "param") 56 | params_element.text = params 57 | 58 | # Adds the cleaned and possibly modified item to the new items list. 59 | new_items.append(item) 60 | 61 | # Clears the root element 62 | root.clear() 63 | # Re-populates the root element with the cleaned and processed items. 64 | for item in new_items: 65 | root.append(item) 66 | 67 | # Returns the modified XML. 68 | return root 69 | 70 | # For checking if the input is a valid JSON string 71 | def is_json_param(param): 72 | try: 73 | res = json.loads(param) # Try to parse the parameter as JSON 74 | return isinstance(res, dict) 75 | except ValueError: 76 | return False 77 | 78 | 79 | def convert_to_openapi(cleanedTree): 80 | # Find all elements in the XML created in the process_tree function 81 | items = cleanedTree.findall("item") 82 | 83 | # If there are no "item" elements, return an empty dictionary. 84 | if not items: 85 | return {} # Return an empty dict or a base structure if no items found 86 | 87 | # Extract the "url" text from the first XML "item" and split it to get the domain part. 88 | first_url = items[0].findtext("url") 89 | splitURL = first_url.split('/')[2] 90 | 91 | # Determine the API name. If "www." or "api." is in the URL, use the next string for the name. 92 | api_name = splitURL.split('.')[1] if any(prefix in first_url for prefix in ["www.", "api."]) else splitURL.split('.')[0] 93 | # Construct the base URL using the domain. 94 | base_url = f"https://{splitURL}" 95 | 96 | # Initialize the OpenAPI specification dictionary with basic information and server URL. 97 | openapi_dict = { 98 | "openapi": "3.0.0", 99 | "info": { 100 | "title": f"Burp2API - {api_name}", 101 | "version": "1.0.0", 102 | "description": "This API has been built with [Burp2API](https://github.com/MrTurvey/Burp2API). " 103 | "This is NOT a replacement for proper documentation that should stem from the official developers." 104 | }, 105 | "servers": [{"url": base_url}], 106 | "paths": {} 107 | } 108 | 109 | # Iterate over each XML "item" to populate the "paths" in the OpenAPI spec. 110 | for item in items: 111 | # Extract relevant details from each "item". 112 | url, uPath = item.findtext("url"), item.findtext("path") 113 | method = item.findtext("method").lower() 114 | status = item.findtext("status") or "200" # Default to 200 if status is not specified 115 | HTTPparams = item.findtext("param") 116 | 117 | # Prepare the path and method in the OpenAPI dictionary, initializing if not existent. 118 | path_item = openapi_dict["paths"].setdefault(uPath, {}) 119 | method_item = path_item.setdefault(method, {"responses": {status: {"description": f"Response status {status}"}}}) 120 | 121 | # Check if there are HTTP parameters 122 | if HTTPparams: 123 | parameters, requestBody = [], {"content": {}} 124 | for param in set(HTTPparams.split('&')): 125 | param_name = param.split('=')[0] 126 | # If the parameter is a JSON object, process it. 127 | if is_json_param(param_name): 128 | properties_dict = {key: {"type": "string"} for key in json.loads(param_name).keys()} 129 | requestBody["content"]["application/json"] = { 130 | "schema": {"type": "object", "properties": properties_dict} 131 | } 132 | else: 133 | # Otherwise, treat it as a query parameter. 134 | parameters.append({ 135 | "name": param_name, 136 | "in": "query", 137 | "required": False, 138 | "schema": {"type": "string"} 139 | }) 140 | 141 | # If there's a requestBody defined, add it to the method item. 142 | if requestBody["content"]: 143 | method_item["requestBody"] = requestBody 144 | # If there are query parameters, add them to the method item 145 | if parameters: 146 | method_item["parameters"] = parameters 147 | 148 | # Return the constructed OpenAPI specification dictionary. 149 | return openapi_dict 150 | 151 | def write_to_file(data, filename): 152 | """ Write data to a file. """ 153 | with open(filename, "w") as f: 154 | f.write(data) 155 | print(f"Output saved to {filename}") 156 | 157 | if __name__ == "__main__": 158 | if len(sys.argv) != 2: 159 | print("Usage: python burp2api.py ") 160 | sys.exit(1) 161 | 162 | # take XML input, parse it to element tree, get root element , process it 163 | input_xml_file = sys.argv[1] 164 | tree = ET.parse(input_xml_file) 165 | treeProcessed = process_tree(tree) 166 | 167 | # create output directory if it doesn't exist 168 | if not os.path.exists(outputDirectory): 169 | # Create the directory 170 | os.makedirs(outputDirectory) 171 | 172 | # Extract just the XML file name 173 | file_name = os.path.basename(sys.argv[1]) 174 | 175 | # output to OpenAPI JSON 176 | openapi_json = json.dumps(convert_to_openapi(treeProcessed), indent=2) 177 | write_to_file(openapi_json, outputDirectory + "/" + file_name + ".json") 178 | 179 | # output modifed XML 180 | tree.write(os.path.join(outputDirectory, f"{file_name}_modified")) 181 | 182 | print(f"Modified XML saved to {file_name}_modified.xml") --------------------------------------------------------------------------------