├── .gitignore ├── BappDescription.html ├── BappManifest.bmf ├── IPRotate.py ├── README.md ├── example.png ├── requirements.txt ├── setup.png └── ui.png /.gitignore: -------------------------------------------------------------------------------- 1 | BappModules/ 2 | -------------------------------------------------------------------------------- /BappDescription.html: -------------------------------------------------------------------------------- 1 |

2 | This extension allows you to easily spin up API Gateways across multiple regions. All the Burp Suite traffic for the targeted host is then routed through the API Gateway endpoints which causes the IP to be different on each request. (There is a chance for recycling of IPs but this is pretty low and the more regions you use the less of a chance).

3 | 4 |

5 | This is useful to bypass different kinds of IP blocking like bruteforce protection that blocks based on IP, API rate limiting based on IP or WAF blocking based on IP etc. 6 |

7 | 8 |

For more information see Bypassing IP Based Blocking Using AWS

9 | -------------------------------------------------------------------------------- /BappManifest.bmf: -------------------------------------------------------------------------------- 1 | Uuid: 2eb2b1cb1cf34cc79cda36f0f9019874 2 | ExtensionType: 2 3 | Name: IP Rotate 4 | RepoName: ip-rotate 5 | ScreenVersion: 2.0a 6 | SerialVersion: 4 7 | MinPlatformVersion: 2 8 | ProOnly: False 9 | Author: David Yesland 10 | ShortDescription: Uses AWS API Gateway to change your IP on every request. 11 | EntryPoint: IPRotate.py 12 | BuildCommand: 13 | SupportedProducts: Pro, Community 14 | -------------------------------------------------------------------------------- /IPRotate.py: -------------------------------------------------------------------------------- 1 | #AUTHOR: Dave Yesland @daveysec, Rhino Security Labs @rhinosecurity 2 | #Burp Suite extension which uses AWS API Gateway to change your IP on every request to bypass IP blocking. 3 | #More Info: https://rhinosecuritylabs.com/aws/bypassing-ip-based-blocking-aws/ 4 | 5 | from javax.swing import JPanel, JTextField, JButton, JLabel, BoxLayout, JPasswordField, JCheckBox, JRadioButton, ButtonGroup 6 | from burp import IBurpExtender, IExtensionStateListener, ITab, IHttpListener 7 | from java.awt import GridLayout 8 | import boto3 9 | import re 10 | 11 | EXT_NAME = 'IP Rotate' 12 | ENABLED = '

Enabled

' 13 | DISABLED = '

Disabled

' 14 | STAGE_NAME = 'burpendpoint' 15 | API_NAME = 'BurpAPI' 16 | AVAIL_REGIONS = [ 17 | "us-east-1","us-west-1","us-east-2", 18 | "us-west-2","eu-central-1","eu-west-1", 19 | "eu-west-2","eu-west-3","sa-east-1","eu-north-1" 20 | ] 21 | 22 | class BurpExtender(IBurpExtender, IExtensionStateListener, ITab, IHttpListener): 23 | def __init__(self): 24 | self.allEndpoints = [] 25 | self.currentEndpoint = 0 26 | self.aws_access_key_id = '' 27 | self.aws_secret_accesskey = '' 28 | self.enabled_regions = {} 29 | 30 | 31 | def registerExtenderCallbacks(self, callbacks): 32 | self.callbacks = callbacks 33 | self.helpers = callbacks.helpers 34 | self.isEnabled = False 35 | 36 | callbacks.registerHttpListener(self) 37 | callbacks.registerExtensionStateListener(self) 38 | callbacks.setExtensionName(EXT_NAME) 39 | callbacks.addSuiteTab(self) 40 | 41 | 42 | def getTargetProtocol(self): 43 | if self.https_button.isSelected() == True: 44 | return 'https' 45 | else: 46 | return 'http' 47 | 48 | def getRegions(self): 49 | self.enabled_regions = {} 50 | for region in AVAIL_REGIONS: 51 | cur_region = region.replace('-','_') 52 | cur_region = cur_region+'_status' 53 | region_status = getattr(self,cur_region) 54 | if region_status.isSelected(): 55 | #dict to contain the running regions and API gateway IDs 56 | self.enabled_regions.update({region:''}) 57 | return 58 | 59 | 60 | #AWS functions 61 | 62 | #Uses boto3 to test the AWS keys and make sure they are valid NOT IMPLEMENTED 63 | def testKeys(self): 64 | return 65 | 66 | #Uses boto3 to spin up an API Gateway 67 | def startAPIGateway(self): 68 | self.getRegions() 69 | for region in self.enabled_regions.keys(): 70 | self.awsclient = boto3.client('apigateway', 71 | aws_access_key_id=self.access_key.text, 72 | aws_secret_access_key=self.secret_key.text, 73 | region_name=region 74 | ) 75 | 76 | self.create_api_response = self.awsclient.create_rest_api( 77 | name=API_NAME, 78 | endpointConfiguration={ 79 | 'types': [ 80 | 'REGIONAL', 81 | ] 82 | } 83 | ) 84 | 85 | get_resource_response = self.awsclient.get_resources( 86 | restApiId=self.create_api_response['id'] 87 | ) 88 | 89 | self.restAPIId = self.create_api_response['id'] 90 | self.enabled_regions[region] = self.restAPIId 91 | 92 | create_resource_response = self.awsclient.create_resource( 93 | restApiId=self.create_api_response['id'], 94 | parentId=get_resource_response['items'][0]['id'], 95 | pathPart='{proxy+}' 96 | ) 97 | 98 | self.awsclient.put_method( 99 | restApiId=self.create_api_response['id'], 100 | resourceId=get_resource_response['items'][0]['id'], 101 | httpMethod='ANY', 102 | authorizationType='NONE', 103 | requestParameters={ 104 | 'method.request.path.proxy':True, 105 | 'method.request.header.X-My-X-Forwarded-For':True 106 | } 107 | ) 108 | 109 | self.awsclient.put_integration( 110 | restApiId=self.create_api_response['id'], 111 | resourceId=get_resource_response['items'][0]['id'], 112 | type='HTTP_PROXY', 113 | httpMethod='ANY', 114 | integrationHttpMethod='ANY', 115 | uri=self.getTargetProtocol()+'://'+self.target_host.text + '/', 116 | connectionType='INTERNET', 117 | requestParameters={ 118 | 'integration.request.path.proxy':'method.request.path.proxy', 119 | 'integration.request.header.X-Forwarded-For': 'method.request.header.X-My-X-Forwarded-For' 120 | } 121 | ) 122 | 123 | self.awsclient.put_method( 124 | restApiId=self.create_api_response['id'], 125 | resourceId=create_resource_response['id'], 126 | httpMethod='ANY', 127 | authorizationType='NONE', 128 | requestParameters={ 129 | 'method.request.path.proxy':True, 130 | 'method.request.header.X-My-X-Forwarded-For':True 131 | } 132 | ) 133 | 134 | self.awsclient.put_integration( 135 | restApiId=self.create_api_response['id'], 136 | resourceId=create_resource_response['id'], 137 | type= 'HTTP_PROXY', 138 | httpMethod= 'ANY', 139 | integrationHttpMethod='ANY', 140 | uri= self.getTargetProtocol()+'://'+self.target_host.text+'/{proxy}', 141 | connectionType= 'INTERNET', 142 | requestParameters={ 143 | 'integration.request.path.proxy':'method.request.path.proxy', 144 | 'integration.request.header.X-Forwarded-For': 'method.request.header.X-My-X-Forwarded-For' 145 | } 146 | ) 147 | 148 | self.deploy_response = self.awsclient.create_deployment( 149 | restApiId=self.restAPIId, 150 | stageName=STAGE_NAME 151 | 152 | ) 153 | 154 | self.allEndpoints.append(self.restAPIId+'.execute-api.'+region+'.amazonaws.com') 155 | 156 | self.usage_response = self.awsclient.create_usage_plan( 157 | name='burpusage', 158 | description=self.restAPIId, 159 | apiStages=[ 160 | { 161 | 'apiId': self.restAPIId, 162 | 'stage': STAGE_NAME 163 | } 164 | ] 165 | ) 166 | 167 | #Print out some info to burp console 168 | print 'Following regions and API IDs started:' 169 | print self.enabled_regions 170 | print 'List of endpoints being used:' 171 | print self.allEndpoints 172 | return 173 | 174 | #Uses boto3 to delete the API Gateway 175 | def deleteAPIGateway(self): 176 | if self.enabled_regions: 177 | for region in self.enabled_regions.keys(): 178 | self.awsclient = boto3.client('apigateway', 179 | aws_access_key_id=self.access_key.text, 180 | aws_secret_access_key=self.secret_key.text, 181 | region_name=region 182 | ) 183 | 184 | response = self.awsclient.delete_rest_api( 185 | restApiId=self.enabled_regions[region] 186 | ) 187 | print response 188 | self.enabled_regions = {} 189 | self.allEndpoints = [] 190 | return 191 | 192 | #Called on "save" button click to save the settings 193 | def saveKeys(self, event): 194 | aws_access_key_id=self.access_key.text 195 | aws_secret_access_key=self.secret_key.text 196 | self.callbacks.saveExtensionSetting("aws_access_key_id", aws_access_key_id) 197 | self.callbacks.saveExtensionSetting("aws_secret_access_key", aws_secret_access_key) 198 | return 199 | 200 | #Called on "Enable" button click to spin up the API Gateway 201 | def enableGateway(self, event): 202 | self.startAPIGateway() 203 | self.status_indicator.text = ENABLED 204 | self.isEnabled = True 205 | self.enable_button.setEnabled(False) 206 | self.secret_key.setEnabled(False) 207 | self.access_key.setEnabled(False) 208 | self.target_host.setEnabled(False) 209 | self.disable_button.setEnabled(True) 210 | return 211 | 212 | #Called on "Disable" button click to delete API Gateway 213 | def disableGateway(self, event): 214 | self.deleteAPIGateway() 215 | self.status_indicator.text = DISABLED 216 | self.isEnabled = False 217 | self.enable_button.setEnabled(True) 218 | self.secret_key.setEnabled(True) 219 | self.access_key.setEnabled(True) 220 | self.target_host.setEnabled(True) 221 | self.disable_button.setEnabled(False) 222 | return 223 | 224 | def getCurrEndpoint(): 225 | 226 | return 227 | 228 | #Traffic redirecting 229 | def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo): 230 | # only process requests 231 | if not messageIsRequest or not self.isEnabled: 232 | return 233 | 234 | # get the HTTP service for the request 235 | httpService = messageInfo.getHttpService() 236 | 237 | #Modify the request host, host header, and path to point to the new API endpoint 238 | #Should always use HTTPS because API Gateway only uses HTTPS 239 | if ':' in self.target_host.text: #hacky fix for https://github.com/RhinoSecurityLabs/IPRotate_Burp_Extension/issues/14 240 | host_no_port = self.target_host.text.split(':')[0] 241 | 242 | else: 243 | host_no_port = self.target_host.text 244 | 245 | if (host_no_port == httpService.getHost()): 246 | #Cycle through all the endpoints each request until then end of the list is reached 247 | if self.currentEndpoint < len(self.allEndpoints)-1: 248 | self.currentEndpoint += 1 249 | #Reset to 0 when end it reached 250 | else: 251 | self.currentEndpoint = 0 252 | 253 | messageInfo.setHttpService( 254 | self.helpers.buildHttpService( 255 | self.allEndpoints[self.currentEndpoint], 256 | 443, True 257 | ) 258 | ) 259 | 260 | requestInfo = self.helpers.analyzeRequest(messageInfo) 261 | new_headers = requestInfo.headers 262 | 263 | #Update the path to point to the API Gateway path 264 | req_head = new_headers[0] 265 | #hacky fix for https://github.com/RhinoSecurityLabs/IPRotate_Burp_Extension/issues/14 266 | if 'http://' in req_head or 'https://' in req_head: 267 | cur_path = re.findall('https?:\/\/.*?\/(.*) ',req_head)[0] 268 | new_headers[0] = re.sub(' (.*?) '," /"+STAGE_NAME+"/"+cur_path+" ",req_head) 269 | 270 | else: 271 | new_headers[0] = re.sub(' \/'," /"+STAGE_NAME+"/",req_head) 272 | 273 | #Replace the Host header with the Gateway host 274 | for header in new_headers: 275 | if header.startswith('Host: '): 276 | host_header_index = new_headers.index(header) 277 | new_headers[host_header_index] = 'Host: '+self.allEndpoints[self.currentEndpoint] 278 | 279 | #Update the headers insert the existing body 280 | body = messageInfo.request[requestInfo.getBodyOffset():len(messageInfo.request)] 281 | messageInfo.request = self.helpers.buildHttpMessage( 282 | new_headers, 283 | body 284 | ) 285 | 286 | #Tab name 287 | def getTabCaption(self): 288 | return EXT_NAME 289 | 290 | #Handle extension unloading 291 | def extensionUnloaded(self): 292 | self.deleteAPIGateway() 293 | return 294 | 295 | #Layout the UI 296 | def getUiComponent(self): 297 | aws_access_key_id = self.callbacks.loadExtensionSetting("aws_access_key_id") 298 | aws_secret_accesskey = self.callbacks.loadExtensionSetting("aws_secret_access_key") 299 | if aws_access_key_id: 300 | self.aws_access_key_id = aws_access_key_id 301 | if aws_secret_accesskey: 302 | self.aws_secret_accesskey = aws_secret_accesskey 303 | 304 | self.panel = JPanel() 305 | 306 | self.main = JPanel() 307 | self.main.setLayout(BoxLayout(self.main, BoxLayout.Y_AXIS)) 308 | 309 | self.access_key_panel = JPanel() 310 | self.main.add(self.access_key_panel) 311 | self.access_key_panel.setLayout(BoxLayout(self.access_key_panel, BoxLayout.X_AXIS)) 312 | self.access_key_panel.add(JLabel('Access Key: ')) 313 | self.access_key = JTextField(self.aws_access_key_id,25) 314 | self.access_key_panel.add(self.access_key) 315 | 316 | self.secret_key_panel = JPanel() 317 | self.main.add(self.secret_key_panel) 318 | self.secret_key_panel.setLayout(BoxLayout(self.secret_key_panel, BoxLayout.X_AXIS)) 319 | self.secret_key_panel.add(JLabel('Secret Key: ')) 320 | self.secret_key = JPasswordField(self.aws_secret_accesskey,25) 321 | self.secret_key_panel.add(self.secret_key) 322 | 323 | self.target_host_panel = JPanel() 324 | self.main.add(self.target_host_panel) 325 | self.target_host_panel.setLayout(BoxLayout(self.target_host_panel, BoxLayout.X_AXIS)) 326 | self.target_host_panel.add(JLabel('Target host: ')) 327 | self.target_host = JTextField('example.com', 25) 328 | self.target_host_panel.add(self.target_host) 329 | 330 | self.buttons_panel = JPanel() 331 | self.main.add(self.buttons_panel) 332 | self.buttons_panel.setLayout(BoxLayout(self.buttons_panel, BoxLayout.X_AXIS)) 333 | self.save_button = JButton('Save Keys', actionPerformed = self.saveKeys) 334 | self.buttons_panel.add(self.save_button) 335 | self.enable_button = JButton('Enable', actionPerformed = self.enableGateway) 336 | self.buttons_panel.add(self.enable_button) 337 | self.disable_button = JButton('Disable', actionPerformed = self.disableGateway) 338 | self.buttons_panel.add(self.disable_button) 339 | self.disable_button.setEnabled(False) 340 | 341 | self.protocol_panel = JPanel() 342 | self.main.add(self.protocol_panel) 343 | self.protocol_panel.setLayout(BoxLayout(self.protocol_panel, BoxLayout.Y_AXIS)) 344 | self.protocol_panel.add(JLabel("Target Protocol:")) 345 | self.https_button = JRadioButton("HTTPS",True) 346 | self.http_button = JRadioButton("HTTP",False) 347 | self.protocol_panel.add(self.http_button) 348 | self.protocol_panel.add(self.https_button) 349 | buttongroup = ButtonGroup() 350 | buttongroup.add(self.https_button) 351 | buttongroup.add(self.http_button) 352 | 353 | self.regions_title = JPanel() 354 | self.main.add(self.regions_title) 355 | self.regions_title.add(JLabel("Regions to launch API Gateways in:")) 356 | 357 | self.regions_panel = JPanel() 358 | self.main.add(self.regions_panel) 359 | glayout = GridLayout(4,3) 360 | self.regions_panel.setLayout(glayout) 361 | for region in AVAIL_REGIONS: 362 | cur_region = region.replace('-','_') 363 | cur_region = cur_region+'_status' 364 | setattr(self,cur_region,JCheckBox(region,True)) 365 | attr = getattr(self,cur_region) 366 | self.regions_panel.add(attr) 367 | 368 | self.status = JPanel() 369 | self.main.add(self.status) 370 | self.status.setLayout(BoxLayout(self.status, BoxLayout.X_AXIS)) 371 | self.status_indicator = JLabel(DISABLED,JLabel.CENTER) 372 | self.status_indicator.putClientProperty("html.disable", None) 373 | self.status.add(self.status_indicator) 374 | 375 | self.panel.add(self.main) 376 | return self.panel 377 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IPRotate_Burp_Extension 2 | 3 | Extension for Burp Suite which uses AWS API Gateway to change your IP on every request. 4 | 5 | More info: [Bypassing IP Based Blocking Using AWS - Rhino Security Labs](https://rhinosecuritylabs.com/aws/bypassing-ip-based-blocking-aws/) 6 | 7 | ## Description 8 | 9 | This extension allows you to easily spin up API Gateways across multiple regions. All the Burp Suite traffic for the targeted host is then routed through the API Gateway endpoints which causes the IP to be different on each request. (There is a chance for recycling of IPs but this is pretty low and the more regions you use the less of a chance). 10 | 11 | This is useful to bypass different kinds of IP blocking like bruteforce protection that blocks based on IP, API rate limiting based on IP or WAF blocking based on IP etc. 12 | 13 | ## Usage 14 | 15 | 1. Setup [Jython](https://www.jython.org/download.html) in Burp Suite. 16 | 2. Install the [boto3](https://github.com/boto/boto3) module for Python 2. 17 | 1. Make sure that you setup your [python environment in burp](https://portswigger.net/burp/documentation/desktop/tools/extender#python-environment) to load the [boto3](https://github.com/boto/boto3) module properly or it won't find it. 18 | 3. Ensure you have a set of AWS keys that have full access to the API Gateway service. This is available through the free tier of AWS. 19 | 4. Insert the credentials into the fields. 20 | 5. Insert the target domain you wish to target. 21 | 6. Select HTTPS if the domain is hosted over HTTPS. 22 | 7. Select all the regions you want to use.(The more you use the larger the IP pool will be) 23 | 8. Click "Enable". 24 | 9. Once you are done ensure you click disable to delete all the resources which were started. 25 | 26 | If you want to check on the resources and enpoints that were started or any potential errors you can look at the output console in Burp. 27 | 28 | ### The Burp UI 29 | 30 | ![Burp Extension UI](ui.png) 31 | 32 | ### Example of how the requests look 33 | 34 | ![Sample Requests](example.png) 35 | 36 | ### Setup 37 | 38 | Make sure you have Jython installed and add IPRotate.py through the Burp Extension options. 39 | 40 | ![Extension Setup](setup.png) 41 | 42 | ## Previous Research 43 | 44 | After releasing this extension it was pointed out that there has been other research in this area using AWS API Gateway to hide an IP address. There is some awesome research and tools by [@ustayready](https://twitter.com/ustayready) [@ryHanson](https://twitter.com/ryHanson) and [@rmikehodges](https://twitter.com/rmikehodges) using this technique. 45 | 46 | Be sure to check them out too: 47 | 48 | - [fireprox](https://github.com/ustayready/fireprox) 49 | - [hideNsneak](https://github.com/rmikehodges/hideNsneak) 50 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PortSwigger/ip-rotate/65bd4050be16cc2f634ead32b70098684672183e/example.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | boto3==1.9.225 2 | -------------------------------------------------------------------------------- /setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PortSwigger/ip-rotate/65bd4050be16cc2f634ead32b70098684672183e/setup.png -------------------------------------------------------------------------------- /ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PortSwigger/ip-rotate/65bd4050be16cc2f634ead32b70098684672183e/ui.png --------------------------------------------------------------------------------