├── .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 | 
31 |
32 | ### Example of how the requests look
33 |
34 | 
35 |
36 | ### Setup
37 |
38 | Make sure you have Jython installed and add IPRotate.py through the Burp Extension options.
39 |
40 | 
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
--------------------------------------------------------------------------------