abhinavsingh / proxy.py

💫 Ngrok FRP Alternative • ⚡ Fast • 🪶 Lightweight • 0️⃣ Dependency • 🔌 Pluggable • 😈 TLS interception • 🔒 DNS-over-HTTPS • 🔥 Poor Man's VPN • ⏪ Reverse & ⏩ Forward • 👮🏿 "Proxy Server" framework • 🌐 "Web Server" framework • ➵ ➶ ➷ ➠ "PubSub" framework • 👷 "Work" acceptor & executor framework
https://abhinavsingh.com/proxy-py-a-lightweight-single-file-http-proxy-server-in-python/
BSD 3-Clause "New" or "Revised" License
2.98k stars 573 forks source link

Modify proxy response for certain urls #1464

Open JavaProgswing opened 2 weeks ago

JavaProgswing commented 2 weeks ago

I want to intercept the request responses and return a custom status code and response body for a list of urls. Can someone guide me in attempting this?

JavaProgswing commented 2 weeks ago

???

JJ-Author commented 2 weeks ago

approach is to write your own plugin (look at the hooks available here) we started to have a look at all the example plugins to figure things out. maybe you can get inspiration from our project since we also alter requests https://github.com/dbpedia/ontology-time-machine/blob/main/ontologytimemachine/custom_proxy.py unfortunately I have no resources (since I am just a newbie user myself) to further guide you, but I agree that documentation needs improvement.

NOTE: selective https interception seems broken in proxypy at the moment in case you would need to change https requests

JavaProgswing commented 2 weeks ago

Thanks, I'll check that out. When I was trying with https urls it works but I only seem to receive CONNECT request methods.

JavaProgswing commented 2 weeks ago

and whenever I try to edit the requests i get requests.exceptions.SSLError: HTTPSConnectionPool(host='google.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLError(1, '[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1002)')))

abhinavsingh commented 2 weeks ago

We have following examples on how to modify request/response, does none of these help? Note that, when you modify body of the responses, you must also modify necessary headers as per new body e.g. content length, content type etc

https://github.com/abhinavsingh/proxy.py?tab=readme-ov-file#modifypostdataplugin https://github.com/abhinavsingh/proxy.py?tab=readme-ov-file#modifychunkresponseplugin https://github.com/abhinavsingh/proxy.py?tab=readme-ov-file#modifyrequestheaderplugin

JavaProgswing commented 2 weeks ago

I only receive CONNECT method when connecting to proxy with a url. how will I check if tls interception works?

abhinavsingh commented 2 weeks ago

If you see CONNECT only, it means TLS intercept is simply not working. Please make sure basic TLS interception examples are working for you before proceeding

JavaProgswing commented 1 week ago

The tls interception examples don't seem to work for me, i generated ca-key, ca-cert and ca-signing-key from wsl then ran the program on windows. but I only receive CONNECT method requests.

JavaProgswing commented 1 week ago

I'm getting the error [WinError 2] The system cannot find the file specified ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=ca_file) IN proxy\core\connection\server.py

when trying to connect with a https url.

JJ-Author commented 1 week ago

my advice is trying to get it running on wsl first. make sure that openssl is installed in that. Note that you need openssl for interception not only for generating the ca cert files, so the claim that proxypy has zero dependencies ist not valid (anymore).

as a general note: you never provide the startup commands, your code, and the full stack trace. this heavily decreases the chance that somebody can help you.

My guesstimate is that you dont have openssl installed or are passing the ca cert files wrong.

JavaProgswing commented 1 week ago

PROXY CODE: from proxy.http.proxy import HttpProxyBasePlugin from proxy.http.parser import HttpParser import proxy import sys

class RequestPlugin(HttpProxyBasePlugin): def init(self, *args, *kwargs): super().init(args, **kwargs)

def handle_client_request(self, request: HttpParser):
    print(f"Request _url: {request._url}")
    print(f"Request.method: {request.method}")
    print(f"Request protocol: {request.protocol}")
    print(f"Request host: {request.host}")
    print(f"Request path: {request.path}")

    print(f"Request properties: {vars(request)}")

    return request

if name == "main":

sys.argv += [
    "--ca-key-file",
    "..\\https-interception-proxypy-main\\ca-key.pem",
    "--ca-cert-file",
    "..\\https-interception-proxypy-main\\ca-cert.pem",
    "--ca-signing-key-file",
    "..\\https-interception-proxypy-main\\ca-signing-key.pem",
]
sys.argv += [
    "--hostname",
    "127.0.0.1",
    "--port",
    "8080",
    "--plugins",
    __name__ + ".RequestPlugin",
    "--log-level",
    "d",
]

proxy.main()

CLIENT CODE:

import requests proxy = { 'http': 'http://127.0.0.1:8080', 'https': 'https://127.0.0.1:8080' }

response = requests.get('https://google.com/', proxies=proxy) print(response.text) print(response.headers)

What could be the problem here?

JavaProgswing commented 1 week ago

it does not work for https urls.

abhinavsingh commented 1 week ago

I don't see your plugin trying to modify the response. Try to modify the response chunks , try this method https://github.com/abhinavsingh/proxy.py/blob/develop/proxy/http/proxy/plugin.py#L130 , see existing examples overriding this method. Lmk.

JavaProgswing commented 6 days ago
from proxy.http.proxy import HttpProxyBasePlugin
from proxy.common.utils import build_http_response
import proxy
import requests
from http.client import responses
import sys

class ProxyPlugin(HttpProxyBasePlugin):
    def handle_client_request(self, request):
        print("Handle client request hook")
        print(
            f"Request method: {request.method} - Request host: {request.host} - Request path: {request.path} - Request headers: {request.headers}"
        )
        mock_response = requests.Response()
        mock_response.status_code = 200
        mock_response.url = 'https://example.com/success'
        mock_response.headers['Content-Type'] = 'text/html'
        mock_response._content = b'<html><body><h1>To be implemented</h1></body></html>'
        self.queue_response(mock_response)
        return None

    def queue_response(self, response):
        self.client.queue(
            build_http_response(
                response.status_code,
                reason=bytes(responses[response.status_code], "utf-8"),
                headers={
                    b"Content-Type": bytes(
                        response.headers.get("Content-Type"), "utf-8"
                    )
                },
                body=response.content,
            )
        )

if __name__ == "__main__":
    sys.argv += [
        "--ca-key-file",
        "..\\Python\\SeleniumProxy\\ca-key.pem",
        "--ca-cert-file",
        "..\\Python\\SeleniumProxy\\ca-cert.pem",
        "--ca-signing-key-file",
        "..\\Python\\SeleniumProxy\\ca-signing-key.pem",
    ]
    sys.argv += [
        "--hostname",
        "127.0.0.1",
        "--port",
        "8080",
        "--plugins",
        __name__ + ".ProxyPlugin",
    ]

    proxy.main()

It doesn't work this way, curl returns