Arduino esp8266 device gets Bad Request error from apache server while making request from - apache

I am sending post request from my arduino device to my hosted server it gives me 400 Bad request error.
Same code runs well in my localhost but not working on Hosted server.
My arduino code if give below.
void callApi(String ID,String path,int comm,String message,int isResponseNeeded) {
String serverPath="www.xyz.com";
String data = "{" ;
data = data + "\"id\": \""+ID+"\"," ;
data = data + "\"version\": \""+VERSION+"\"," ;
data = data + "\"command\": \""+comm+"\"," ;
data = data + "\"message\": \""+message+"\"" ;
data = data + "}" ;
Serial.print("Send data...on=>");
Serial.println(path);
if (wifiClient.connect(serverPath,80)) {
Serial.println(data);
wifiClient.println("POST /abc/xyz HTTP/1.1");
wifiClient.println("Content-Type: application/json");
wifiClient.print("Host: ");
wifiClient.println(serverPath);
wifiClient.print("Content-Length: ");
wifiClient.println(data.length());
wifiClient.print("\n");
wifiClient.print(data);
wifiClient.print("\n");
Serial.println("Data sent...Reading response..");
if(isResponseNeeded>0){
unsigned long timeout = millis();
while (wifiClient.available() == 0) {
if (millis() - timeout > 10000) {
Serial.println(">>> Client Timeout !");
break;
}
}
String response;
while(wifiClient.available()){
String line = wifiClient.readStringUntil('\n');
response=line;
Serial.println(response);
}
Serial.println(response.length());
}
out put of this code.
{"id": "_60_1_94_f_a9_3c_","version": "1.10","command": "0","message": ""}
Data sent...Reading response..
HTTP/1.1 400 Bad Request
Date: Sat, 20 May 2017 05:13:41 GMT
Server: Apache/2.4.18 (Ubuntu)
Content-Length: 324
Connection: close
Content-Type: text/html; charset=iso-8859-1
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
<hr>
<address>Apache/2.4.18 (Ubuntu) Server at ubuntu-512mb-nyc2-01.localdomain Port 80</address>
</body></html>
===========================================================
And one one more thing i have observed that. it runs GET request fine. only it cause issue while making POST request on my hosted server.

I guess that API service requires POST content and body to be application/x-www-form-urlencoded encoded.
application/x-www-form-urlencoded or multipart/form-data?

Related

Why does this REST request work from every client except Talend API tester

This is an example of a REST POST request that works everywhere except Talend API tester. It give a 500 error.
Here is how I set the request up in my applications.
URLConnection connection = new URL("https://" + authHost + "/connect/token").openConnection();
logger.info("Connection oppened");
// message contains the form data: key=value&key=value&key=value
String message = "password=" + password + "&grant_type=password&username=" + username +
"&client_id=foolid&scope=mouthwash";
logger.info(message);
// Setting header fields.
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setRequestProperty("Accept", "application/json");
connection.getOutputStream().write(message.getBytes("UTF-8"));
Here is a working example in Postman
POST /connect/token HTTP/1.1
Host: identityserver.uat.example.com
Content-Type: application/x-www-form-urlencoded
Accept: application/json
Cookie: ARRAffinity=6a37d2ecf3441e27913cb832c4b767c68cad0e45c8806b3c5344d1b52d57f67a; ARRAffinitySameSite=6a37d2ecf3441e27913cb832c4b767c68cad0e45c8806b3c5344d1b52d57f67a
Content-Length: 137
password=secret&grant_type=password&username=fool&client_id=foolid&scope=mouthwash
And here is what Talend say it sends, which gets the 500 error -- which I understand comes from the server.
POST /connect/token HTTP/1.1
Accept: application/json
Content-Length: 137
Content-Type: application/x-www-form-urlencoded
Host: identityserver.uat.example.com
password=secret&grant_type=password&username=fool&client_id=foolid&scope=mouthwash
What's happening here?

arduino gets response 301 when making a HTTPS GET request

I am making a simple arduino app that send a GET request to a HTTPS site. The code I'm using is exactly the same as the code in the MKRGSM library examples (GsmSslWebClient). But for whatever reason I always get the same response when connecting to a HTTPS site: "301 moved permanently". I kind of know what that means, I am aware that you are supposed to just make another request to the location specified in the header. But I don't know what I have to change to be able to address a https site. I'm sorry for my ignorance and I do know that in the example it clearly states that it connects to http://www.arduino.cc/asciilogo.txt but why is it then any different than the normal http example?
I would also point out here, that I have tried changing port to 80 and client settings, to work for unprotected http which works just fine. So its just the https that doesn't work.
this is the code:
/*
Web client
This sketch connects to a website using SSL through a MKR GSM 1400 board. Specifically,
this example downloads the URL "http://www.arduino.cc/asciilogo.txt" and
prints it to the Serial monitor.
Circuit:
* MKR GSM 1400 board
* Antenna
* SIM card with a data plan
created 8 Mar 2012
by Tom Igoe
*/
// libraries
#include <MKRGSM.h>
#include "arduino_secrets.h"
// Please enter your sensitive data in the Secret tab or arduino_secrets.h
// PIN Number
const char PINNUMBER[] = SECRET_PINNUMBER;
// APN data
const char GPRS_APN[] = SECRET_GPRS_APN;
const char GPRS_LOGIN[] = SECRET_GPRS_LOGIN;
const char GPRS_PASSWORD[] = SECRET_GPRS_PASSWORD;
// initialize the library instance
GSMSSLClient client;
GPRS gprs;
GSM gsmAccess;
// URL, path and port (for example: arduino.cc)
char server[] = "arduino.cc";
char path[] = "/asciilogo.txt";
int port = 443; // port 443 is the default for HTTPS
void setup() {
// initialize serial communications and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
Serial.println("Starting Arduino web client.");
// connection state
bool connected = false;
// After starting the modem with GSM.begin()
// attach the shield to the GPRS network with the APN, login and password
while (!connected) {
if ((gsmAccess.begin(PINNUMBER) == GSM_READY) &&
(gprs.attachGPRS(GPRS_APN, GPRS_LOGIN, GPRS_PASSWORD) == GPRS_READY)) {
connected = true;
} else {
Serial.println("Not connected");
delay(1000);
}
}
Serial.println("connecting...");
// if you get a connection, report back via serial:
if (client.connect(server, port)) {
Serial.println("connected");
// Make a HTTP request:
client.print("GET ");
client.print(path);
client.println(" HTTP/1.1");
client.print("Host: ");
client.println(server);
client.println("Connection: close");
client.println();
} else {
// if you didn't get a connection to the server:
Serial.println("connection failed");
}
}
void loop() {
// if there are incoming bytes available
// from the server, read them and print them:
if (client.available()) {
char c = client.read();
Serial.print(c);
}
// if the server's disconnected, stop the client:
if (!client.available() && !client.connected()) {
Serial.println();
Serial.println("disconnecting.");
client.stop();
// do nothing forevermore:
for (;;)
;
}
}
and this is the output:
Starting Arduino web client.
connecting...
connected
HTTP/1.1 301 Moved Permanently
Server: nginx
Date: Thu, 19 Nov 2020 20:24:07 GMT
Content-Type: text/html
Content-Length: 178
Connection: close
Location: https://www.arduino.cc/asciilogo.txt
Strict-Transport-Security: max-age=500; includeSubDomains
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx</center>
</body>
</html>
disconnecting.
It is also possible that I'm just stupid and the answer is clear as day, but if somebody could please tell me exactly what to do, maybe even put an example, that would be greatly appreciated.
I haven't really tried much except changing the setting to see if they work for normal http and some other libraries that worked even worse. Sadly that's all I could find on the internet since I'm not that skilled to be messing with libraries on my own. I am using arduino mkr gsm 1400.
Be sure to call me out if I missed to mention any detail that could help solving this issue
Thanks to anybody that can help me in advance.

How to disable access to cloudfront via the *.cloudfront.net url?

I created an AOI to restrict access of the s3 bucket to public.
So you can not access the s3 objects via the s3 endpoint but cloudfront can access all those objects and serve them.
I setup an Alternate Domain Names and add the SSL Certificate for this domain.
I setup route 53 with a A rule to alias cloudfront distribution
I can access the page using the Cloudfront public url (*.cloudfront.net) and mydomain.com
How can I remove the *.cloudfront.net access to my page?
This should be possible because the only service that needs this url is route 53.
Much easier than Lamda#Edge would be just to configure an ACL to block each request containing the Host header with your cloudfront distribution url.
Configure AWS WAF / ACL
You can use Lambda#Edge Viewer Request trigger. This allows you to inspect the request before the cache is checked, and either allow processing to continue or to return a generated response.
So, you can check the referer and make sure the request coming from your domain.
'use strict';
exports.handler = (event, context, callback) => {
// extract the request object
const request = event.Records[0].cf.request;
// extract the HTTP `Referer` header if present
// otherwise an empty string to simplify the matching logic
const referer = (request.headers['referer'] || [ { value: '' } ])[0].value;
// verify that the referring page is yours
// replace example.com with your domain
// add other conditions with logical or ||
if(referer.startsWith('https://example.com/') ||
referer.startsWith('http://example.com/'))
{
// return control to CloudFront and allow the request to continue normally
return callback(null,request);
}
// if we get here, the referring page is not yours.
// generate a 403 Forbidden response
// you can customize the body, but the size is limited to ~40 KB
return callback(null, {
status: '403',
body: 'Access denied.',
headers: {
'cache-control': [{ key: 'Cache-Control', value: 'private, no-cache, no-store, max-age=0' }],
'content-type': [{ key: 'Content-Type', value: 'text/plain' }],
}
});
};
For more info read the following pages:
https://stackoverflow.com/a/51006128/6619626
Generating HTTP Responses in Request Triggers
Updating HTTP Responses in Origin-Response Triggers
Finally, this article has a lot of valuable info
How to Prevent Hotlinking by Using AWS WAF, Amazon CloudFront, and Referer Checking
Also, a very simple solution is to add a CloudFront function on the viewer request event for your behaviour in question:
function isCloudFrontURL(headers) {
if(headers && headers["host"]) {
if(headers["host"].value.includes("cloudfront"))
return true
else if(headers["host"].multiValue)
return headers["host"].multiValue.some(entry => entry.value.includes("cloudfront"))
}
return false
}
function handler(event) {
if(isCloudFrontURL(event.request.headers))
return {
statusCode: 404,
statusDescription: 'Page not found',
headers: {
"content-type": {
"value": "text/plain; charset=UTF-8"
}
}
}
else
return event.request;
}
Based on #mhelf's answer, I prepared a demo in Terraform on how to set up WAF v2 for CloudFront.
Terraform resources
(1.) Configure AWS provider.
// WAF v2 for CloudFront MUST be created in us-east-1
provider "aws" {
alias = "virginia"
region = "us-east-1"
}
(2.) Create CloudFront distribution.
// CloudFront which is accessible at example.com
// and should not be accessible at ***.cloudfront.net
resource aws_cloudfront_distribution cf {
// ...
// ...
web_acl_id = aws_wafv2_web_acl.waf.arn
aliases = [
"example.com",
]
// ...
// ...
}
(3.) Finally create WAF v2.
// WAF v2 that blocks all ***.cloudfront.net requests
resource aws_wafv2_web_acl waf {
provider = aws.virginia
name = "example-waf"
description = "..."
scope = "CLOUDFRONT"
default_action {
allow {}
}
rule {
name = "cf-host-rule"
priority = 0
action {
block {
}
}
statement {
regex_match_statement {
regex_string = "\\w+.cloudfront.net"
field_to_match {
single_header {
name = "host"
}
}
text_transformation {
priority = 0
type = "LOWERCASE"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "example-waf-cf-host-rule"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "example-waf"
sampled_requests_enabled = true
}
}
Notes
It would probably be safer/cleaner to use byte_match_statement to check the Host header value against aws_cloudfront_distribution.cf.domain_name. However, this would create a cycle between the CF and the WAF resource, which is why I used regex_match_statement.
Support for regex_match_statement has been added relatively recently in the AWS provider v4.34.0 (GH Issue 25101 / GH Pull request 22452 / GH Release v4.34.0)
WAF is a paid service, see: https://aws.amazon.com/waf/pricing/
cURL test
curl -v https://example.com
> GET / HTTP/2
> Host: example.com
> user-agent: curl/7.68.0
> accept: */*
>
< HTTP/2 200
< content-type: image/jpeg
< content-length: 9047
< date: Wed, 19 Oct 2022 13:40:55 GMT
< x-cache: Hit from cloudfront
curl -v https://***.cloudfront.net
> GET / HTTP/2
> Host: ***.cloudfront.net
> user-agent: curl/7.68.0
> accept: */*
>
< HTTP/2 403
< server: CloudFront
< date: Thu, 20 Oct 2022 08:15:44 GMT
< content-type: text/html
< content-length: 919
< x-cache: Error from cloudfront
< via: 1.1 ***.cloudfront.net (CloudFront)
<
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>403 ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
Request blocked.
We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
<BR clear="all">
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront)
Request ID: ***
</PRE>
<ADDRESS>
</ADDRESS>
</BODY></HTML>

Inserting client certificates into an HTTPS POST request

I'm building a hardware device which connects to the AWS IOT platform. According to the documentation the authentication with the aws iot platform is done with TLS. I have the Root CA, client key and client certificate files on the device that authorize the access. Is there a way to use these files in the HTTP header while making the POST request? If so, how? So far here is the code for the Energia IDE (based on the Arduino IDE) and using the WiFiClient methods.
if (client.sslConnect(aws_endpoint, 443))
{
Serial.println("\nConnected to AWS endpoint");
String PostData = "{\"value1\" : \"testValue\", \"value2\" : \"Hello\", \"value3\" : \"World!\" }";
request = "POST /things/";
request += thingname;
request += "/shadow";
request += " HTTP/1.1";
Serial.print("Request:\t"); Serial.println(request);
Serial.print("Post data:\t"); Serial.println(PostData);
client.println(request);
client.println("Host: ");
client.println(aws_endpoint);
client.println(":443");
client.println("User-Agent: Energia/1.1");
client.println("Connection: close");
client.println("Content-Type: application/json");
client.print("Content-Length: "); client.println(PostData.length());
client.println();
client.println(PostData);
client.println();
}
else
{
Serial.println("Connection failed");
}
Serial.println();
Serial.println("Server response:");
Serial.println();
// Capture response from the server. (10 second timeout)
long timeOut = 5000;
long lastTime = millis();
while((millis()-lastTime) < timeOut)
{ // Wait for incoming response from server
while (client.available())
{ // Characters incoming from the server
char c = client.read(); // Read characters
Serial.write(c);
}
}
This however, gives an authentication error:
HTTP/1.1 403 Forbidden
content-type: application/json
content-length: 91
date: Tue, 26 Jul 2016 11:46:59 GMT
x-amzn-RequestId: 4d5388a9-e3c4-460a-b674-c3f971f3330d
connection: Keep-Alive
x-amzn-ErrorType: ForbiddenException:
{"message":"Missing Authentication Token","traceId":"4d5388a9-e3c4-460a-b674-c3f971f3330d"}
The TLS client certificates would be sent/used as part of your client.sslConnect() call, not as part of the HTTP request. The TLS handshake (and exchange/validation of client and server certificates) happens before any HTTP message is sent.
This AWS forums post suggests that you may need to be using port 8443 (not port 443), for the shadow API. It looks like the use/requirement of TLS mutual authentication (via certificates), versus the use of AWS SIGv4 headers, is determined by AWS IOT based on the port used.
Hope this helps!

Getting Dropbox - 403 error when logging on using dropboxuploader.php

Hey I'm using dropboxuploader.php to login into dropbox. All was working fine, but when i came into work yesterday i could no longer connect. This is what dropbox is returning to me.
HTTP/1.1 100 Continue
HTTP/1.1 403 Forbidden
Server: nginx/1.2.3
Date: Thu, 04 Oct 2012 08:44:36 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
It seems you tried to do something we can't verify. Did you log into a different Dropbox account in a different window? Try clicking here to go back to the page you came from, or just go home.
Replace the login function with below code and it should work:
protected function login() {
$data = $this->request('https://www.dropbox.com/login');
$str = '<input type="hidden" name="t" value="';
$start = strpos($data,$str);
$val = "";
if($start !== false)
{
$val = substr($data,$start+strlen($str),24);
}
$data = $this->request('https://www.dropbox.com/login', true, array('login_email'=>$this->email, 'login_password'=>$this->password, 't'=>$val));
if (stripos($data, 'location: /home') === false)
throw new Exception('Login unsuccessful.');
$this->loggedIn = true;
}
Just update your dropbox uploader file instead doing your fixes.
https://github.com/jakajancar/DropboxUploader