FastAPI how to allow endpoint access for specific IP only? - fastapi

How to limit endpoint access only to specific IPs with FastAPI?

The accepted answer makes use of the TrustedHostMiddleware but that can be easily spoofed using a reverse proxy, i.e. using NGINX or using any other technique. In my opinion, validating IP address in a custom middleware is more secure:
from fastapi import FastAPI, Request, status
from fastapi.responses import JSONResponse
app = FastAPI()
# Whitelisted IPs
WHITELISTED_IPS = []
#app.middleware('http')
async def validate_ip(request: Request, call_next):
# Get client IP
ip = str(request.client.host)
# Check if IP is allowed
if ip not in WHITELISTED_IPS:
data = {
'message': f'IP {ip} is not allowed to access this resource.'
}
return JSONResponse(status_code=status.HTTP_400_BAD_REQUEST, content=data)
# Proceed if IP is allowed
return await call_next(request)
I'd maintain a list of whitelisted IPs and then I'd compare the client IP to the list and will return a 400 Bad Request error if the IP is not in the whitelisted IPs list.

FastAPI provides a TrustedHostMiddleware that:
Enforces that all incoming requests have a correctly set Host header,
in order to guard against HTTP Host Header attacks.
from fastapi import FastAPI from fastapi.middleware.trustedhost import TrustedHostMiddleware
app = FastAPI()
app.add_middleware(
TrustedHostMiddleware, allowed_hosts=["example.com","*.example.com"]
)
#app.get("/") async def main():
return {"message": "Hello World"}
The following arguments are supported:
allowed_hosts - A list of domain names that should be allowed as hostnames. Wildcard domains such as *.example.com are supported for
matching subdomains to allow any hostname either use
allowed_hosts=["*"] or omit the middleware.
If an incoming request does not validate correctly then a 400 response
will be sent.
Another solution would be to compose an IP whitelist for your deployment medium (ex: k8).

You can use ufw
sudo ufw allow from IP proto tcp to any port PORT

Related

Usage of 'Host' Header in Web Requests

I am looking at the http-requests in BurpSuite. I see a field named as 'Host'. What is the importance of this field?
What happens if I change this field and then send the request? If I change the host header field to some other IP then would the server respond back to this new modified IP?
A single web server can host multiple websites with different domains and subdomains.
The Host header allows it to distinguish between them.
Given the limited availability of IPv4 addresses, this is important as there are more websites than available IP addresses.
What happens if I change this field and then send the request?
If the server pays attention to it and recognises the hostname, it will respond with that website (otherwise it may fall back to its default website or throw an error).
For an example, see Name-based Virtual Host Support in the Apache HTTPD manual.
If I change the host header field to some other IP then would the server respond back to this new modified IP?
No. The Host header is the host the client is asking for. It has nothing to do with where the response should be sent.
To quote from https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host :
The Host request header specifies the host and port number of the server to which the request is being sent.
If no port is included, the default port for the service requested (e.g., 443 for an HTTPS URL, and 80 for an HTTP URL) is implied.
A Host header field must be sent in all HTTP/1.1 request messages. A 400 (Bad Request) status code may be sent to any HTTP/1.1 request message that lacks a Host header field or that contains more than one.

How to redirect all mitmproxy HTTP traffic to a specific HTTP server?

I want to redirect all HTTP traffic intercepted by mitmproxy to a particular HTTP server, regardless of where the HTTP traffic was destined too.
I know how to set an upstream proxy server for mitmserver, but in this case I don't want another proxy server, but a (destination) HTTP server instead.
Any ideas?
One way to do this would be inject a python script that overwrites the destination of every request. You add a -s script.py parameter to the mitmproxy/mitmdump command (or call master.addons.add(script.Script('script.py')) if you are using mitmproxy library) and add for example the following into your script:
from mitmproxy import http
def request(self, flow: http.HTTPFlow) -> None:
flow.request.host = 'google.com'
flow.request.path = '/'
... further customize request method, cookies, etc etc as needed

Why does the host address is included in HTTP 1.1 GET command?

GET /calcuapp/calculator.jsp HTTP/1.1
Host: 192.168.1.66:8080
I'm using PuTTy and the host destination is already set up on the settings. Why do I need again to type the host destination as you can see above?
The short answer is Virtual Hosts.
For many years now, it has been quite common to host multiple sites/domains from a single server. HTTP 1.1 supports this by requiring the host header. If you use HTTP 1.0 you may leave this out.
The Host HTTP header is mandatory since HTTP/1.1 and it's used for virtual hosting.
It must include the domain name of the server, and the TCP port number on which the server is listening. The port number may be omitted if the port is the standard port for the service requested (80 for HTTP and 443 for HTTPS).
A HTTP/1.1 request that lacks the Host header should be responded with a 400 (Bad Request) status code.
The RFC 7230, the current reference message syntax and routing in HTTP/1.1, tells the whole story about this header:
5.4. Host
The Host header field in a request provides the host and port
information from the target URI, enabling the origin server to
distinguish among resources while servicing requests for multiple
host names on a single IP address.
Host = uri-host [ ":" port ]
A client MUST send a Host header field in all HTTP/1.1 request
messages. If the target URI includes an authority component, then a
client MUST send a field-value for Host that is identical to that
authority component, excluding any userinfo subcomponent and its #
delimiter. If the authority component is missing or
undefined for the target URI, then a client MUST send a Host header
field with an empty field-value.
Since the Host field-value is critical information for handling a
request, a user agent SHOULD generate Host as the first header field
following the request-line.
For example, a GET request to the origin server for
http://www.example.org/pub/WWW/ would begin with:
GET /pub/WWW/ HTTP/1.1
Host: www.example.org
A client MUST send a Host header field in an HTTP/1.1 request even if
the request-target is in the absolute-form, since this allows the
Host information to be forwarded through ancient HTTP/1.0 proxies
that might not have implemented Host.
When a proxy receives a request with an absolute-form of
request-target, the proxy MUST ignore the received Host header field
(if any) and instead replace it with the host information of the
request-target. A proxy that forwards such a request MUST generate a
new Host field-value based on the received request-target rather than
forward the received Host field-value.
Since the Host header field acts as an application-level routing
mechanism, it is a frequent target for malware seeking to poison a
shared cache or redirect a request to an unintended server. An
interception proxy is particularly vulnerable if it relies on the
Host field-value for redirecting requests to internal servers, or for
use as a cache key in a shared cache, without first verifying that
the intercepted connection is targeting a valid IP address for that
host.
A server MUST respond with a 400 (Bad Request) status code to any
HTTP/1.1 request message that lacks a Host header field and to any
request message that contains more than one Host header field or a
Host header field with an invalid field-value.
Your local resolver (DNS etc) converts the host name on the command line to an IP address before connecting; there is no way for the remote server to know which host name you gave on the command line if there are multiple host names which resolve to the same IP address (this is what's called "virtual hosting"; with HTTP 1.0, you needed a separate IP address for each distinct HTTP host, which is extremely wasteful, but saves you from needing to transmit the Host: header).

Super simple Python 3.x library for accessing http requests

Is there a very simple way to access the direct text/bytes of an http request in Python 3.x? Similar to what you would get out of Telnet or something like that. I'm looking for something I can set up to listen on a port, accept the request and directly read what comes across. It wouldn't define it was looking for POST or GET, etc., just the raw values:
Sample value:
GET /index.html/?=request HTTP/1.1
Host: www.example.com
User-Agent: Safari/4.0
The library I was looking for is here. The code in particular I was looking for is this:
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
"""
The RequestHandler class for our server.
It is instantiated once per connection to the server, and must
override the handle() method to implement communication to the
client.
"""
def handle(self):
# self.request is the TCP socket connected to the client
self.data = self.request.recv(1024).strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# just send back the same data, but upper-cased
self.request.sendall(self.data.upper())
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
# Create the server, binding to localhost on port 9999
server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
server.serve_forever()

JBoss Netty getting User IP (Http Request)

I need to log the user ip ady's for every request to our JBoss Netty server. I thought:
MessageEvent e;
e.getChannel().getRemoteAddress();
was the correct answer, but this always returns 127.0.0.1 and I need the actual client ip. Coming from Rails I checked how they find out the ip, from the docu:
Determines originating IP address.
REMOTE_ADDR is the standard but will
fail if the user is behind a proxy.
HTTP_CLIENT_IP and/or
HTTP_X_FORWARDED_FOR are set by
proxies so check for these if
REMOTE_ADDR is a proxy.
HTTP_X_FORWARDED_FOR may be a comma-
delimited list in the case of multiple
chained proxies; the last address
which is not trusted is the
originating IP.
So should I check for all the headers in Netty or is there an easier way?
Ok I have the answer. Using ChannelHandlerContext instead of MessageEvent does the trick.
SocketAddress remoteAddress = ctx.getChannel().getRemoteAddress();

Resources