CSRF Check fails while using nginx as reverse proxy and basic auth #512

Closed
opened 2026-10-05 04:26:16 +02:00 by adam · 4 comments
Owner

Hey,

I'm using znc 1.6.0 on Debian Jessie, together with nginx 1.6.2 as a reverse proxy.
It works fine, as long as I don't try to change anything ;-)
Whenever I send a form, it fails with:

Access denied: POST requests need to send a secret token to prevent cross-site request forgery attacks.

As far as I can see the reason is in the nginx basic auth that I configured for that website.
The user and password are the same as for webadmin page, which results in me being automatically logged in when entering the credentials for the htaccess auth.

But this seems to lead to a behaviour that every request creates a new session id, which seems to kill the CSRF access token.

I can see the following when running znc -Df

[2015-04-14 17:18:13.334322] _LISTENER == ConnectionFrom(127.0.0.1, 51534) [Allowed]
[2015-04-14 17:18:13.334991] There are [1] clients from [127.0.0.1]
[2015-04-14 17:18:13.335437] =================== CWebSock::OnLogin(), basic auth? true
[2015-04-14 17:18:13.335564] Remote IP: (external IP); discarding session [ce6d19f5c3debd0ae8b4047ea6b00c3a3f7926465942dd68d5d6921bea807b29]
[2015-04-14 17:18:13.335664] Auto generated session: [18bacfc287b33311601ecc14a2141d7535e0c8339ea92ba97b440b30814b7448]
[2015-04-14 17:18:13.335788] Page Request [/znc/pub/default.css]
[2015-04-14 17:18:13.335950] CTemplate::AppendPath(/usr/local/share/znc/webskins/default//) == [/usr/local/share/znc/webskins/default/]
[2015-04-14 17:18:13.336041] CTemplate::AppendPath(/usr/local/share/znc/webskins/default//) == [/usr/local/share/znc/webskins/default/]
[2015-04-14 17:18:13.336135] Found [/usr/local/share/znc/webskins/default/pub/default.css]
[2015-04-14 17:18:13.336203] About to print [/usr/local/share/znc/webskins/default/pub/default.css]
[2015-04-14 17:18:13.336351] - 200 (OK) [text/css; charset=utf-8]
[2015-04-14 17:18:13.337658] - ETag: [] / If-None-Match []
[2015-04-14 17:18:13.337823] Successful login attempt ==> USER [admin] ==> SESSION [18bacfc287b33311601ecc14a2141d7535e0c8339ea92ba97b440b30814b7448]

This keeps going on (especially the "discarding session - generating session" part) for every request that is being made.

When I turn basic auth off, it looks like this:

[2015-04-14 17:18:39.451747] _LISTENER == ConnectionFrom(127.0.0.1, 51545) [Allowed]
[2015-04-14 17:18:39.451820] There are [0] clients from [127.0.0.1]
[2015-04-14 17:18:39.452067] Page Request [/znc/login]
[2015-04-14 17:18:39.452109] Found existing session from cookie: [b576a3461e12f0ad97abb3ca57d18d16dcc5d8775ac7ee456ec9be1d532b6972] IsLoggedIn(false)
[2015-04-14 17:18:39.452163] =================== CWebSock::OnLogin(), basic auth? false
[2015-04-14 17:18:39.452191] - Redirect to [/znc/?cookie_check=true] with prefix [/znc]
[2015-04-14 17:18:39.452217] - 302 (Found) [text/html; charset=utf-8]
[2015-04-14 17:18:39.452413] Successful login attempt ==> USER [xpac] ==> SESSION [b576a3461e12f0ad97abb3ca57d18d16dcc5d8775ac7ee456ec9be1d532b6972]

So, maybe the login-thingie needs to check if a user is already logged in and not recreate the session each time the browser submits the login data because of htaccess.

But this is just my interpretation of what is happening ;-)

Thanks for your great work!

Hey, I'm using znc 1.6.0 on Debian Jessie, together with nginx 1.6.2 as a reverse proxy. It works fine, as long as I don't try to change anything ;-) Whenever I send a form, it fails with: Access denied: POST requests need to send a secret token to prevent cross-site request forgery attacks. As far as I can see the reason is in the nginx basic auth that I configured for that website. The user and password are the same as for webadmin page, which results in me being automatically logged in when entering the credentials for the htaccess auth. But this seems to lead to a behaviour that every request creates a new session id, which seems to kill the CSRF access token. I can see the following when running znc -Df [2015-04-14 17:18:13.334322] _LISTENER == ConnectionFrom(127.0.0.1, 51534) [Allowed] [2015-04-14 17:18:13.334991] There are [1] clients from [127.0.0.1] [2015-04-14 17:18:13.335437] =================== CWebSock::OnLogin(), basic auth? true [2015-04-14 17:18:13.335564] Remote IP: (external IP); discarding session [ce6d19f5c3debd0ae8b4047ea6b00c3a3f7926465942dd68d5d6921bea807b29] [2015-04-14 17:18:13.335664] Auto generated session: [18bacfc287b33311601ecc14a2141d7535e0c8339ea92ba97b440b30814b7448] [2015-04-14 17:18:13.335788] Page Request [/znc/pub/_default_.css] [2015-04-14 17:18:13.335950] CTemplate::AppendPath(/usr/local/share/znc/webskins/_default_//) == [/usr/local/share/znc/webskins/_default_/] [2015-04-14 17:18:13.336041] CTemplate::AppendPath(/usr/local/share/znc/webskins/_default_//) == [/usr/local/share/znc/webskins/_default_/] [2015-04-14 17:18:13.336135] Found [/usr/local/share/znc/webskins/_default_/pub/_default_.css] [2015-04-14 17:18:13.336203] About to print [/usr/local/share/znc/webskins/_default_/pub/_default_.css] [2015-04-14 17:18:13.336351] - 200 (OK) [text/css; charset=utf-8] [2015-04-14 17:18:13.337658] - ETag: [] / If-None-Match [] [2015-04-14 17:18:13.337823] Successful login attempt ==> USER [admin] ==> SESSION [18bacfc287b33311601ecc14a2141d7535e0c8339ea92ba97b440b30814b7448] This keeps going on (especially the "discarding session - generating session" part) for every request that is being made. When I turn basic auth off, it looks like this: [2015-04-14 17:18:39.451747] _LISTENER == ConnectionFrom(127.0.0.1, 51545) [Allowed] [2015-04-14 17:18:39.451820] There are [0] clients from [127.0.0.1] [2015-04-14 17:18:39.452067] Page Request [/znc/login] [2015-04-14 17:18:39.452109] Found existing session from cookie: [b576a3461e12f0ad97abb3ca57d18d16dcc5d8775ac7ee456ec9be1d532b6972] IsLoggedIn(false) [2015-04-14 17:18:39.452163] =================== CWebSock::OnLogin(), basic auth? false [2015-04-14 17:18:39.452191] - Redirect to [/znc/?cookie_check=true] with prefix [/znc] [2015-04-14 17:18:39.452217] - 302 (Found) [text/html; charset=utf-8] [2015-04-14 17:18:39.452413] Successful login attempt ==> USER [xpac] ==> SESSION [b576a3461e12f0ad97abb3ca57d18d16dcc5d8775ac7ee456ec9be1d532b6972] So, maybe the login-thingie needs to check if a user is already logged in and not recreate the session each time the browser submits the login data because of htaccess. But this is just my interpretation of what is happening ;-) Thanks for your great work! <!-- github-sync-id:68414721 -->
adam closed this issue 2026-10-05 04:26:16 +02:00
Author
Owner

Somewhere cookies are being lost.

On every request that you submit, the HTTP reply generated by ZNC sets a "SessionId" cookie. This cookie is getting lost.

When a request comes in without that cookie (for example, your POST requests), a new, random session id is generated and a new cookie is set. The CSRF token will then not match against this new session id.

The "discarding session" is just a hard-coded limit of 5 sessions per ip. When a sixth session is being created, the oldest session gets deleted. So this is orthogonal to the issue that you are seeing.

Either you configured your browser not to accept cookies or nginx is eating the cookies. I don't know how I can help you figuring out the details here, sorry.

Somewhere cookies are being lost. On every request that you submit, the HTTP reply generated by ZNC sets a "SessionId" cookie. This cookie is getting lost. When a request comes in without that cookie (for example, your POST requests), a new, random session id is generated and a new cookie is set. The CSRF token will then not match against this new session id. The "discarding session" is just a hard-coded limit of 5 sessions per ip. When a sixth session is being created, the oldest session gets deleted. So this is orthogonal to the issue that you are seeing. Either you configured your browser not to accept cookies or nginx is eating the cookies. I don't know how I can help you figuring out the details here, sorry. <!-- github-comment-id:93048542 -->
Author
Owner

ZNC knows how to handle proxied requests correctly, but it needs a header or two added to the HTTP requests to do so.

In the location / block for my ZNC reverse proxy, I have nginx set the following options before the proxy_pass to ZNC:

        proxy_http_version 1.1;
        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_set_header   Host             $http_host;
        proxy_set_header   Upgrade          $http_upgrade;
        proxy_redirect     off;

IIRC ZNC cares about the X-Forwarded-For header the most, but the others can't hurt.

ZNC knows how to handle proxied requests correctly, but it needs a header or two added to the HTTP requests to do so. In the `location /` block for my ZNC reverse proxy, I have nginx set the following options before the `proxy_pass` to ZNC: ``` proxy_http_version 1.1; 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_set_header Host $http_host; proxy_set_header Upgrade $http_upgrade; proxy_redirect off; ``` IIRC ZNC cares about the `X-Forwarded-For` header the most, but the others can't hurt. <!-- github-comment-id:93051297 -->
Author
Owner

Just for reference (if this ever turns up in Google etc.)

My nginx config now looks like this:

location /znc/ {
    auth_basic              "znc webadmin";
    auth_basic_user_file    /var/www/.htaccess/.htpasswd.znc;

    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_set_header        Host            $http_host;
    proxy_set_header        Upgrade         $http_upgrade;
    proxy_pass_header       Set-Cookie;
    proxy_pass              http://127.0.0.1:48668/znc/;
}

This works fine for me, with auth enabled.

I don't know if I was missing a specific parameter or it was more a browser caching issue, but at least now it works and I'm too lazy to find out ;)

Thanks for your support!

This can be closed. :)

Just for reference (if this ever turns up in Google etc.) My nginx config now looks like this: ``` location /znc/ { auth_basic "znc webadmin"; auth_basic_user_file /var/www/.htaccess/.htpasswd.znc; 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_set_header Host $http_host; proxy_set_header Upgrade $http_upgrade; proxy_pass_header Set-Cookie; proxy_pass http://127.0.0.1:48668/znc/; } ``` This works fine for me, with auth enabled. I don't know if I was missing a specific parameter or it was more a browser caching issue, but at least now it works and I'm too lazy to find out ;) Thanks for your support! This can be closed. :) <!-- github-comment-id:93324862 -->
Author
Owner

When a request comes in without that cookie (for example, your POST requests)

POSTs come with cookies just fine.

Nginx forwards Authorization header to ZNC, right? If so, reverse proxy doesn't really matter.
ZNC supports HTTP-Basic auth, and authenticates user when receives Authorization header.
While processing Authorization header, it checks for the session from cookie, and creates the session if there was no found.
If Authorization header came before Cookie header, ZNC didn't see the cookie yet, and creates the new one.
If Cookie header came before Authorization, it works.

> When a request comes in without that cookie (for example, your POST requests) POSTs come with cookies just fine. Nginx forwards Authorization header to ZNC, right? If so, reverse proxy doesn't really matter. ZNC supports HTTP-Basic auth, and authenticates user when receives Authorization header. While processing Authorization header, it checks for the session from cookie, and creates the session if there was no found. If Authorization header came _before_ Cookie header, ZNC didn't see the cookie yet, and creates the new one. If Cookie header came before Authorization, it works. <!-- github-comment-id:93599445 -->
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: adam/znc#512