I have a javascript code to get username from a php file. The javascript code is not in the same domain, so it is cross-domain. In this case, domain1.com want to retrieve user information from domain2.com by using XMLHttpRequest. Here is the code
var http = new XMLHttpRequest();
http.open("POST", linkbased+'/username/', true);
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http.onreadystatechange = function() {
if(http.readyState == 4 && http.status == 200) {
document.getElementById("username").innerHTML = http.responseText;
}
}
http.send(data);
Here is my php file code
<?php
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');
echo $_COOKIE['username'];
?>
If I access the php code directly, it will show the username. However, if I access it via XMLHttpRequest. It won't read the username. Is there something part am I missing?
From MDN
Credentialed requests
Credentialed Access Control Requests – that is, requests that are
accompanied by Cookies or HTTP Authentication information (and which
expect Cookies to be sent with responses) – can be either Simple or
Preflighted, depending on the request methods used.
In a Simple Request scenario, the request will be sent with Cookies
(e.g. if the withCredentials flag is set on XMLHttpRequest). If the
server responds with Access-Control-Allow-Credentials: true attached
to the credentialed response, then the response is accepted by the
client and exposed to web content. In a Preflighted Request, the
server can respond with Access-Control-Allow-Credentials: true to the
OPTIONS request.
So you might need to alter your code - the below is not tested however
var http = new XMLHttpRequest();
http.withCredentials = true;
http.onreadystatechange = function() {
if(http.readyState == 4 && http.status == 200) {
document.getElementById("username").innerHTML = http.responseText;
}
}
http.open("POST", linkbased+'/username/', true);
http.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
http.send( data );
if( $_SERVER['REQUEST_METHOD'] == "POST" ) {
if( $_SERVER['HTTP_ORIGIN'] == 'http://domain1.com' ){
header('Access-Control-Allow-Origin: http://domain1.com');
header('Access-Control-Allow-Methods: GET, POST');
header('Access-Control-Allow-Credentials: true');
header('Content-Type: text/plain');
echo array_key_exists( 'username', $_COOKIE ) ? $_COOKIE['username'] : 'username not set';
} else {
header('HTTP/1.1 403 Access Forbidden',true,403);
header('Content-Type: text/plain');
echo "Sorry, you are not allowed to make such a request";
}
}
Related
Recently i did a site for a client. After receiving it the client sent the site for acunetix security check, which brought back this alert.
----acunetix alert---
`Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Credentials: true
Any origin is accepted (arbitrary Origin header values are reflected in Access-Control-
Allow-Origin response headers). For the WordPress /wp-json/ endpoint, this may be
the intended behavior and requires manual review. For further information, please
refer to the WordPress REST API Handbook linked in the "References" section below.`
--- the end of acunetix alert ---
i finally found where this is located - in rest-api.php
function rest_send_cors_headers( $value ) {
$origin = get_http_origin();
if ( $origin ) {
// Requests from file:// and data: URLs send "Origin: null".
if ( 'null' !== $origin ) {
$origin = esc_url_raw( $origin );
}
header( 'Access-Control-Allow-Origin: ' . $origin );
header( 'Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE' );
header( 'Access-Control-Allow-Credentials: true' );
header( 'Vary: Origin', false );
} elseif ( ! headers_sent() && 'GET' === $_SERVER['REQUEST_METHOD'] && ! is_user_logged_in() ) {
header( 'Vary: Origin', false );
}
return $value;
}
Can anyone let me know what should i do in this case? How do i fix is or i just should let it be how it is done by wordpress.
thank you so much in advance.
see
function cors(){
if (array_key_exists('HTTP_ORIGIN', $_SERVER)) {
$origin = $_SERVER['HTTP_ORIGIN'];
} else if (array_key_exists('HTTP_REFERER', $_SERVER)) {
$origin = $_SERVER['HTTP_REFERER'];
} else {
$origin = $_SERVER['REMOTE_ADDR'];
}
$allowed_domains = array(
'http://localhost:4200',
'https://x.ir',
'https://www.x.ir',
);
if (in_array($origin, $allowed_domains)) {
header('Access-Control-Allow-Origin: ' . $origin);
}
header("Access-Control-Allow-Headers: Origin, X-API-KEY, X-Requested-With, Content-Type, Accept, Access-Control-Request-Method, Access-Control-Allow-Headers, Authorization, observe, enctype, Content-Length, X-Csrf-Token");
header("Access-Control-Allow-Methods: GET, PUT, POST, DELETE, PATCH, OPTIONS");
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Max-Age: 3600");
header('content-type: application/json; charset=utf-8');
$method = $_SERVER['REQUEST_METHOD'];
if ($method == "OPTIONS") {
header("HTTP/1.1 200 OK CORS");
die();
}
}
When I send POST request with json data in React app to PHP server, $_POST is empty.
And also php://input doesn't work.
I tried with following code.
PHP server side:
if($_SERVER['REQUEST_METHOD'] == "OPTIONS") {
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Allow-Headers: Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization');
header('Access-Control-Max-Age: 1000');
header("Content-Length: 0");
header("Content-Type: text/plain");
} else if($_SERVER['REQUEST_METHOD'] == "POST") {
echo "Checking case1:\n";
echo "<br/>";
$data = json_decode(file_get_contents('php://input'), true);
var_dump($data);
echo "<br/>";
echo "Checking case2:\n";
echo "<br/>";
var_dump($_POST);
}
React side code:
axios.post(
'https://xx.xx.com/index2.php',
{
member: id
},
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Access-Control-Allow-Origin': '*'
},
withCredentials: true
}
)
PHP server is running on CloudFlare.
I wonder if this is related with Cloudflare's cache process or not.
I hope any helps, Thank you in advance.
Use headers as below
It works for me on PHP
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
}
while sending from axios you need to change headers
headers: {
'Content-Type': 'multipart/form-data,multipart/form-data',
'Access-Control-Allow-Origin': '*'
}
I have made a XMLHttpRequest with the 'responseType' set to 'blob' to a php script, is it possible i return more than one type of response? If script can run successfully, response with 'contentType/application/zip' but if the script failed, response with 'string'(error text). I try to response a string in my php script
but, in my javascript, i set the XHR.responseType = 'blob'.
In PHP
if(someThingWrong)
{
echo "Archive not found"; //Repsonse is DOMString
header("HTTP/1.1 404 Not Found");
exit;
}
else
{
header("Content-type: application/zip"); //Response is octe-Stream or zip
header("Content-Disposition: attachment; filename=\"" . basename($newZipFile) . "\"" );
readfile($newZipFile);
}
In Javascript
XHR.open("POST", "anycode.php");
XHR.responseType = "blob";
When i try to access the xhr.ResponseText, it show 'undefined'.
if(XHR.status == 404)
{alert(XHR.ResponseText);}
Any help is greatly appreciated.
You can use a FileReader object to get the response as text.
if(XHR.status == 404){
var fr = new FileReader();
fr.onload = function(e){
alert(e.target.result);
}
fr.readAsText(XHR.response);
}
I am working on a Chrome Extension that will run on a 3rd party website. On page load it will make an API call to the 3rd party API and display some of the API information on the page.
Important bits of info
The extension will be running on https://app.example.com.
The API endpoints are under https://api.example.com.
I cannot modify the code or server configurations for either of the above.
Since my browser extension runs as if it were on the page it obviously introduces cross-domain ajax issues so to get around this I have put together a PHP script on https://forward.example.com. I can modify the code and server configuration for this.
https://forward.example.com
Purpose: To accept requests from any domain, and forward the request to the domain specified in the X-Destination-Url header. It should also respond back to the original request with the headers and content of the X-Destination-Url request.
forward.php
I know this is messy, but I just threw it together in a couple of minutes.
$headers = getallheaders();
$destinationKey = 'X-Destination-Url';
$destination = null;
$removeHeaders = array('Origin', 'X-Destination-Url', 'Referer');
if (array_key_exists($destinationKey, $headers)) {
$destination = $headers[$destinationKey];
unset($headers[destinationKey]);
}
foreach ($removeHeaders as $h) {
unset($headers[$h]);
}
$authTokenKey = 'X-Destination-Auth-Token';
$authToken = null;
if (array_key_exists($authTokenKey, $headers)) {
$authToken = $headers[$authTokenKey];
unset($headers[$authTokenKey]);
$headers['X-Auth-Token'] = $authToken;
}
if ($destination === null) {
die('Invalid X-Destination-Url');
}
$preparedHeaders = array();
foreach ($headers as $k => $v) {
$preparedHeaders[] = "{$k}:{$v}";
}
$responseHeaders = array();
function handleHeaderLine($ch, $headerLine)
{
global $responseHeaders;
$responseHeaders[] = $headerLine;
return strlen($headerLine);
}
$ch = curl_init();
// Note that at this point $destination == 'https://api.example.com'
$opts = array(
CURLOPT_URL => $destination,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_HEADERFUNCTION => 'handleHeaderLine',
CURLOPT_CUSTOMREQUEST => $_SERVER['REQUEST_METHOD'],
CURLOPT_HTTPHEADER => $preparedHeaders,
CURLOPT_SSL_VERIFYPEER => false,
);
curl_setopt_array($ch, $opts);
$resp = curl_exec($ch);
foreach ($responseHeaders as $header) {
header($header);
}
echo $resp;
die;
.htaccess
Header add Access-Control-Allow-Origin "*"
Header add Access-Control-Allow-Headers "Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, X-Auth-Token, X-Destination-Url, X-Destination-Auth-Token"
Header add Access-Control-Allow-Methods "PUT, GET, POST, DELETE, OPTIONS"
Chrome Extension
The Chrome Extension just runs as javascript on-page.
This is the bit of code that interacts with the above.
$.get({
async: true,
url: 'https://forward.example.com/forward.php',
beforeSend: function(xhr){
xhr.setRequestHeader('X-Destination-Auth-Token', 'api-key ' + self.apiKey);
xhr.setRequestHeader('X-Destination-Url', 'https://api.example.com');
},
success: function (d) {
console.log(d);
}
});
When the above runs, Chrome spits out the following error...
Mixed Content: The page at 'https://app.example.com' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://api.example.com'. This request has been blocked; the content must be served over HTTPS.
Finally...
How on earth has https://api.example.com been changed to http://api.example.com?
At the point that forward.php makes the curl request to X-Destination-Url it is using the correct https url, and it is set up to accept any SSL Certificate, so I'm quite confused as to how this is happening.
Can anyone give me some insight into what's going on here?
I am uploading files in mysubdomain images.mysite.url from application running on my site www.mysite.com.
For this I am using dropzone for AJAX file upload and PHP.
After selecting file it is showing message the Server responded with 0 code. at upload page and at console
XMLHttpRequest cannot load http://www.images.mysite.com/upload.php. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8085' is therefore not allowed access.
(for testing purpose, I first run on localhost same message after uploading to server)
On the server, my upload.php:
<?php
header('Access-Control-Allow-Origin: *');
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept");
header('Access-Control-Allow-Methods: GET, POST, PUT');
header('content-type: application/json; charset=utf-8');
$uploaddir = '/post/';
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
echo '<pre>';
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
echo "File is valid, and was successfully uploaded.\n";
} else {
echo "Possible file upload attack!\n";
}
echo 'Here is some more debugging info:';
print_r($_FILES);
print "</pre>";
?>
Dropzone
$("i#dzopen").dropzone({
paramName: "file",
maxFilesize: 10,
url: 'http://www.images.mysite.com/upload.php',
previewsContainer: "#media-upload-previews",
uploadMultiple: true,
parallelUploads: 1,
dataType: 'jsonp',
maxFiles: 10,
acceptedFiles: "image/*,audio/*,video/*",
init: function () {
this.on("success", function (file, responseText) {
log("log " + responseText);
});
}
});
How to resolve the problem?
With your code as is, the Access-Control-Allow-Origin header should have been sent.
You may have a server error, or something else that prevents the headers from being sent (headers already sent? content flushed? wrong URL?).
Look at your console and browser's developer tools network tab for errors and look at the headers.
After you fix whatever it is that prevented the headers form being sent, consider the following:
Dropzone sends a
Access-Control-Request-Headers:accept, cache-control, content-type, x-requested-with
header. You don't reply with cache-control accepted, so you will need to add it:
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Cache-Control");
You allow uploadMultiple, but only expect a single file.
The paramName that you specify in your dropzone config is different from the one that you use on the server ("file" vs 'userfile').
Your server does not return valid JSON, which the dropzone handler expects.
A contrived example that might get you started (don't use in production!):
<?php
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Cache-Control");
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT');
header('content-type: application/json; charset=utf-8');
$uploaddir = '/post/';
$idx = "file";
$res = array("success" => true, "status" =>array());
if (isset($_FILES[$idx]) && is_array($_FILES[$idx])) {
foreach ($_FILES[$idx]["error"] as $key => $error) {
$status = array("success" => true);
if ($error == UPLOAD_ERR_OK) {
$tmp_name = $_FILES[$idx]["tmp_name"][$key];
$name = $_FILES[$idx]["name"][$key];
if (move_uploaded_file($tmp_name, $uploaddir.$name)) {
$status["message"] = "ok";
} else {
$res["success"] = false;
$status["success"] = false;
$status["error"] = error_get_last();
$status["message"] = "internal server error";
}
} else {
$res["success"] = false;
$status["success"] = false;
$status["error"] = $error;
$status["message"] = "upload error";
}
$res["status"][] = $status;
}
}
echo(json_encode($res));
Edit:
Turns out there was a server issue. Apache's _mod_security_ had blocked the file uploads, but not normal POST or GET requests, and returned 406 Not Acceptable.
Issuing
$ curl http://example.com/upload.php -X POST \
-F "file[]=#/path/to/file" \
-v
resulted in
> POST /upload.php HTTP/1.1
> User-Agent: curl/7.35.0
> Host: images.sitename.com
> Accept: */*
> Content-Length: 719
> Expect: 100-continue
> Content-Type: multipart/form-data; boundary=------------------------755cbe89e26cbeb1
>
< HTTP/1.1 100 Continue
< HTTP/1.1 406 Not Acceptable
* Server nginx admin is not blacklisted
< Server: nginx admin
< Date: Mon, 19 Oct 2015 08:06:59 GMT
< Content-Type: text/html; charset=iso-8859-1
< Connection: keep-alive
< Content-Length: 380
* HTTP error before end of send, stop sending
<
(nginx serves as a reverse-proxy and Apache is behind it).
In such case, if you manage the server yourself, you can disable the problematic rule using .htaccess, or in other ways:
Method 1 - completely disable mode_security for the file (less recommended)
<IfModule mod_security2.c>
SecRuleEngine Off
</IfModule>
Method 2 - disable specific rule
$ cat /var/log/apache2/error.log | grep ModSecurity
(modify the path to point to your Apache error log), which should returns lines like this one:
[<date>] [error] [client <...>] ModSecurity: Access denied with code 406 (phase 2). <...> at REQUEST_FILENAME. [file <...>] [id "950004"] [msg <...>] <...>
Note the [id "950004"].
It can be disabled via:
<IfModule mod_security2.c>
SecRuleRemoveById 950004
</IfModule>
There are other, potentially better, ways of doing this.
Here is a good reference for mod_security configuration.