CHTTPSock::PrintHeader: emit standard hardening response headers
PrintHeader doesn't set Cache-Control, X-Frame-Options, X-Content-Type-Options, or Referrer-Policy for any response. The static
file path adds Cache-Control: public (which is also wrong direction for
authenticated content) but everything else goes out bare.
No X-Frame-Options / frame-ancestors → webadmin pages framable. CSRF
is enforced server-side (WebModules.cpp:661-669), but the admin's
browser still carries the cookie + token, so clickjacking that triggers
a state-changing form submit will pass the token check.
No X-Content-Type-Options: nosniff → endpoints that serve text/plain
(samplewebapi, any module that does WebSock.Write(text) with Content-Type: text/plain) are MIME-sniffed by old browsers and can
yield reflected XSS.
No Cache-Control on authenticated HTML → previously rendered admin
pages can be reread from browser history on a shared workstation.
Also at line 378, change Cache-Control: public for static files to Cache-Control: private, max-age=... so shared HTTP caches don't fan out
skin/avatar resources across users.
Happy to PR. Easy to test with curl -i.
# `CHTTPSock::PrintHeader`: emit standard hardening response headers
`PrintHeader` doesn't set `Cache-Control`, `X-Frame-Options`,
`X-Content-Type-Options`, or `Referrer-Policy` for any response. The static
file path adds `Cache-Control: public` (which is also wrong direction for
authenticated content) but everything else goes out bare.
```cpp
// src/HTTPSock.cpp
716: bool CHTTPSock::PrintHeader(off_t uContentLength, const CString& sContentType,
unsigned int uStatusId, const CString& sStatusMsg) {
...
734: Write("HTTP/" + ... + "\r\n");
735: Write("Date: " + GetDate() + "\r\n");
736: Write("Server: " + CZNC::GetTag(false) + "\r\n");
737: if (uContentLength > 0) Write("Content-Length: ...");
741: Write("Content-Type: " + m_sContentType + "\r\n");
742: for (Set-Cookie: ...) // SameSite=Strict; HttpOnly; Secure-when-SSL
749: for (auto& it : m_msHeaders) ...
753: Write("Connection: Close\r\n");
755: Write("\r\n");
```
Concrete consequences:
- No `X-Frame-Options` / `frame-ancestors` → webadmin pages framable. CSRF
is enforced server-side (`WebModules.cpp:661-669`), but the admin's
browser still carries the cookie + token, so clickjacking that triggers
a state-changing form submit will pass the token check.
- No `X-Content-Type-Options: nosniff` → endpoints that serve `text/plain`
(`samplewebapi`, any module that does `WebSock.Write(text)` with
`Content-Type: text/plain`) are MIME-sniffed by old browsers and can
yield reflected XSS.
- No `Cache-Control` on authenticated HTML → previously rendered admin
pages can be reread from browser history on a shared workstation.
## Fix
In `PrintHeader`, before the trailing `\r\n`:
```cpp
Write("X-Frame-Options: SAMEORIGIN\r\n");
Write("X-Content-Type-Options: nosniff\r\n");
Write("Referrer-Policy: same-origin\r\n");
if (uStatusId != 304 && !m_sContentType.StartsWith("image/") &&
!m_sContentType.StartsWith("font/") && !m_sContentType.StartsWith("text/css")) {
Write("Cache-Control: no-store, no-cache, must-revalidate, max-age=0\r\n");
Write("Pragma: no-cache\r\n");
}
```
Also at line 378, change `Cache-Control: public` for static files to
`Cache-Control: private, max-age=...` so shared HTTP caches don't fan out
skin/avatar resources across users.
Happy to PR. Easy to test with `curl -i`.
<!-- github-sync-id:4326794263 -->
Sorry, this was an accidental duplicate submission on my side. #2012 has the canonical content, please track that one and feel free to close this.
Sorry, this was an accidental duplicate submission on my side. #2012 has the canonical content, please track that one and feel free to close this.
<!-- github-comment-id:4318627546 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
CHTTPSock::PrintHeader: emit standard hardening response headersPrintHeaderdoesn't setCache-Control,X-Frame-Options,X-Content-Type-Options, orReferrer-Policyfor any response. The staticfile path adds
Cache-Control: public(which is also wrong direction forauthenticated content) but everything else goes out bare.
Concrete consequences:
X-Frame-Options/frame-ancestors→ webadmin pages framable. CSRFis enforced server-side (
WebModules.cpp:661-669), but the admin'sbrowser still carries the cookie + token, so clickjacking that triggers
a state-changing form submit will pass the token check.
X-Content-Type-Options: nosniff→ endpoints that servetext/plain(
samplewebapi, any module that doesWebSock.Write(text)withContent-Type: text/plain) are MIME-sniffed by old browsers and canyield reflected XSS.
Cache-Controlon authenticated HTML → previously rendered adminpages can be reread from browser history on a shared workstation.
Fix
In
PrintHeader, before the trailing\r\n:Also at line 378, change
Cache-Control: publicfor static files toCache-Control: private, max-age=...so shared HTTP caches don't fan outskin/avatar resources across users.
Happy to PR. Easy to test with
curl -i.#2012 ?
Sorry, this was an accidental duplicate submission on my side. #2012 has the canonical content, please track that one and feel free to close this.