pythonmitmproxy

mitmproxy: Replace response by resubmitted request


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?


Solution

  • 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.