SSRF Vulnerability while calling REST API - java

I am using a method where it calls another REST API to retrieve an ID from the DB. When I run the veracode scan for the class I am getting Security flaw "Server-side Request Forgery" at below line.
response = resttemplate.getForEntity(resturl, String.class);
Not sure How to fix this issue. Any help is appreciated. Below is my full code for that method.
public static String getIDFromDB(String resturl) {
String id = null;
RestTemplate resttemplate = new RestTemplate();
ResponseEntity<String> response = new ResponseEntity<>(HTTPStatus.OK)
try {
response = resttemplate.getForEntity(resturl, String.class);
if (response.getStatusCode == HTTPStatus.OK && response.getBody.trim() != null) {
id = response.getBody.trim() ;
}
} Catch(Exception e) {
log.error("failed to get msgID: {}", e);
}
}

This is because you are allowing in your code to pass the resturl completely in your code, so it enables the attacker to bypass and route the URL to their intended destination.
To avoid this, so should externalise and refer the URL having domain and the application contexts with operation name in config files or dB

Related

connection timeout while calling another 3rd party rest api using rest temple in java aws lamba

I am trying to call 3rd party rest api using rest template in aws lambda but connection timeout is happening. this third party api is working fine in postman.
Below is the code snippet
#Override
public APIGatewayProxyResponseEvent apply(APIGatewayProxyRequestEvent request) {
Gson gson = new Gson();
EmailRequest emailRequest = gson.fromJson(request.getBody(),EmailRequest.class);
System.out.println("Inside function");
APIGatewayProxyResponseEvent responseEvent = new APIGatewayProxyResponseEvent();
if(emailRequest!=null && emailRequest.getUserId() !=null && emailRequest.getPid()!=null) {
Optional<UserDetail> userDetail = userRepository.findById(emailRequest.getUserId());
Optional<Prospect> prospect = prospectRepository.findById(emailRequest.getPid());
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
HttpEntity<Prospect> entity = new HttpEntity<>(prospect.get(), httpHeaders);
try {
ResponseEntity<String> result = restTemplate.postForEntity(userDetail.get().getCrmUrl(), entity, String.class);
if(result.getStatusCode().is5xxServerError()) {
responseEvent.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
responseEvent.withBody("Issue while sending email details");
}
responseEvent.setStatusCode(HttpStatus.OK.value());
} catch (ResourceAccessException e) {
responseEvent.setStatusCode(HttpStatus.GATEWAY_TIMEOUT.value());
responseEvent.withBody("Rest templete time out");
return responseEvent;
}
responseEvent.setStatusCode(HttpStatus.OK.value());
responseEvent.withBody("UserId crm : " + userDetail.get().getCrmUrl() +" and Prospect first name : " + prospect.get().getFirstName());
} else {
responseEvent.setStatusCode(HttpStatus.BAD_REQUEST.value());
responseEvent.withBody("Please check the request data");
}
return responseEvent;
}
Please help me out how can I access third party rest api in aws lambda.
This could be due to the fact that a lambda function in a VPC does not have access to the internet by default. From docs:
When you connect a function to a VPC in your account, the function can't access the internet unless your VPC provides access.
A popular way to overcome this is through NAT gateway and private subnet as described in:
How do I give internet access to a Lambda function that's connected to an Amazon VPC?

Google Cloud Platform - cloud functions API - 401 Unauthorized

I'm struggling with invoking GCP cloud functions via REST API using Java.
The steps that I've performed to do it were:
create a service account with role "Cloud Functions Invoker"
download JSON key file for the newly created service account
in my code, obtain an access token using the following method:
private String getAuthToken() {
File credentialsPath = new File(PATH_TO_JSON_KEY_FILE);
GoogleCredentials credentials;
try (FileInputStream serviceAccountStream = new FileInputStream(credentialsPath)) {
credentials = ServiceAccountCredentials.fromStream(serviceAccountStream);
return credentials
.createScoped(Lists.newArrayList("https://www.googleapis.com/auth/cloud-platform"))
.refreshAccessToken()
.getTokenValue();
} catch (IOException e) {
throw new RuntimeException("Action could not be performed");
}
}
perform a REST call, using the created token:
public <Payload, Response> ResponseEntity<Response> callCloudFunction(
String endpoint,
Payload payload,
Class<Response> klazz
) {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_JSON);
String url = gCloudUrl + endpoint;
String token = getAuthToken();
String payloadString = null;
if (payload != null) {
try {
ObjectMapper objectMapper = new ObjectMapper();
payloadString = objectMapper.writeValueAsString(payload);
} catch (JsonProcessingException e) {
System.out.println(e.getMessage());
throw new RuntimeException("Could not perform action");
}
}
headers.add("Authorization", String.format("Bearer %s", token));
HttpEntity<String> entity = new HttpEntity<>(payloadString, headers);
return restTemplate.exchange(url, HttpMethod.POST, entity, klazz);
}
The implementation looks fine, but in response I'm getting 401 Unauthorized.
Unfortunately, GCP documentation is not really helpful. I think I've searched through all the possible places.
First of all, agree, it's not clear...
Then, you have to know (and it's not clear again) that you need an access token to call Google Cloud API, but and identity token to call IAP (on App Engine for example) or private Cloud Function and Cloud Run. And this identity token need to be signed by Google.
And, as mentioned in the code, you need to have a service account on your computer, but I recommend you to avoid this on GCP, it's not required if you use default authentication (see my code, on your computer set the GOOGLE_APPLICATION_CREDENTIALS env var that points to the service account key file). The best way is to not use service account key file on your computer also, but it's not yet possible (that is a security issue IMO, and I'm discussing with Google on this...)
Anyway, here a code snippet which works in Java (nowhere in the documentation...)
String myUri = "https://path/to/url";
// You can use here your service account key file. But, on GCP you don't require a service account key file.
// However, on your computer, you require one because you need and identity token and you can generate it with your user account (long story... I'm still in discussion with Google about this point...)
Credentials credentials = GoogleCredentials.getApplicationDefault().createScoped("https://www.googleapis.com/auth/cloud-platform");
IdTokenCredentials idTokenCredentials = IdTokenCredentials.newBuilder()
.setIdTokenProvider((IdTokenProvider) credentials)
.setTargetAudience(myUri).build();
HttpRequestFactory factory = new NetHttpTransport().createRequestFactory(new HttpCredentialsAdapter(idTokenCredentials));
HttpRequest request = factory.buildGetRequest(new GenericUrl(myUri));
HttpResponse httpResponse = request.execute();
System.out.println(CharStreams.toString(new InputStreamReader(httpResponse.getContent(), Charsets.UTF_8)));
NOTE If you want to continue to use RestTemplate object and set manually your token, you can generate it like this
String token = ((IdTokenProvider) credentials).idTokenWithAudience(myUri, Collections.EMPTY_LIST).getTokenValue();
System.out.println(token);

spring mvc Proxy server - maintaining url context (contextpath)

I have the below piece spring REST controller class.
#RestController
#RequestMapping("/global")
public class ProxyController extends BaseController{
#RequestMapping(value = "/**")
public ResponseEntity<String> proxy(HttpServletRequest request, HttpServletResponse response ) throws Exception {
try {
String restOfTheUrl = (String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
URL uri = new URL("https://myrealserver" +
restOfTheUrl);
HttpHeaders headers = new HttpHeaders();
HttpEntity<String> httpEntity = new HttpEntity<>(headers);
RestTemplate restTemplate = new RestTemplate();
return resp;
} catch (Exception e) {
logger.error("Error ", e);
return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
What I am trying to achieve here is to hide a server behind a proxy, which blindly forwards requests to the server.
This piece of code is invoked with url
https://myproxyserver/myapp1/end/point1
which in turn returns an html page with few clickable links. Now when the user clicks I am expecting the link to be invoked as
https://myproxyserver/myapp1/end/point2
Where as actually the endpoint invoked is
https://myproxyserver/end/point2
In the html page returned by the actual server, the path is end/point2 and has no mention of myapp1. So on click on those links my context changes to https://myproxyserver/end/point2 instead of https://myproxyserver/myapp1/end/point2
How do I ensure that the root context is always https://myproxyserver/myapp1 and not https://myproxyserver ?
You want to get your server context path. this is sample code.
like this :
public static String getServerNameAndContextPath(HttpServletRequest req) {
return "https://" + req.getServerName() + req.getContextPath();
}
Finally I resolved the problem by taking what D D suggested. I scanned through the whole response body, fortunately I had a pattern that I could use to scan and appended the context of the url to where ever required. That resolved the problem for me this problem.

Efficient way to detect strings?

I am working on a project in which I am making a call to one of my servers using RestTemplate which is running a restful service and getting the response back from them.
The response that I will be getting from my server can be either of these error responses (that's all I have for error response) if something has gone wrong -
{"warning": "user_id not found", "user_id": some_user_id}
{"error": "user_id for wrong partition", "user_id": some_user_id, "partition": some_partition}
{"error": "missing client id", "client_id":2000}
or below successful response (it can be any random json string key can also be different) -
{"#data": {"oo":"1205000384","p":"2047935"}
If I am getting any error response as mentioned above, then I am deserializing it (my bad :( ) so that I can log them as an error with a specific error or warning I got front the server which can be for example - user_id not found or missing client id.
If it is a successful response then also I am deserializing it which I don't need for my use case as we don't have any POJO and I just need to return the response as it is which I have got from the server.
In my use case, I don't need to deserialize my response string if it is a successful response as we don't have any POJO for that and we are returning the response string as it is which we have got from the server. But just for logging specific error messages (if I am getting error response from the server) I am deserializing it which I am thinking is unnecessary. There might be better solution for my use case.
Below is my Java client which is calling Callable task using future.get -
public class TestingClient implements IClient {
private ExecutorService service = Executors.newFixedThreadPool(10);
private RestTemplate restTemplate = new RestTemplate();
#Override
public String executeSync(ClientKey keys) {
String response = null;
try {
ClientTask ClientTask = new ClientTask(keys, restTemplate);
Future<String> future = service.submit(ClientTask);
response = handle.get(keys.getTimeout(), TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
} catch (Exception e) {
}
return response;
}
}
And now below is my ClientTask class which implements Callable interface. In the call method, I am generating an URL and then hit the server using RestTemplate and get the response back -
class ClientTask implements Callable<String> {
private ClientKey cKeys;
private RestTemplate restTemplate;
public ClientTask(ClientKey cKeys, RestTemplate restTemplate) {
this.restTemplate = restTemplate;
this.cKeys = cKeys;
}
#Override
public String call() throws Exception {
// .. some code here
String url = "some_url";
String response = restTemplate.getForObject(url, String.class);
String test = checkJSONResponse(response);
return test;
}
private String checkJSONResponse(final String response) throws Exception {
// may be there are some better way of doing it for my scenario instead of using GSON
Gson gson = new Gson();
String str = null;
JsonObject jsonObject = gson.fromJson(response, JsonObject.class); // parse it, may be performance issues here/
if (jsonObject.has("error") || jsonObject.has("warning")) {
final String error = jsonObject.get("error") != null ? jsonObject.get("error").getAsString() : jsonObject
.get("warning").getAsString();
// log specific `error` here using log4j
str = response;
} else {
str = response;
}
return str;
}
}
As you can see in my above code we are deserializing the JSON string only to log specific error messages if we are getting any error response back. But for successful response we don't need any deserialization but still we are doing it.
Is there any better way of solving this problem? Because currently I am seeing some performance issues with the GSON deserialization.
The only way I can identify successful response along with error response is with error or warning in the response so I am thinking of using regular expressions which can identify error or warning as the key in the response string. If they contain error or warning in the response string then extract the specific error or warning message and log it. But not sure whether this will have any performance benefit or not.
Is there any other better way of solving this problem without using GSON deserialization.
It is a good practice to use HTTP status codes for your responses (e.g. BAD_REQUEST, NOT_FOUND). Return one of them from the server and then check on the client. It will allow to parse response only if some error code is returned:
String result = restTemplate.execute("url", HttpMethod.GET, null, new HttpMessageConverterExtractor<String> {
#Override
public MyEntity extractData(ClientHttpResponse response)
throws IOException {
String result = super.extractData(response);
if (response.getStatusCode() != HttpStatus.OK) {
// parse message and log only for some error code
JsonObject errorJson = parse(result);
log.warn("Got {} status error, with message [{}]", response.getStatusCode(), errorJson.get("warning"));
}
return result;
}
});
You do not need to deserialize to a POJO.
A simple JSON parser such as the one found on json.org will provide minimal JSON parsing an return a JSONObject that you can query.
I very much doubt that
you can come up with a faster parsing of your json responses using regular expressions or otherwise, without taking the risk of failing in corner cases
given the size of your response strings, that the JSON parsing is the performance bottleneck in your code
Unless you have done some serious profiling, I would play safe and follow the first rule of program optimization

Always get null response in ResponseEntity when using RestTemplate.postForEntity

I' m trying to send JSON request using Jackson library from my Android app to the web server but response is always null. I tested it just with the HttpRequest API and all works fine - I've got a response. But now I try to use Spring RestTemplate and I can't receive a result. Here is my code:
protected Void doInBackground(String... params) {
LinkedHashMap<String, Object> _map = new LinkedHashMap<String, Object>();
_map.put("login", "Fred");
_map.put("password", "pass");
ObjectMapper _mapper = new ObjectMapper ();
StringWriter _writer = new StringWriter();
try {
_mapper.writeValue(_writer,_map);
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
String _baseURL = "https...."//Address of the server;
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> _entity = new HttpEntity<String>(_writer.toString(),requestHeaders);
RestTemplate templ = new RestTemplate();
templ.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
templ.getMessageConverters().add(new StringHttpMessageConverter());
ResponseEntity<String> _response = templ.postForEntity(_baseURL, _entity,String.class);
String _Test = _response.getBody();
So I always have null in _Test.
I suspect this is because of https protocol. Can RestTemplate work with https?
So what's wrong with that code. How to fix this?
Thanks in advance. I really need a help!
You have to set the responseType, otherwise the RestTemplate will throw away the body of your response. It needs the responseType to find the correct message converter. With a null responseType, the delegate below will be null...
if (delegate != null) {
T body = delegate.extractData(response);
return new ResponseEntity<T>(body, response.getHeaders(), response.getStatusCode());
}
else {
return new ResponseEntity<T>(response.getHeaders(), response.getStatusCode());
}
With the RestTemplate default constructor, Spring includes just about every converter except for RSS, XML and JSON, which depends on if Rome, JAXB or Jackson is on the classpath. I would set the responseType as String and run it with the debugger to see why it's not finding the correct converter. It's hard for me to say why without seeing the response and headers from the server.
Typo or are you connecting to an https port?
String _baseURL = "https...."//Address of the server;
I think you should monitor the port you are trying to connect to and see if there is a connection even established. One easy way I do that is to make a laptop an ad-hoc network and have an Android device connect to it and then, you should be able to monitor all traffic from your android device with a packet sniffer like wireshark.

Categories