CSRF Failure On AJAX POST Request After Deploy Django Application With Nginx - ajax

I use Nginx and Gunicorn to deploy my Django 2.X blog on VPS.
When i push some data to Django backend via Jquery AJAX, then i got 403 CSRF error. I googled a lot but still can't figure out how to fix this problem. I tried to deploy them on my local computer with same configuration files, and everything is okay.
Firstly, I didn't set CSRF_USE_SESSIONS = True and CSRF_COOKIE_HTTPONLY = True in my Django settings.py . so they are both default value False.
I've copied the AJAX sample code from Django document:
// get csrftoken via JavaScript
function getCookie(name) {
let cookieValue = null
if (document.cookie && document.cookie !== '') {
let cookies = document.cookie.split(';')
for (let i = 0; i < cookies.length; i++) {
let cookie = jQuery.trim(cookies[i])
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1))
break
}
}
}
return cookieValue
}
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method))
}
$.ajaxSetup({
beforeSend: (xhr, settings) => {
let $captchaMassage = $('#captcha-message')
let csrftoken = getCookie('csrftoken')
console.debug('csrftoken: ' + csrftoken)
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken)
}
if ($captchaMassage) {
$captchaMassage.text('please wait...')
}
}
})
and this is my AJAX:
$.ajax({
async: true,
method: 'POST',
url: '{% url "captcha_mine_taken_verification" %}',
dataType: 'json',
data: {'coinhive_captcha_token': token},
success: result => {
if (result.success) {
console.debug(result.data)
$captchaMassage.text('thanks to donate')
} else {
$captchaMassage.text('failure reason: ' + result.reason)
}
},
error: () => {
$captchaMassage.text('oh my god, something wrong...')
}
})
this is Request Header from Chrome:
POST /cloudsen_blog/captcha-mine/taken-verification HTTP/1.1
Host: 104.243.15.163
Connection: keep-alive
Content-Length: 55
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://104.243.15.163
X-CSRFToken: null <<<< this always null
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://104.243.15.163/cloudsen_blog/blog/article/5
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-TW;q=0.6
this is my Gunicorn config file:
import multiprocessing
# unix domain socket
# bind = 'unix:/home/cloudsen/work/deploy/webservers/sockets/nginx-gunicorn.sock'
# TCP
bind = '127.0.0.1:8000'
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = 'gevent'
errorlog = '/home/cloudsen/work/deploy/webservers/gunicorn/gunicorn.error.log'
accesslog = '/home/cloudsen/work/deploy/webservers/gunicorn/gunicorn.access.log'
proc_name = 'gunicorn_myblog'
this is my Nginx config file:
worker_processes 2;
user cloudsen;
error_log /home/cloudsen/work/deploy/webservers/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
accept_mutex on;
use epoll;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile off;
access_log /home/cloudsen/work/deploy/webservers/nginx/access.log combined;
# Arch Linux start
types_hash_max_size 4096;
server_names_hash_bucket_size 128;
# Arch Linux end
upstream app_server {
#server unix:/home/cloudsen/work/deploy/webservers/sockets/nginx-gunicorn.sock fail_timeout=0;
server 127.0.0.1:8000 fail_timeout=0;
}
server {
listen 80 default_server;
return 444;
}
server {
listen 80;
server_name 104.243.15.163;
client_max_body_size 4G;
keepalive_timeout 5;
location /favicon.ico {
access_log off;
log_not_found off;
}
location /static/ {
alias /home/cloudsen/work/python/project/RedQueen/collected_statics/;
}
location = / {
rewrite ^ /cloudsen_blog;
}
location / {
proxy_pass_header X-CSRFToken;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header Cookie $http_cookie;
proxy_redirect off;
proxy_pass http://app_server;
}
}
}
What's more, when i debug in Chrome, I found that my AJAX didn't send CSRF-TOKEN, and document.cookie always get empty string, so getCookie('csrftoken') always return Null. But i don't know why.
Thanks for help ~~~

Page uses AJAX without any HTML formĀ¶
A page makes a POST request via AJAX, and the page does not have an HTML form with a csrf_token that would cause the required CSRF cookie to be sent.
Solution: use ensure_csrf_cookie() on the view that sends the page.
By use ensure_csrf_cookie() decorator finally solved the problem.
from django.views.decorators.csrf import ensure_csrf_cookie
# use this on your view
#ensure_csrf_cookie
def go_article_detail_page(request: HttpRequest, article_pk: int):
pass
And now, whenever you visit the page, Django will send a csrftoken in document.cookie .

Related

XMLHttpRequest blocked by CORS policy annotation #CrossOrigin

Access to XMLHttpRequest at 'http://localhost:8080/api/auth/signup' from origin 'https://mysuite.ru' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
http.cors().and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/admin").hasRole("ADMIN")
.antMatchers("/").permitAll()
.antMatchers("/favicon.ico").permitAll()
.antMatchers("/static/**").permitAll()
.antMatchers("/manifest.json").permitAll()
.antMatchers("/logo192.png").permitAll()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/test/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
#RestController
#RequestMapping("/api/auth")
#CrossOrigin(origins = "*", maxAge = 3600)
public class AuthController {
Redirecting via nginx
server {
listen 443 ssl;
server_name is my address.ru;
ssl_certificate C:/ssl/ip.crt;
ssl_certificate_key C:/ssl/ip.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://localhost:8080;
proxy_set_header Host $http_host;
proxy_redirect off;
}
}
UPDATE
Request Headers:
Accept: application/json, text/plain, */*
Access-Control-Allow-Headers: DNT,User-Agent,X-Requested-With,If-
Modified-Since,Cache-Control,Content-Type,Range
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Content-Length,Content-Range
Content-Type: application/json;charset=UTF-8
Referer
sec-ch-ua: "Chromium";v="92", " Not A;Brand";v="99", "Yandex";v="21"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/92.0.4515.159 YaBrowser/21.8.3.614 Yowser/2.5
Safari/537.36
Add in nginx:
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
#
# Custom headers and headers various browsers *should* be OK with but aren't
#
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
#
# Tell client that this pre-flight info is valid for 20 days
#
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
}
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_pass http://localhost:8080;
}
No result yet
Try adding the methods attribute to #CrossOrigin. When used to annotate a method, the supported methods are the same as the ones to which the method is mapped, but since you are using it at the class level you might need to specify them as follows:
#CrossOrigin(origins = "*", maxAge = 3600, methods = {RequestMethod.GET, RequestMethod.POST} ) // Just an example
Try replacing your #CrossOrigin annotation with
#CrossOrigin(value = "https://mysuite.ru", allowCredentials = "true")
Don't use "*" as an origin but "https://mysuite.ru".
The problem was that I was mistakenly doing a post request on the front side to localhost

How to configure my NGINX to allow CSRF protection on my Spring Boot application

I am trying to separate my Spring Boot application from my front-end, namely my Angular 7+ application, by using an NGINX reverse proxy.
My Spring Boot application is of version 2.0.3+.RELEASE and has CSRF protection enabled.
My Security configuration looks like the following:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().and()
.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and().csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
My nginx.conf looks like this:
events {
worker_connections 768;
}
http {
# Nginx will handle gzip compression of responses from the app server
gzip on;
gzip_proxied any;
gzip_types text/plain application/json;
gzip_min_length 1000;
server {
listen 80;
# Nginx will reject anything not matching /api
location /api {
# Reject requests with unsupported HTTP method
if ($request_method !~ ^(GET|POST|HEAD|OPTIONS|PUT|DELETE)$) {
return 405;
}
# Only requests matching the whitelist expectations will
# get sent to the application server
proxy_pass http://app:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;
}
location / {
root /var/www/ui;
try_files $uri $uri/ /index.html =404;
index index.html index.htm;
}
}
}
Considering the following Request Header for http://localhost/api/myResource I get a Forbidden message on POST request:
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: it,it-IT;q=0.9,en;q=0.8,it-CH;q=0.7
authorization: Basic
Connection: keep-alive
Content-Length: 94
Content-Type: application/json
Cookie: SESSION=MTdmNGFmODctMTNiMC00YzRjLWJjNTAtYmVlMTgzMzJkZTli; XSRF-TOKEN=fbe30e1e-1f64-4910-9040-799217c59b51
Host: localhost
Origin: http://localhost
Referer: http://localhost/admin/bundles
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
X-Requested-With: XMLHttpRequest
The Spring application logs the following error:
Invalid CSRF token found for http://localhost/api/myResource
NON GET calls should pass in X-XSRF-Token in header when calling backend spring boot server to this explicity ,
#Injectable()
export class CustomInterceptor implements HttpInterceptor {
constructor(private http: Http,private tokenExtractor: HttpXsrfTokenExtractor) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const headerName = 'X-XSRF-TOKEN';
let token = this.tokenExtractor.getToken() as string;
console.log(token)
if (token !== null && !request.headers.has(headerName)) {
request = request.clone({ headers: request.headers.set(headerName, token) });
}

Is it possible to forward a get request using nginx proxy to the same url with port

I have a simple python bottle api, and a nginx server that host the index.html that makes ajax request to the bottle api running on the local host on port 8001. The problem is I don't handle the CORS because its running in a closed env and its just a simple api to manage some active directory stuff and it sits behind a firewall and basic auth. I'm not really worried about cross site scripting. What I need to know is if I program my ajax request from
$.ajax({
type: "GET",
url: "http://localhost:8001/newuser/" + "firstName=" + fname + "&lastName=" + lname + "&email=" + email + "&password=" + new_password,
success: function(data){
alert(data);
document.getElementById("alert").innerHTML = data.toString();
}
});
to
$.ajax({
type: "GET",
url: "/newuser/" + "firstName=" + fname + "&lastName=" + lname + "&email=" + email + "&password=" + new_password,
success: function(data){
alert(data);
document.getElementById("alert").innerHTML = data.toString();
}
});
how do I write the nginx rewrite to take the ajax request and make it goto port 8001 at local host, and if thats even possible. I looked at a few examples but couldn't quite find what I needed.
Can someone help me with the nginx code for this, I need to forward the request to localhost at :8001 and not :80 when /newuser/ is detected.
This is because when I call localhost:8001 it gives me a cors error in the web console.
I tried to disable CORS in nginx
nginx config
location * {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
virtual nginx config
location * {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
#
# Om nom nom cookies
#
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
#
# Custom headers and headers various browsers *should* be OK with but aren't
#
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
#
# Tell client that this pre-flight info is valid for 20 days
#
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
}
Here is my solution I figured it out.
#
# A virtual host using mix of IP-, name-, and port-based configuration
#
upstream admanager.oneplatform.build {
server localhost:8001;
}
server {
listen 80 default_server;
server_name _;
root /opt/admanager1;
index index.html;
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
location /newuser/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://localhost:8001/newuser/;
proxy_ssl_session_reuse off;
proxy_set_header Host $http_host;
proxy_redirect off;
}
location /update/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://localhost:8001/update/;
proxy_ssl_session_reuse off;
proxy_set_header Host $http_host;
proxy_redirect off;
}
}

Golang app behind Nginx reverse proxy won't accept ajax request on firefox due to CORS

So I have a domain name which is a static html file that sends an ajax request to a subdomain app which is behind Nginx in a reverse proxy.
Here is my ajax code:
$(document).ready(function(){
function call() {
$.ajax({
type: "POST",
url: "https://test.example.com/call",
crossDomain: true,
data: $("#form-call").serialize(),
success: function(response) {
$("#response").html(response);
},
error: function() {
alert("error");
}
});
And on Nginx I have:
upstream example {
server 192.168.1.10:6000;
}
server {
listen 443;
server_name test.example.com;
ssl on;
ssl_certificate /etc/nginx/ssl/afs.crt;
ssl_certificate_key /etc/nginx/ssl/afs.key;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Authorization "";
client_max_body_size 0;
chunked_transfer_encoding on;
add_header 'Access-Control-Allow-Origin' "$http_origin";
location / {
proxy_pass https://exapmle;
proxy_read_timeout 900;
}
}
And I have this on my golang app to help with CORS:
func (s *MyServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if origin := req.Header.Get("Origin"); origin != "" {
rw.Header().Set("Access-Control-Allow-Origin", origin)
rw.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
rw.Header().Set("Access-Control-Allow-Headers",
"Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
}
// Stop here if its Preflighted OPTIONS request
if req.Method == "OPTIONS" {
return
}
// Lets Gorilla work
s.r.ServeHTTP(rw, req)
}
It works fine on chrome, but on firefox I get the error:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://test.example.com/call. This can be fixed by moving the resource to the same domain or enabling CORS.
DISCLAIMER: I have just seen how old this post is, I imagine the problem has already been solved.
This worked for me:
func corsHandler(fn http.HandleFunc) http.HandleFunc {
return func(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("Access-Control-Allow-Origin", "*")
fn(rw, req)
}
}
Then when setting up your routers you can:
r := mux.NewRouter()
r.HandleFunc("/<route>", corsHandler(handleRouteMethod)).Methods("<METHOD>")
Slightly different approach but it has definitely worked, so if all else fails, you could give it a try (this assumes you are using gorilla mux, if you are using the httprouter or something else then your corsHandler method maybe needs to use a different function signature).

Can't disable same origin policy on nginx

I need to disable the same origin policy on the server. Just as a background: I have verified that everything is working by starting chrome with the disable web security flag. Everything works as expected.
Here's what I have done on the nginx side:
upstream phpfcgi {
server unix:/var/run/php5-fpm.sock; #for PHP-FPM running on UNIX socket
}
server {
listen 80;
root /var/www/yammi2;
index index.html index.php index.htm;
server_name myserver.ch;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Credentials 'true';
add_header Access-Control-Allow-Headers 'Content-Type,accept,x-wsse,origin';
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS, PUT, DELETE';
# strip app.php/ prefix if it is present
rewrite ^/app\.php/?(.*)$ /$1 permanent;
location / {
index app.php;
try_files $uri #rewriteapp;
}
location #rewriteapp {
rewrite ^(.*)$ /app.php/$1 last;
}
# pass the PHP scripts to FastCGI server from upstream phpfcgi
location ~ ^/(app|app_dev|config)\.php(/|$) {
fastcgi_pass phpfcgi;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTPS off;
}
}
When I then do curl call: curl -I myserver.ch, I get the following result:
HTTP/1.1 302 Found
Server: nginx/1.1.19
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
X-Powered-By: PHP/5.3.10-1ubuntu3.9
Set-Cookie: PHPSESSID=gvcl3v533ib91l2c6v888gl9d3; path=/
cache-control: no-cache
date: Fri, 10 Jan 2014 07:01:18 GMT
location: http://myserver.ch/admin/restaurant
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type,accept,x-wsse,origin
Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE
so at least it seems that the headers are set correctly, yet the result when I make the ajax call:
OPTIONS http://myserver.ch/api/v1/restaurant/closest?max=50&lat=47&lon=8 500 (Internal Server Error) jquery-2.0.3.js:7845
OPTIONS http://myserver.ch/api/v1/restaurant/closest?max=50&lat=47&lon=8 Origin http://localhost is not allowed by Access-Control-Allow-Origin. jquery-2.0.3.js:7845
XMLHttpRequest cannot load http://myserver.ch/api/v1/restaurant/closest?max=50&lat=47&lon=8. Origin http://localhost is not allowed by Access-Control-Allow-Origin. overview.html:1
I'm a bit confused by the "Internal Server Error", but I figured since it works with the flag, this has to be something to do with same origin.
The server application is a symphony app. I hope I haven't missed anything. Any idea how to fix this? Or even how to debug it?
Maybe one last snipped, here is how I make the call (again, shouldn't be the issue, because with the disable security flag it works as expected):
$.ajax({
url: url,
headers: {"x-wsse": getWsseHeader()},
beforeSend: function (request) {
request.setRequestHeader("x-wsse", getWsseHeader());
},
success: function() {
},
error: function(error) {
console.log(error.statusText);
}
});
Change this line
add_header Access-Control-Allow-Origin *;
as
add_header 'Access-Control-Allow-Origin' '';

Resources