I try to replace a response in mitmproxy by the result of resubmitting the same request again. This is my current script:
from mitmproxy import http
from mitmproxy import ctx
import requests
import re
class ReplaceToken:
nextToken = ""
def response(self, flow: http.HTTPFlow):
m = re.search('localhost.local:8085/test/index.php/(.+?)" method=', str(flow.response.content))
if m:
self.nextToken = m.group(1)
ctx.log.info("Next token: " + self.nextToken)
m = re.search('Wrong token!<br>', str(flow.response.content))
if m:
flow = flow.copy()
request = flow.request.copy()
ctx.log.info("Found wrong token! Resend request with correct token...")
request.path = re.sub("/[^/]*$", "/" + self.nextToken, flow.request.path)
playback = ctx.master.addons.get('clientplayback')
flow.request = request
playback.start_replay([flow])
addons = [ReplaceToken()]
In the response I want to detect an error condition (eg. wrong CSRF token). If that condition is present, I want to resend the request with the correct token, wait for the correct response and replace to original response data with the correct one.
However, in the above script the response is not replaced. And the correct request is only made in mitmprox internally.
A possible workaround would be to use $random-http-lib
to make a HTTP request bypassing mitmproxy. Like the following PoC:
import requests
def response(self, flow: http.HTTPFlow):
r = requests.get("http://www.example.com")
ctx.log.info(r.text)
flow.response.text = r.text
However, this would require to manually "copy" each aspect from the original request (HTTP method, path, headers, body, etc...) to the external HTTP library - which I would like to avoid.
Is there any way to achieve this using only mitmproxy itself?
Accidentally (more or less) I found a basic concept for an OAuth addon on github, which does exactly what I was looking for: oauth-mitmproxy
So the code would look like this:
def response(self, flow: http.HTTPFlow):
m = re.search('localhost.local:8085/test/index.php/(.+?)" method=', str(flow.response.content))
if m:
self.nextToken = m.group(1)
ctx.log.info("Next token: " + self.nextToken)
if flow.is_replay:
self.postponed_flow.response = flow.response
self.postponed_flow.resume()
return
m = re.search('Wrong token!<br>', str(flow.response.content))
if m:
ctx.log.info("Found wrong token! Resend request with correct token...")
resend_flow = flow.copy()
resend_flow.request.path = re.sub("/[^/]*$", "/" + self.nextToken, flow.request.path)
ctx.master.commands.call("replay.client", [resend_flow])
flow.intercept()
self.postponed_flow = flow
The "trick" here is to intercept and store the error-state flow (self.postponed_flow
). Then the complete flow is cloned, adapted and replayed. If a replay flow is detected, the response is copied to the original flow and the flow will be resumed.