The question is self explanatory, I hope. I am setting up a Spring Security enviroment with a CAS-server. Because the exact same application is deployed on the same server, but the server is accessible via different host names (.de domain, .com domain, possibly more than that) and we want to deploy the same application on test systems and the local one as well, I built a dynamic service, where the service URL is derived from request URL.
public static String makeDynamicUrlFromRequest(ServiceProperties serviceProperties, HttpServletRequest request) {
String serviceUrl = "https://backup-url.de/login";
URI uri = null;
try {
uri = new URI(request.getRequestURL().toString());
} catch (URISyntaxException e) {
logger.error("Someone tried accessing a disallowed service!", e);
}
if(uri != null){
serviceUrl = uri.getScheme() + "://" + uri.getHost() + "/login";
}
return serviceUrl;
}
Is it possible to spoof this? If it is, does an additional regex-check provide me with the necessary security against this?
#developerwjk
"If they modified the request url how would you have gotten the request?"
An HTTP server is just a program that listens on a TCP port, waits for some incoming text and writes out some text as a response. (A trivial web server can be written in like 20 lines of code.) It only sees the IP address and port of whatever connected to it. That could even be a proxy, or some other sort of middle-ware. If you don't tell the program "by the way, I reached you through the URL http://my.com/myapp/servlet" then it just doesn't know e.g. how a browser will reach it.
#Schaka
I don't know about your particular setup, but for jetty9, the result of getRequestURL is determined from the request URL in the request header, and - if the former is missing - the URL in the Host parameter. That is, if you connect to my.com and send the following request:
POST http://spoofed1.tld/myapp/servlet HTTP/1.1
Host: spoofed2.tld
(Keep in mind that the Host parameter is mandatory.)
Then getRequestURL will return http://spoofed1.tld/myapp/servlet
And if you send this:
POST /myapp/servlet HTTP/1.1
Host: spoofed2.tld
Then jetty itself will respond with
HTTP/1.1 302 Found
Location: http://spoofed2.tld/myapp/servlet
Content-Length: 0
Server: Jetty(<some version number>)
So the answer is yes, HttpServletRequest.getRequestURL() is spoofable! by modifying the request URL and/or the Host request header.
Related
I have an application implemented with Spring Boot, where I use Spring Security for authentication. I already have "token based" authentication in place, where clients are required to retrieve a token, and then use that token to authenticate in subsequent requests.
I would like to enhance this so that a token could be restricted to a specific hostname, so that is can only be used for requests from that host. This is similar to what the google maps API does with its API keys, where it is possible to restrict them by IP or host name.
Here is the code I have implemented to try to retrieve the request's host name
public String getClientHostName(HttpServletRequest request) {
String hostName = null;
// get the request's IP address
String clientAddress = httpRequest.getRemoteAddr();
String xfHeader = httpRequest.getHeader("X-Forwarded-For");
if (xfHeader != null) {
clientAddress = xfHeader.split(",")[0];
}
// try to resolve the host name from the IP address
try {
InetAddress address = InetAddress.getByName(clientAddress);
hostName = address.getHostName();
} catch (UnknownHostException e) {
logger.error("Failed to get the host name from the request's remote address. ", e);
}
return hostName;
}
I have 2 issues right now:
This code does not always manage to retrieve the hostname. Sometimes it just returns the IP address. I understand this may be down to some IP spoofing check the InetAddress class does.
When testing requests from different hosts, I do not always get the IP address I am expecting. I often get the IP of another host that is forwarding the request (which I thought would be solved by checking "X-Forwarded-For"). This makes me wonder how to even retrieve the IP of the host that is the real originator of the request.
Is there a reliable way to check the host name of the originator of a request?
have you tried getting hostname by String referrer = request.getHeader("referer"); ?
Also, on client side also you can add a snippet to find out the hostname in the headers.
Or you can provide below code to be added on client side and on server you can read the value of domain which will return hostname
<input type="button" value="Register" onClick="call()"/>
<script>
function call(){
var domain=window.location.hostname;
window.open('http://<your-hostname>/register?domain='+domain,'_self');
}
</script>
I am currently working on a project where we have multiple microservices on the server side. Which microservices are available to the client changes constantly, as do the location they are available on.
To keep all these changes away from the client, I am working on a REST Router. This would be a single microservice on which knows where each of the other microservices is located. The client can then just talk to this router microservice which will send the request to the correct endpoint.
While this should be fairly straight forward in my opinion, I'm running into al sorts of problems. Since the microservices don't necessarily live on the same host, the router can't simple forward the call, since this only works within the same host.
I am currently trying to make it work by sending a redirect back to the client containing the correct endpoint URL in the location header. But when I try this through restlet, I get a 404 instead of the expected 307 with the location header.
Here's my router code:
#Path("/{any : .*}")
public Response forward(#Context HttpServletRequest request) {
Response response = null;
String requestUri = request.getRequestURI().toString();
String actualUri = requestUri.substring(requestUri.indexOf("route") + 6);
String endpointId = actualUri.substring(0, actualUri.indexOf('/'));
try {
response = redirectTo("http://localhost:8181/api/user/available");
} catch (Throwable e) {
e.printStackTrace();
response = Response.serverError().status(500).build();
}
return response;
}
private Response redirectTo(String path) {
try {
URI uri = new URI(path);
return Response.temporaryRedirect(uri).build();
} catch (URISyntaxException e) {
e.printStackTrace();
return null;
}
}
As far as I can tell, this should work. When I debug the code, I can see that the created Response contains the 307 HTTP status, and the correct location header. But in the client test application (Restlet in Chrome) I keep hitting the 404. When I point restlet directly at the http://localhost:8181/api/user/available I do get the expected result, so the endpoint is available and responding on that address.
Can anyone point me out where I'm going wrong here? Is this really a server issue, or am I missing something in my client software?
I want to develop an android app to get post.xml with HttpClient. But it failed to get content with 80 port.
If I start the web server(WEBrick here) with 3000 port, the URI is http://192.168.1.103:3000/posts.xml;
Android App can get response with correct length, like 568;
The same web files, I started them with another server (Nignx here) with 80 port, the uri is
"http://192.168.1.103/posts.xml; The Android App can NOT get content with length, it's -1 here.
This URI can be opened with browser(both PC and android emulator) correctly. Furthermore, the response is "HTTP/1.1 200 OK" with responsep.getStatusLine().
is it related with "Socket ports below 1024 can NOT access in linux like system", which is on
http://groups.google.com/group/android-developers/browse_thread/thread/660123ca64ba1229#
Any Ninja can tell me what should I do if I can to get content with 80 port?
The following is my code.
public class AndroidWorldActivity extends Activity {
/** Called when the activity is first created. */
TextView tv;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
retreiveProjects();
}
private void retreiveProjects()
{
HttpClient httpClient = new DefaultHttpClient();
try
{
String url3000 = "http://192.168.1.103:3000/posts.xml";
String url = "http://192.168.1.103/posts.xml";
Log.d( "posts", "performing get " + url3000);
HttpGet httpGet=new HttpGet(url3000);
HttpResponse responsep=httpClient.execute(httpGet);
System.out.println(responsep.getStatusLine());
HttpEntity httpEntity = responsep.getEntity();
int length = ( int ) httpEntity.getContentLength();
// print the lenght of content
System.out.println("The content length is: "+length);
Log.d( "posts", "The content length is: " + length );
From your description, I understand that you are trying to connect from adroid to an external HTTP server attached to port 80? If so, restriction about port lower than 1024 on android has nothing to do (you are not trying to listen on port 80 on android device). I think, that you have a problem with Nginx.
Try to execute GET request from an external machine to Nginx and investigate response content (headers, payload). I would recommend to do it with some more low-level tool instead of web browser (almost all web browser nowadays are able to "repair" illegal server responses), for example curl:
curl -D - http://192.168.1.103/posts.xml
You seem to have two separate problems.
The problem on WeBrick seems to be that the UNIX / Linux won't allow your web server to bind to port 80. There are two obvious things to check:
Is the web server running with root privilege when it attempts to bind to the port? See Is there a way for non-root processes to bind to "privileged" ports on Linux? for a variety of ways to work around this problem.
Is some other application already bound to port 80? Is so, kill it and try to run your web server again.
The problem with Nignx is different. Here, the server is up and running and giving your client responses, but the client is seeing -1 as the content length.
This is normal behaviour. The getContentLength() method returns -1 if the response doesn't have a "Content-length" header, and it is perfectly legitimate (according to the HTTP specification) for a response to not have this header. You have two choices:
Change your client-side application to deal with the case where the content length is unspecified; e.g. just read the body into a buffer and count how many bytes you got.
Change the server to set the relevant header.
FOLLOWUP
I see. Your original question was hard to understand and I misinterpreted. You seemed to be saying that WEBrick wasn't working at all.
The difference between WEBrick and Nginx is that they simply implement the response differently. Both are legitimate (valid) implementation. The real problem is that your application is assuming that a web server will always set the "Content-length" header. That is an incorrect assumption.
To repeat, the problem / fault is in your client code, not in Nginx.
I use Netbeans to generate web service client code, client-style JAX-WS, so i can invoke a web service API.
However, when I invoke the web service API, I get the exception:
com.sun.xml.internal.ws.client.ClientTransportException: The server sent HTTP status code 307: Temporary Redirect
Why do I get this? What is the workaround? I know the problem isn't with the web service itself, because I can get responses fine via soapUI and .Net.
Faced the same problem about a month ago.
Web service client classes were generated using Apache CXF and web service returned HTTP
status 307, which led to the same exception.
Invocation of the same web service method using soapUI with property Follow Redirects set to true was successful and returned needed data.
After googling awhile, it looked like there is no property to enable following redirects in the JAX-WS for this.
So, below is the code which is currently working, though I'm not sure it is compliant with any standards:
Supposing generated client classes looks like:
// generated service class
public class MyWebServiceClient extends javax.xml.ws.Service {
// ...
private final QName portName = "...";
// ...
public RetrieveMyObjects getRetrieveMyObjects() {
return super.getPort(portName, RetrieveMyObject.class);
}
// ...
}
// generated port interface
// annotations here
public interface RetrieveMyObjects {
// annotations here
List<MyObject> getAll();
}
Now, upon executing following code:
MyWebServiceClient wsClient = new MyWebServiceClient("wsdl/location/url/here.wsdl");
RetrieveMyObjectsPort retrieveMyObjectsPort = wsClient.getRetrieveMyObjects();
wsClient should return instance which is both instance of RetrieveMyObjects & javax.xml.ws.BindingProvider interfaces. It is not stated anywhere on the surface of JAX-WS, but it seems that a lot of code is based on that fact. One can re-assure him\herself by executing something like:
if(!(retrieveMyObjectsPort instanceof javax.xml.ws.BindingProvider)) {
throw new RuntimeException("retrieveMyObjectsPort is not instance of " + BindingProvider.class + ". Redirect following as well as authentication is not possible");
}
Now, when we are sure that retrieveMyObjectsPort is instance of javax.xml.ws.BindingProvider we can send plain HTTP POST request to it, simulating SOAP request (though it looks incredibly incorrect & ugly, but this works in my case and I didn't find anything better while googling) and check whether web service will send redirect status as a response:
// defined somewhere before
private static void checkRedirect(final Logger logger, final BindingProvider bindingProvider) {
try {
final URL url = new URL((String) bindingProvider.getRequestContext().get(ENDPOINT_ADDRESS_PROPERTY));
logger.trace("Checking WS redirect: sending plain POST request to {}", url);
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setInstanceFollowRedirects(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "text/html; charset='UTF-8'");
connection.setDoOutput(true);
if(connection.getResponseCode() == 307) {
final String redirectToUrl = connection.getHeaderField("location");
logger.trace("Checking WS redirect: setting new endpoint url, plain POST request was redirected with status {} to {}", connection.getResponseCode(), redirectToUrl);
bindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, redirectToUrl);
}
} catch(final Exception e) {
logger.warn("Checking WS redirect: failed", e);
}
}
// somewhere at the application start
checkRedirect(logger, (BindingProvider) retrieveMyObjectsPort);
Now, what this method does is: it takes BindingProvider.ENDPOINT_ACCESS_PROPERTY of retrieveMyObjectsPort i.e. the url to which this port method will be sending SOAP requests and sends plain HTTP POST request as described above. Then it checks whether response status is 307 - Temporary Redirect (other statuses like 302 or 301 may also be included) and if it is, gets the URL to which web service is redirecting and sets new endpoint for the specified port.
In my case this checkRedirect method is called once for each web service port interface and then everything seems to work fine:
Redirect is checked on url like http://example.com:50678/restOfUrl
Web service redirects to url like https://example.com:43578/restOfUrl (please note that web service client authentication is present) - endpoint of a port is set to that url
Next web service requests executed via that port are successful
Disclaimer: I'm quite new to webservices and this is what I managed to achieve due to the lack of solutions for this questions, so please correct me if something is wrong here.
Hope this helps
Yes I know this post is old, but I've had similar errors, and thought maybe somebody would benefit from my solution.
the one that plagued me the most was:
com.sun.xml.ws.client.ClientTransportException: The server sent HTTP status code 200: OK
Which turns out to mean an incomplete response header. Apparently jax-ws does some kind of validation that includes validating the HTTP headers as well. And the server I was using was just sending an empty header.
It worked like a charm after adding 'application/soap+xml' to the Content-Type header.
I am writing a Java Rest Web Service and need the caller's IP Address. I thought I saw this in the cookie once but now I don't see it. Is there a consistent place to get this information?
I saw one example of using an "OperationalContext" to get it but that was not in java.
Inject a HttpServletRequest into your Rest Service as such:
import javax.servlet.http.HttpServletRequest;
#GET
#Path("/yourservice")
#Produces("text/xml")
public String activate(#Context HttpServletRequest req,#Context SecurityContext context){
String ipAddressRequestCameFrom = requestContext.getRemoteAddr();
// header name is case insensitive
String xForwardedForIP = req.getHeader("X-Forwarded-For");
// if xForwardedForIP is populated use it, else return ipAddressRequestCameFrom
String ip = xForwardedForIP != null ? xForwardedForIP : ipAddressRequestCameFrom;
System.out.println("IP is "+ip);
// get the host name the client contacted. If the header `Host` is populated the `Host` header is automatically returned.
// An AWS ALB populated the Host header for you.
String hostNameRequestCameFrom = req.getServerName();
System.out.println("Host is "+hostNameRequestCameFrom);
//Also if security is enabled
Principal principal = context.getUserPrincipal();
String userName = principal.getName();
}
As #Hemant Nagpal mentions, you can also check the X-Forwarded-For header to determine the real source if a load balancer inserts this into the request.
According to this answer, the getHeader() call is case insensitive.
You can also get the servername that the client contacted. This is either the DNS name or the value set in the Host header with an OSI layer 7 load balancer can populate.
1. Example: no headers are populated
curl "http://127.0.0.1:8080/"
returns
IP is 127.0.0.1
Host is 127.0.0.1
2. Example: X-Forwarded-For and Host headers are populated
curl --header "X-Forwarded-For: 1.2.3.4" --header "Host: bla.bla.com:8443" "http://127.0.0.1:8080/"
returns
IP is 1.2.3.4
Host is bla.bla.com
I think you can get the IP through the request object.
If I'm not mistaken, request.getRemoteAddr() or so.
You could do something like this:
#WebService
public class YourService {
#Resource
WebServiceContext webServiceContext;
#WebMethod
public String myMethod() {
MessageContext messageContext = webServiceContext.getMessageContext();
HttpServletRequest request = (HttpServletRequest) messageContext.get(MessageContext.SERVLET_REQUEST);
String callerIpAddress = request.getRemoteAddr();
System.out.println("Caller IP = " + callerIpAddress);
}
}
Assuming you are making your "web service" with servlets, the rather simple method call .getRemoteAddr() on the request object will give you the callers IP address.
If your application is running on a webserver that is located behind a reverse proxy or load balancer, then that proxy can be configured to inject the requested IP address in a request header. Different reverse proxies can inject different headers. Consult the documentation for your proxy server. We listed a couple of the most used in our example below but this is by no means a complete list.
When your client uses a (forward) proxy, then it might insert headers to say what the client IP addres is. Or it might not. And the IP address inserded here might be incorrect.
This means that the value you get by calling request.getRemoteAddr() is the IP address of the immediate upstream source of the request.
As we said, there are many headers for different proxies in use, but x-forwareded-for is most likely to be inserted by a proxy.
As a last note, even if you get an IP address either from the header or from request.getRemoteAddr() it is not guarenteed to be the client IP address. e.g.: if your proxy does not include the IP address of the client then you’ll get the IP address of the proxy or load balancer. If your client works on a private network and connect to the internet via a NAT gateway, then the IP address in the HTTP request will be an address of the NAT server. Or even for a hacker it is quite easy to inject a header with a different IP address. So this means that you cannot reliably find out the IP address of the system that the request originated from.
private static final String[] IP_HEADER_CANDIDATES = {
"X-Forwarded-For",
"Proxy-Client-IP",
"WL-Proxy-Client-IP",
"HTTP_X_FORWARDED_FOR",
"HTTP_X_FORWARDED",
"HTTP_X_CLUSTER_CLIENT_IP",
"HTTP_CLIENT_IP",
"HTTP_FORWARDED_FOR",
"HTTP_FORWARDED",
"HTTP_VIA",
"REMOTE_ADDR" };
public static String getClientIpAddress(HttpServletRequest request) {
for (String header : IP_HEADER_CANDIDATES) {
String ip = request.getHeader(header);
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
return ip;
}
}
return request.getRemoteAddr();
}