nextcloud / server

☁️ Nextcloud server, a safe home for all your data
https://nextcloud.com
GNU Affero General Public License v3.0
26.25k stars 3.95k forks source link

Access forbidden: CSRF check failed on logout #17065

Closed alvvdc closed 3 years ago

alvvdc commented 4 years ago

Steps to reproduce

  1. Install Lighttpd and PHP.
  2. Download Nextcloud server ZIP.
  3. Log in Nextcloud, and when you log out, in the most cases you get the next error:

Access forbidden CSRF check failed

Screenshot at 2019-09-09 15-18-28

Expected behaviour

Log out succesfully.

Actual behaviour

When I try logout, I get the telled error. Then, if I refresh the website the session is still active.

In Network Firefox explorer (from F12) the logout request get a 412 status code:

Screenshot at 2019-09-09 15-27-30

This error is with Lighttpd web service, with Nginx works fine.

Server configuration

Operating system: Debian / Raspbian Web server: Lighttpd Database: MariaDB PHP version: PHP 7.3 Nextcloud version: (see Nextcloud admin page) 16.0.4 Updated from an older Nextcloud/ownCloud or fresh install: Fresh install Where did you install Nextcloud from: https://download.nextcloud.com/server/releases/nextcloud-16.0.4.zip

kesselb commented 4 years ago

Anything in the logs?

alvvdc commented 4 years ago

Anything in the logs?

Nothing in Nextcloud logs with debug 0 level. Nothing in PHP logs. In Lighttpd:

192.168.1.8 192.168.1.29 - [09/Sep/2019:15:08:08 +0100] "GET /nextcloud/index.php/logout?requesttoken=FfsiGZcBHPF%2BqSnsIhrlZNOXbAr%2F682WV718xe7WqfI%3D%3AfI1VL8ZUKLY4hlqKbG2OIpWlLlO6qJuiP9kT9qfn7MY%3D HTTP/1.1" 412 11746 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0"

UweDoe commented 4 years ago

Hi, I have the same problem running nextcloud on a rasberry PI3 using DietPi/Nextcloud/Lighttpd and reproduced as well running this system in a VM using virtual box. Setting the logging of nextcloud to debug I get:

[core] Debug: OC\AppFramework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException: CSRF check failed at <>

  1. /var/www/nextcloud/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php line 95 OC\AppFramework\Middleware\Security\SecurityMiddleware->beforeController(OC\Core\Controller\LoginController {}, "logout")
  2. /var/www/nextcloud/lib/private/AppFramework/Http/Dispatcher.php line 97 OC\AppFramework\Middleware\MiddlewareDispatcher->beforeController(OC\Core\Controller\LoginController {}, "logout")
  3. /var/www/nextcloud/lib/private/AppFramework/App.php line 126 OC\AppFramework\Http\Dispatcher->dispatch(OC\Core\Controller\LoginController {}, "logout")
  4. /var/www/nextcloud/lib/private/AppFramework/Routing/RouteActionHandler.php line 47 OC\AppFramework\App::main("OC\Core\Controller\LoginController", "logout", OC\AppFramework\ ... {}, {_route: "core.login.logout"})
  5. <> OC\AppFramework\Routing\RouteActionHandler->__invoke({_route: "core.login.logout"})
  6. /var/www/nextcloud/lib/private/Route/Router.php line 297 undefinedundefinedcall_user_func(OC\AppFramework\ ... {}, {_route: "core.login.logout"})
  7. /var/www/nextcloud/lib/base.php line 975 OC\Route\Router->match("/logout")
  8. /var/www/nextcloud/index.php line 42 OC::handleRequest()

GET /nextcloud/index.php/logout?requesttoken=DTGY0BwWUvscxsw7frSaWhfTaRezU1t9SeeJGGXoF%2BQ%3D%3AfXqpk1NEZqNyrr9YDPL5KFWQEFLhZTIuI9%2FmQRCQZIE%3D from 192.168.16.37 by admin at 2019-09-23T14:33:41+00:00

Nextcloud Version: 16.0.4.1 Installierte Apps: 36 Apps mit verfügbaren Aktualisierungen: 1

PHP Version: 7.3.9 Arbeitspeicher-Grenzwert: 128 MB Maximale Ausführungszeit: 3600 Maximale Größe zum Hochladen: 8388608 TB

Datenbank Art: mysql Version: 10.3.17 Größe: 1,3 MB

UweDoe commented 4 years ago

The bug is easy to reproduce. Tell me, if more traces are needed. I can deliver them quickly. As it is now, the combination Nextcloud/Lighttpd can't be used productively. If you want to reproduce it by yourself do the following steps: 1) Install virtual box https://www.virtualbox.org/ 2) Download DietPi for Virtual Box and install it https://dietpi.com/ 3) start dietpi-software and install nextcloud 4) start browser on host system and login to nextcloud page of guest system 5) logout nextcloud in browser The fault shows up extremly often. Typically you need 5 faulty logout trys until you have a successful one.

kesselb commented 4 years ago
1. Install virtual box https://www.virtualbox.org/

2. Download DietPi for Virtual Box and install it https://dietpi.com/

3. start dietpi-software and install nextcloud

4. start browser on host system and login to nextcloud page of guest system

5. logout nextcloud in browser
   The fault shows up extremly often. Typically you need 5 faulty logout trys until you have a successful one.

Logout works in Chromium and Firefox.

kesselb commented 4 years ago

cc @MichaIng any idea?

kesselb commented 4 years ago

OK. I can reproduce it somehow. Like you said login, upload some files, delete some files or do something else and try to logout afterwards.

        public function isTokenValid(CsrfToken $token): bool {
                if(!$this->sessionStorage->hasToken()) {
                        return false;
                }

                $isEqual = hash_equals($this->sessionStorage->getToken(), $token->getDecryptedValue());
                if (!$isEqual) {
                        \OC::$server->getLogger()->warning('going to validate csrf token "' . $this->sessionStorage->getToken() . '" with "' . $token->getDecryptedValue() . '"');
                }

                return $isEqual;
        }

Added some logging to isTokenValid and ...

image

cc @rullzer @ChristophWurst :confused:

ChristophWurst commented 4 years ago

Could you add some logging to \OC\Security\CSRF\CsrfToken::getDecryptedValue so we see the values of the individual parts?

MichaIng commented 4 years ago

I was able to replicate as well on fresh NC17 installed on DietPi Buster VirtualBox image. @kesselb you tested on DietPi as well or another system? Just in case our default Lighttpd config on Debian Buster based images:

lighttpd -pf /etc/lighttpd/lighttpd.conf ``` config { var.PID = 18245 var.CWD = "/root" mimetype.assign = ( ".pcf.Z" => "application/x-font-pcf", ".tar.bz2" => "application/x-gtar-compressed", ".tar.gz" => "application/x-gtar-compressed", ".ez" => "application/andrew-inset", ".anx" => "application/annodex", # 5 ".atom" => "application/atom+xml", ".atomcat" => "application/atomcat+xml", ".atomsrv" => "application/atomserv+xml", ".lin" => "application/bbolin", ".cu" => "application/cu-seeme", # 10 ".davmount" => "application/davmount+xml", ".dcm" => "application/dicom", ".tsp" => "application/dsptype", ".es" => "application/ecmascript", ".epub" => "application/epub+zip", # 15 ".pfr" => "application/font-tdpfr", ".spl" => "application/futuresplash", ".gz" => "application/gzip", ".hta" => "application/hta", ".jar" => "application/java-archive", # 20 ".ser" => "application/java-serialized-object", ".class" => "application/java-vm", ".js" => "application/javascript", ".json" => "application/json", ".m3g" => "application/m3g", # 25 ".hqx" => "application/mac-binhex40", ".cpt" => "application/mac-compactpro", ".nb" => "application/mathematica", ".nbp" => "application/mathematica", ".mbox" => "application/mbox", # 30 ".mdb" => "application/msaccess", ".doc" => "application/msword", ".dot" => "application/msword", ".mxf" => "application/mxf", ".asn" => "application/octet-stream", # 35 ".bin" => "application/octet-stream", ".deploy" => "application/octet-stream", ".ent" => "application/octet-stream", ".msp" => "application/octet-stream", ".msu" => "application/octet-stream", # 40 ".oda" => "application/oda", ".opf" => "application/oebps-package+xml", ".ogx" => "application/ogg", ".one" => "application/onenote", ".onepkg" => "application/onenote", # 45 ".onetmp" => "application/onenote", ".onetoc2" => "application/onenote", ".pdf" => "application/pdf", ".pgp" => "application/pgp-encrypted", ".key" => "application/pgp-keys", # 50 ".sig" => "application/pgp-signature", ".prf" => "application/pics-rules", ".ai" => "application/postscript", ".eps" => "application/postscript", ".eps2" => "application/postscript", # 55 ".eps3" => "application/postscript", ".epsf" => "application/postscript", ".epsi" => "application/postscript", ".ps" => "application/postscript", ".rar" => "application/rar", # 60 ".rdf" => "application/rdf+xml", ".rtf" => "application/rtf", ".stl" => "application/sla", ".smi" => "application/smil+xml", ".smil" => "application/smil+xml", # 65 ".wasm" => "application/wasm", ".xht" => "application/xhtml+xml", ".xhtml" => "application/xhtml+xml", ".xml" => "application/xml", ".xsd" => "application/xml", # 70 ".dtd" => "application/xml-dtd", ".xsl" => "application/xslt+xml", ".xslt" => "application/xslt+xml", ".xspf" => "application/xspf+xml", ".zip" => "application/zip", # 75 ".apk" => "application/vnd.android.package-archive", ".cdy" => "application/vnd.cinderella", ".ddeb" => "application/vnd.debian.binary-package", ".deb" => "application/vnd.debian.binary-package", ".udeb" => "application/vnd.debian.binary-package", # 80 ".sfd" => "application/vnd.font-fontforge-sfd", ".kml" => "application/vnd.google-earth.kml+xml", ".kmz" => "application/vnd.google-earth.kmz", ".xul" => "application/vnd.mozilla.xul+xml", ".xlb" => "application/vnd.ms-excel", # 85 ".xls" => "application/vnd.ms-excel", ".xlt" => "application/vnd.ms-excel", ".xlam" => "application/vnd.ms-excel.addin.macroEnabled.12", ".xlsb" => "application/vnd.ms-excel.sheet.binary.macroEnabled.12", ".xlsm" => "application/vnd.ms-excel.sheet.macroEnabled.12", # 90 ".xltm" => "application/vnd.ms-excel.template.macroEnabled.12", ".eot" => "application/vnd.ms-fontobject", ".thmx" => "application/vnd.ms-officetheme", ".cat" => "application/vnd.ms-pki.seccat", ".pps" => "application/vnd.ms-powerpoint", # 95 ".ppt" => "application/vnd.ms-powerpoint", ".ppam" => "application/vnd.ms-powerpoint.addin.macroEnabled.12", ".pptm" => "application/vnd.ms-powerpoint.presentation.macroEnabled.12", ".sldm" => "application/vnd.ms-powerpoint.slide.macroEnabled.12", ".ppsm" => "application/vnd.ms-powerpoint.slideshow.macroEnabled.12", # 100 ".potm" => "application/vnd.ms-powerpoint.template.macroEnabled.12", ".docm" => "application/vnd.ms-word.document.macroEnabled.12", ".dotm" => "application/vnd.ms-word.template.macroEnabled.12", ".odc" => "application/vnd.oasis.opendocument.chart", ".odb" => "application/vnd.oasis.opendocument.database", # 105 ".odf" => "application/vnd.oasis.opendocument.formula", ".odg" => "application/vnd.oasis.opendocument.graphics", ".otg" => "application/vnd.oasis.opendocument.graphics-template", ".odi" => "application/vnd.oasis.opendocument.image", ".odp" => "application/vnd.oasis.opendocument.presentation", # 110 ".otp" => "application/vnd.oasis.opendocument.presentation-template", ".ods" => "application/vnd.oasis.opendocument.spreadsheet", ".ots" => "application/vnd.oasis.opendocument.spreadsheet-template", ".odt" => "application/vnd.oasis.opendocument.text", ".odm" => "application/vnd.oasis.opendocument.text-master", # 115 ".ott" => "application/vnd.oasis.opendocument.text-template", ".oth" => "application/vnd.oasis.opendocument.text-web", ".pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation", ".sldx" => "application/vnd.openxmlformats-officedocument.presentationml.slide", ".ppsx" => "application/vnd.openxmlformats-officedocument.presentationml.slideshow", # 120 ".potx" => "application/vnd.openxmlformats-officedocument.presentationml.template", ".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ".xltx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.template", ".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document", ".dotx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.template", # 125 ".cod" => "application/vnd.rim.cod", ".mmf" => "application/vnd.smaf", ".sdc" => "application/vnd.stardivision.calc", ".sds" => "application/vnd.stardivision.chart", ".sda" => "application/vnd.stardivision.draw", # 130 ".sdd" => "application/vnd.stardivision.impress", ".sdf" => "application/vnd.stardivision.math", ".sdw" => "application/vnd.stardivision.writer", ".sgl" => "application/vnd.stardivision.writer-global", ".sxc" => "application/vnd.sun.xml.calc", # 135 ".stc" => "application/vnd.sun.xml.calc.template", ".sxd" => "application/vnd.sun.xml.draw", ".std" => "application/vnd.sun.xml.draw.template", ".sxi" => "application/vnd.sun.xml.impress", ".sti" => "application/vnd.sun.xml.impress.template", # 140 ".sxm" => "application/vnd.sun.xml.math", ".sxw" => "application/vnd.sun.xml.writer", ".sxg" => "application/vnd.sun.xml.writer.global", ".stw" => "application/vnd.sun.xml.writer.template", ".sis" => "application/vnd.symbian.install", # 145 ".cap" => "application/vnd.tcpdump.pcap", ".pcap" => "application/vnd.tcpdump.pcap", ".vsd" => "application/vnd.visio", ".vss" => "application/vnd.visio", ".vst" => "application/vnd.visio", # 150 ".vsw" => "application/vnd.visio", ".wbxml" => "application/vnd.wap.wbxml", ".wmlc" => "application/vnd.wap.wmlc", ".wmlsc" => "application/vnd.wap.wmlscriptc", ".wpd" => "application/vnd.wordperfect", # 155 ".wp5" => "application/vnd.wordperfect5.1", ".wk" => "application/x-123", ".7z" => "application/x-7z-compressed", ".abw" => "application/x-abiword", ".dmg" => "application/x-apple-diskimage", # 160 ".bcpio" => "application/x-bcpio", ".torrent" => "application/x-bittorrent", ".bz2" => "application/x-bzip", ".cab" => "application/x-cab", ".cbr" => "application/x-cbr", # 165 ".cbz" => "application/x-cbz", ".cda" => "application/x-cdf", ".cdf" => "application/x-cdf", ".vcd" => "application/x-cdlink", ".pgn" => "application/x-chess-pgn", # 170 ".mph" => "application/x-comsol", ".cpio" => "application/x-cpio", ".dcr" => "application/x-director", ".dir" => "application/x-director", ".dxr" => "application/x-director", # 175 ".dms" => "application/x-dms", ".wad" => "application/x-doom", ".dvi" => "application/x-dvi", ".gsf" => "application/x-font", ".pfa" => "application/x-font", # 180 ".pfb" => "application/x-font", ".pcf" => "application/x-font-pcf", ".mm" => "application/x-freemind", ".gan" => "application/x-ganttproject", ".gnumeric" => "application/x-gnumeric", # 185 ".sgf" => "application/x-go-sgf", ".gcf" => "application/x-graphing-calculator", ".gtar" => "application/x-gtar", ".taz" => "application/x-gtar-compressed", ".tbz" => "application/x-gtar-compressed", # 190 ".tgz" => "application/x-gtar-compressed", ".hdf" => "application/x-hdf", ".hwp" => "application/x-hwp", ".ica" => "application/x-ica", ".info" => "application/x-info", # 195 ".ins" => "application/x-internet-signup", ".isp" => "application/x-internet-signup", ".iii" => "application/x-iphone", ".iso" => "application/x-iso9660-image", ".jam" => "application/x-jam", # 200 ".jnlp" => "application/x-java-jnlp-file", ".jmz" => "application/x-jmol", ".chrt" => "application/x-kchart", ".kil" => "application/x-killustrator", ".skd" => "application/x-koan", # 205 ".skm" => "application/x-koan", ".skp" => "application/x-koan", ".skt" => "application/x-koan", ".kpr" => "application/x-kpresenter", ".kpt" => "application/x-kpresenter", # 210 ".ksp" => "application/x-kspread", ".kwd" => "application/x-kword", ".kwt" => "application/x-kword", ".latex" => "application/x-latex", ".lha" => "application/x-lha", # 215 ".lyx" => "application/x-lyx", ".lzh" => "application/x-lzh", ".lzx" => "application/x-lzx", ".book" => "application/x-maker", ".fb" => "application/x-maker", # 220 ".fbdoc" => "application/x-maker", ".fm" => "application/x-maker", ".frame" => "application/x-maker", ".frm" => "application/x-maker", ".maker" => "application/x-maker", # 225 ".mif" => "application/x-mif", ".m3u8" => "application/x-mpegURL", ".application" => "application/x-ms-application", ".manifest" => "application/x-ms-manifest", ".wmd" => "application/x-ms-wmd", # 230 ".wmz" => "application/x-ms-wmz", ".bat" => "application/x-msdos-program", ".com" => "application/x-msdos-program", ".dll" => "application/x-msdos-program", ".exe" => "application/x-msdos-program", # 235 ".msi" => "application/x-msi", ".nc" => "application/x-netcdf", ".pac" => "application/x-ns-proxy-autoconfig", ".nwc" => "application/x-nwc", ".o" => "application/x-object", # 240 ".oza" => "application/x-oz-application", ".p7r" => "application/x-pkcs7-certreqresp", ".crl" => "application/x-pkcs7-crl", ".pyc" => "application/x-python-code", ".pyo" => "application/x-python-code", # 245 ".qgs" => "application/x-qgis", ".shp" => "application/x-qgis", ".shx" => "application/x-qgis", ".qtl" => "application/x-quicktimeplayer", ".rdp" => "application/x-rdp", # 250 ".rpm" => "application/x-redhat-package-manager", ".rss" => "application/x-rss+xml", ".rb" => "application/x-ruby", ".sce" => "application/x-scilab", ".sci" => "application/x-scilab", # 255 ".xcos" => "application/x-scilab-xcos", ".shar" => "application/x-shar", ".swf" => "application/x-shockwave-flash", ".swfl" => "application/x-shockwave-flash", ".scr" => "application/x-silverlight", # 260 ".sql" => "application/x-sql", ".sit" => "application/x-stuffit", ".sitx" => "application/x-stuffit", ".sv4cpio" => "application/x-sv4cpio", ".sv4crc" => "application/x-sv4crc", # 265 ".tar" => "application/x-tar", ".gf" => "application/x-tex-gf", ".pk" => "application/x-tex-pk", ".texi" => "application/x-texinfo", ".texinfo" => "application/x-texinfo", # 270 ".roff" => "application/x-troff", ".t" => "application/x-troff", ".tr" => "application/x-troff", ".man" => "application/x-troff-man", ".me" => "application/x-troff-me", # 275 ".ms" => "application/x-troff-ms", ".ustar" => "application/x-ustar", ".src" => "application/x-wais-source", ".wz" => "application/x-wingz", ".crt" => "application/x-x509-ca-cert", # 280 ".xcf" => "application/x-xcf", ".fig" => "application/x-xfig", ".xpi" => "application/x-xpinstall", ".xz" => "application/x-xz", ".amr" => "audio/amr", # 285 ".awb" => "audio/amr-wb", ".axa" => "audio/annodex", ".au" => "audio/basic", ".snd" => "audio/basic", ".csd" => "audio/csound", # 290 ".orc" => "audio/csound", ".sco" => "audio/csound", ".flac" => "audio/flac", ".kar" => "audio/midi", ".mid" => "audio/midi", # 295 ".midi" => "audio/midi", ".m4a" => "audio/mpeg", ".mp2" => "audio/mpeg", ".mp3" => "audio/mpeg", ".mpega" => "audio/mpeg", # 300 ".mpga" => "audio/mpeg", ".m3u" => "audio/mpegurl", ".oga" => "audio/ogg", ".ogg" => "audio/ogg", ".opus" => "audio/ogg", # 305 ".spx" => "audio/ogg", ".sid" => "audio/prs.sid", ".aif" => "audio/x-aiff", ".aifc" => "audio/x-aiff", ".aiff" => "audio/x-aiff", # 310 ".gsm" => "audio/x-gsm", ".wax" => "audio/x-ms-wax", ".wma" => "audio/x-ms-wma", ".ra" => "audio/x-realaudio", ".ram" => "audio/x-realaudio", # 315 ".rm" => "audio/x-realaudio", ".pls" => "audio/x-scpls", ".sd2" => "audio/x-sd2", ".wav" => "audio/x-wav", ".alc" => "chemical/x-alchemy", # 320 ".cac" => "chemical/x-cache", ".cache" => "chemical/x-cache", ".csf" => "chemical/x-cache-csf", ".cascii" => "chemical/x-cactvs-binary", ".cbin" => "chemical/x-cactvs-binary", # 325 ".ctab" => "chemical/x-cactvs-binary", ".cdx" => "chemical/x-cdx", ".cer" => "chemical/x-cerius", ".c3d" => "chemical/x-chem3d", ".chm" => "chemical/x-chemdraw", # 330 ".cif" => "chemical/x-cif", ".cmdf" => "chemical/x-cmdf", ".cml" => "chemical/x-cml", ".cpa" => "chemical/x-compass", ".bsd" => "chemical/x-crossfire", # 335 ".csm" => "chemical/x-csml", ".csml" => "chemical/x-csml", ".ctx" => "chemical/x-ctx", ".cef" => "chemical/x-cxf", ".cxf" => "chemical/x-cxf", # 340 ".emb" => "chemical/x-embl-dl-nucleotide", ".embl" => "chemical/x-embl-dl-nucleotide", ".spc" => "chemical/x-galactic-spc", ".gam" => "chemical/x-gamess-input", ".gamin" => "chemical/x-gamess-input", # 345 ".inp" => "chemical/x-gamess-input", ".fch" => "chemical/x-gaussian-checkpoint", ".fchk" => "chemical/x-gaussian-checkpoint", ".cub" => "chemical/x-gaussian-cube", ".gau" => "chemical/x-gaussian-input", # 350 ".gjc" => "chemical/x-gaussian-input", ".gjf" => "chemical/x-gaussian-input", ".gal" => "chemical/x-gaussian-log", ".gcg" => "chemical/x-gcg8-sequence", ".gen" => "chemical/x-genbank", # 355 ".hin" => "chemical/x-hin", ".ist" => "chemical/x-isostar", ".istr" => "chemical/x-isostar", ".dx" => "chemical/x-jcamp-dx", ".jdx" => "chemical/x-jcamp-dx", # 360 ".kin" => "chemical/x-kinemage", ".mcm" => "chemical/x-macmolecule", ".mmd" => "chemical/x-macromodel-input", ".mmod" => "chemical/x-macromodel-input", ".mol" => "chemical/x-mdl-molfile", # 365 ".rd" => "chemical/x-mdl-rdfile", ".rxn" => "chemical/x-mdl-rxnfile", ".sd" => "chemical/x-mdl-sdfile", ".tgf" => "chemical/x-mdl-tgf", ".mcif" => "chemical/x-mmcif", # 370 ".mol2" => "chemical/x-mol2", ".b" => "chemical/x-molconn-Z", ".gpt" => "chemical/x-mopac-graph", ".mop" => "chemical/x-mopac-input", ".mopcrt" => "chemical/x-mopac-input", # 375 ".mpc" => "chemical/x-mopac-input", ".zmt" => "chemical/x-mopac-input", ".moo" => "chemical/x-mopac-out", ".mvb" => "chemical/x-mopac-vib", ".prt" => "chemical/x-ncbi-asn1-ascii", # 380 ".aso" => "chemical/x-ncbi-asn1-binary", ".val" => "chemical/x-ncbi-asn1-binary", ".pdb" => "chemical/x-pdb", ".ros" => "chemical/x-rosdal", ".sw" => "chemical/x-swissprot", # 385 ".vms" => "chemical/x-vamas-iso14976", ".vmd" => "chemical/x-vmd", ".xtel" => "chemical/x-xtel", ".xyz" => "chemical/x-xyz", ".ttc" => "font/collection", # 390 ".otf" => "font/ttf", ".ttf" => "font/ttf", ".woff" => "font/woff", ".woff2" => "font/woff2", ".gif" => "image/gif", # 395 ".ief" => "image/ief", ".jp2" => "image/jp2", ".jpg2" => "image/jp2", ".jpe" => "image/jpeg", ".jpeg" => "image/jpeg", # 400 ".jpg" => "image/jpeg", ".jpm" => "image/jpm", ".jpf" => "image/jpx", ".jpx" => "image/jpx", ".pcx" => "image/pcx", # 405 ".png" => "image/png", ".svg" => "image/svg+xml", ".svgz" => "image/svg+xml", ".tif" => "image/tiff", ".tiff" => "image/tiff", # 410 ".djv" => "image/vnd.djvu", ".djvu" => "image/vnd.djvu", ".ico" => "image/vnd.microsoft.icon", ".wbmp" => "image/vnd.wap.wbmp", ".cr2" => "image/x-canon-cr2", # 415 ".crw" => "image/x-canon-crw", ".ras" => "image/x-cmu-raster", ".cdr" => "image/x-coreldraw", ".pat" => "image/x-coreldrawpattern", ".cdt" => "image/x-coreldrawtemplate", # 420 ".erf" => "image/x-epson-erf", ".art" => "image/x-jg", ".jng" => "image/x-jng", ".bmp" => "image/x-ms-bmp", ".nef" => "image/x-nikon-nef", # 425 ".orf" => "image/x-olympus-orf", ".psd" => "image/x-photoshop", ".pnm" => "image/x-portable-anymap", ".pbm" => "image/x-portable-bitmap", ".pgm" => "image/x-portable-graymap", # 430 ".ppm" => "image/x-portable-pixmap", ".rgb" => "image/x-rgb", ".xbm" => "image/x-xbitmap", ".xpm" => "image/x-xpixmap", ".xwd" => "image/x-xwindowdump", # 435 ".eml" => "message/rfc822", ".iges" => "model/iges", ".igs" => "model/iges", ".mesh" => "model/mesh", ".msh" => "model/mesh", # 440 ".silo" => "model/mesh", ".vrml" => "model/vrml", ".wrl" => "model/vrml", ".x3db" => "model/x3d+binary", ".x3dv" => "model/x3d+vrml", # 445 ".x3d" => "model/x3d+xml", ".appcache" => "text/cache-manifest", ".ics" => "text/calendar", ".icz" => "text/calendar", ".css" => "text/css; charset=utf-8", # 450 ".csv" => "text/csv; charset=utf-8", ".323" => "text/h323", ".htm" => "text/html", ".html" => "text/html", ".shtml" => "text/html", # 455 ".uls" => "text/iuls", ".markdown" => "text/markdown; charset=utf-8", ".md" => "text/markdown; charset=utf-8", ".mml" => "text/mathml", ".asc" => "text/plain; charset=utf-8", # 460 ".brf" => "text/plain; charset=utf-8", ".conf" => "text/plain; charset=utf-8", ".log" => "text/plain; charset=utf-8", ".pot" => "text/plain; charset=utf-8", ".spec" => "text/plain; charset=utf-8", # 465 ".srt" => "text/plain; charset=utf-8", ".text" => "text/plain; charset=utf-8", ".txt" => "text/plain; charset=utf-8", ".rtx" => "text/richtext", ".sct" => "text/scriptlet", # 470 ".wsc" => "text/scriptlet", ".tsv" => "text/tab-separated-values", ".tm" => "text/texmacs", ".ttl" => "text/turtle", ".vcard" => "text/vcard", # 475 ".vcf" => "text/vcard", ".jad" => "text/vnd.sun.j2me.app-descriptor", ".wml" => "text/vnd.wap.wml", ".wmls" => "text/vnd.wap.wmlscript", ".bib" => "text/x-bibtex; charset=utf-8", # 480 ".boo" => "text/x-boo; charset=utf-8", ".h++" => "text/x-c++hdr; charset=utf-8", ".hh" => "text/x-c++hdr; charset=utf-8", ".hpp" => "text/x-c++hdr; charset=utf-8", ".hxx" => "text/x-c++hdr; charset=utf-8", # 485 ".c++" => "text/x-c++src; charset=utf-8", ".cc" => "text/x-c++src; charset=utf-8", ".cpp" => "text/x-c++src; charset=utf-8", ".cxx" => "text/x-c++src; charset=utf-8", ".h" => "text/x-chdr; charset=utf-8", # 490 ".htc" => "text/x-component", ".csh" => "text/x-csh; charset=utf-8", ".c" => "text/x-csrc; charset=utf-8", ".diff" => "text/x-diff; charset=utf-8", ".patch" => "text/x-diff; charset=utf-8", # 495 ".d" => "text/x-dsrc; charset=utf-8", ".hs" => "text/x-haskell; charset=utf-8", ".java" => "text/x-java; charset=utf-8", ".ly" => "text/x-lilypond; charset=utf-8", ".lhs" => "text/x-literate-haskell; charset=utf-8", # 500 ".moc" => "text/x-moc; charset=utf-8", ".p" => "text/x-pascal; charset=utf-8", ".pas" => "text/x-pascal; charset=utf-8", ".gcd" => "text/x-pcs-gcd", ".pl" => "text/x-perl; charset=utf-8", # 505 ".pm" => "text/x-perl; charset=utf-8", ".py" => "text/x-python; charset=utf-8", ".scala" => "text/x-scala; charset=utf-8", ".etx" => "text/x-setext", ".sfv" => "text/x-sfv", # 510 ".sh" => "text/x-sh; charset=utf-8", ".tcl" => "text/x-tcl; charset=utf-8", ".tk" => "text/x-tcl; charset=utf-8", ".cls" => "text/x-tex; charset=utf-8", ".ltx" => "text/x-tex; charset=utf-8", # 515 ".sty" => "text/x-tex; charset=utf-8", ".tex" => "text/x-tex; charset=utf-8", ".vcs" => "text/x-vcalendar", ".3gp" => "video/3gpp", ".ts" => "video/MP2T", # 520 ".axv" => "video/annodex", ".dl" => "video/dl", ".dif" => "video/dv", ".dv" => "video/dv", ".fli" => "video/fli", # 525 ".gl" => "video/gl", ".mp4" => "video/mp4", ".mpe" => "video/mpeg", ".mpeg" => "video/mpeg", ".mpg" => "video/mpeg", # 530 ".ogv" => "video/ogg", ".mov" => "video/quicktime", ".qt" => "video/quicktime", ".webm" => "video/webm", ".mxu" => "video/vnd.mpegurl", # 535 ".flv" => "video/x-flv", ".lsf" => "video/x-la-asf", ".lsx" => "video/x-la-asf", ".mkv" => "video/x-matroska", ".mpv" => "video/x-matroska", # 540 ".mng" => "video/x-mng", ".asf" => "video/x-ms-asf", ".asx" => "video/x-ms-asf", ".wm" => "video/x-ms-wm", ".wmv" => "video/x-ms-wmv", # 545 ".wmx" => "video/x-ms-wmx", ".wvx" => "video/x-ms-wvx", ".avi" => "video/x-msvideo", ".movie" => "video/x-sgi-movie", ".ice" => "x-conference/x-cooltalk", # 550 ".sisx" => "x-epoc/x-sisx-app", ".vrm" => "x-world/x-vrml", "README" => "text/plain; charset=utf-8", "Makefile" => "text/x-makefile; charset=utf-8", "" => "application/octet-stream", # 555 ) server.document-root = "/var/www" server.upload-dirs = ("/var/cache/lighttpd/uploads") server.errorlog = "/var/log/lighttpd/error.log" server.pid-file = "/var/run/lighttpd.pid" server.username = "www-data" server.groupname = "www-data" server.port = 80 server.http-parseopts = ( "header-strict" => "enable", "host-strict" => "enable", "host-normalize" => "enable", "url-normalize-unreserved" => "enable", "url-normalize-required" => "enable", # 5 "url-ctrls-reject" => "enable", "url-path-2f-decode" => "enable", "url-path-dotseg-remove" => "enable", # 8 ) index-file.names = ("index.php", "index.html", "index.lighttpd.html") url.access-deny = ("~", ".inc") static-file.exclude-extensions = (".php", ".pl", ".fcgi") compress.cache-dir = "/var/cache/lighttpd/compress/" compress.filetype = ("application/javascript", "text/css", "text/html", "text/plain") url.redirect = ( "^/.well-known/caldav" => "/nextcloud/remote.php/dav", "^/.well-known/carddav" => "/nextcloud/remote.php/dav", "^/ocm-provider" => "/nextcloud/ocm-provider", "^/ocs-provider" => "/nextcloud/ocs-provider", # 4 ) fastcgi.server = ( ".php" => ( ( "socket" => "/run/php/php7.3-fpm.sock", "broken-scriptfilename" => "enable", # 2 ), ), ) server.modules = ( "mod_indexfile", "mod_setenv", "mod_access", "mod_alias", "mod_redirect", "mod_fastcgi", "mod_rewrite", "mod_compress", "mod_dirlisting", "mod_staticfile", # 10 ) $SERVER["socket"] == "[::]:80" { # block 1 } # end of $SERVER["socket"] == "[::]:80" $HTTP["url"] =~ "^/nextcloud($|/)" { # block 2 dir-listing.activate = "disable" $HTTP["url"] =~ "^/nextcloud/(build|tests|config|lib|3rdparty|templates|data)($|/)" { # block 3 url.access-deny = ("") } # end of $HTTP["url"] =~ "^/nextcloud/(build|tests|config|lib|3rdparty|templates|data)($|/)" $HTTP["url"] =~ "^/nextcloud/(\.|autotest|occ|issue|indie|db_|console)" { # block 4 url.access-deny = ("") } # end of $HTTP["url"] =~ "^/nextcloud/(\.|autotest|occ|issue|indie|db_|console)" $HTTP["url"] =~ "^/nextcloud/.+[^/]\.(css|js|woff2?|svg|gif|map)$" { # block 5 setenv.add-response-header = ( "Cache-Control" => "public, max-age=15778463", "Referrer-Policy" => "no-referrer", "X-Content-Type-Options" => "nosniff", "X-Download-Options" => "noopen", "X-Frame-Options" => "SAMEORIGIN", # 5 "X-Permitted-Cross-Domain-Policies" => "none", "X-Robots-Tag" => "none", "X-XSS-Protection" => "1; mode=block", # 8 ) } # end of $HTTP["url"] =~ "^/nextcloud/.+[^/]\.(css|js|woff2?|svg|gif|map)$" } # end of $HTTP["url"] =~ "^/nextcloud($|/)" } ```

Mostly Debian package defaults, the custom /nextcloud part is at the bottom. PHP is as well default php7.3-fpm + APCu memory caching + Redis-based file locking. I can provide more details if required, just not sure what might play a role here 😉

UweDoe commented 4 years ago

I have modified the source to get some additional logging: public function getDecryptedValue(): string { $token = explode(':', $this->value); if (\count($token) !== 2) { return ''; } $obfuscatedToken = $token[0]; $secret = $token[1]; \OC::$server->getLogger()->info('obfuscatedToken: '. $obfuscatedToken .', secret: '. $secret); $tmpReturn = base64_decode($obfuscatedToken) ^ base64_decode($secret); \OC::$server->getLogger()->info('getDecryptedValue: '. $tmpReturn ); return base64_decode($obfuscatedToken) ^ base64_decode($secret); } }

With this I did the ususal test: 1) login as admin 2) goto user / protocol and see the logging 3) logout

this time successfull

4) login as admin 5) goto user / protocol and see the logging 6) logout (unsuccessfull) 7) push browsers back button 8) logout (unsuccessfull) ... until successfull logout

Traces are attached. For details see the readme. logs+traces.zip

MichaIng commented 4 years ago

Problem is not yet solved, additional logs and stack trace provided above. Issue, although Lighttpd not officially supported, is security-relevant, e.g. on public clients:

d235j commented 4 years ago

I'm getting the same error with nginx, though also on https://mysite/index.php/apps/files/ajax/getstoragestats.php?dir=%2F.

J0WI commented 4 years ago

I was able to reproduce this with nginx and front_controller_active true: /logout?requesttoken=abc returns CSRF check failed, but index.php/logout?requesttoken=abc works.

tsposato commented 4 years ago

I also can reproduce this issue on NC 17.0.1 using nginx. Happy to provide logs if needed. If I click logout twice it returns me to login screen, then upon logging with a new user I get the error: Access forbidden CSRF check failed

I was also getting the same error in NC 16.x - it is a smallish home server so it doesn't bother me that and I trawled through a bunch of forum posts and proposed fixes for similar issues to no avail.

pbalm commented 4 years ago

This fixed this problem for me:

https://github.com/nextcloud/server/issues/1075#issuecomment-274376615

pbalm commented 4 years ago

I also think this issue is a duplicate of this one: https://github.com/nextcloud/server/issues/1075

MichaIng commented 4 years ago

@pbalm It is not an HTTPS-only issue. I get the same error when logging in+out via HTTP, hence forcing SSL/HTTPS is no solution there. I am testing with NC18 Beta 4 now and settings the two settings to "false" instead.


NC18 Beta 4 same issue.

'overwriteprotocol' => 'http',
'forcessl' => false,

Same issue (note I am testing HTTP here, to verify it is no HTTPS-only issue).


For testing/replicating:

  1. Login
  2. Go to settings
  3. Go to log viewer
  4. Logout

If the issue does not occur, try to restart webserver (and clear browser cache?) and redo the above. I also restarted Redis and MariaDB, but pretty sure that is not required.

Tested with:

root@VM-Buster:~# lighttpd -v
lighttpd/1.4.53 (ssl) - a light and fast webserver

root@VM-Buster:~# php -v
PHP 7.3.11-1~deb10u1 (cli) (built: Oct 26 2019 14:14:18) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.11, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.3.11-1~deb10u1, Copyright (c) 1999-2018, by Zend Technologies

root@VM-Buster:~# mariadb -v
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 57
Server version: 10.3.18-MariaDB-0+deb10u1 Debian 10

Just to wrap up some information, when reading through #1075:

So even that the above things seem to have some influence for people, none of it is a definite reason, nor can related changes solve it definitely. Not sure if its somehow possible, CSRF check fails, for whatever reason, to immediately mark the user as logged out for that particular session?

ChristophWurst commented 4 years ago

@MichaIng thanks for the detailed debugging info. Could you add the logs @kesselb added to his instance and additionally log the values (ref https://github.com/nextcloud/server/issues/17065#issuecomment-536923540) so we can get a bit more info about why this check is failing?

MichaIng commented 4 years ago

@ChristophWurst I'll do. Any preference whether to do this on stable v17.0.2 or current v18 RC1? Latter makes more sense?

ChristophWurst commented 4 years ago

Thanks! It shouldn't matter :)

MichaIng commented 4 years ago

Okay I updated an instance to 18 RC2 and disabled all apps but log viewer to have latest code and remove any possible apps influence. Then edited: lib/private/Security/CSRF/CsrfTokenManager.php

        public function isTokenValid(CsrfToken $token): bool {
                if(!$this->sessionStorage->hasToken()) {
                        return false;
                }

                // DEBUG START
                $isEqual = hash_equals($this->sessionStorage->getToken(), $token->getDecryptedValue());
                if (!$isEqual) {
                        \OC::$server->getLogger()->warning('going to validate csrf token "' . $this->sessionStorage->getToken() . '" with "' . $token->getDecryptedValue() . '"');
                }
                // DEBUG END

                return hash_equals(
                        $this->sessionStorage->getToken(),
                        $token->getDecryptedValue()
                );
        }

lib/private/Security/CSRF/CsrfToken.php

        public function getDecryptedValue(): string {
                $token = explode(':', $this->value);
                if (\count($token) !== 2) {
                        return '';
                }
                $obfuscatedToken = $token[0];
                $secret = $token[1];
                // DEBUG START
                \OC::$server->getLogger()->info('obfuscatedToken: '. $obfuscatedToken .', secret: '. $secret);
                $tmpReturn = base64_decode($obfuscatedToken) ^ base64_decode($secret);
                \OC::$server->getLogger()->info('getDecryptedValue: '. $tmpReturn );
                // DEBUG END
                return base64_decode($obfuscatedToken) ^ base64_decode($secret);
        }
  1. Access + login Nextcloud
  2. Access log viewer... my goodness, produces tenth of logs every second from getDecryptedValue, obviously the log viewer poll triggers the event.
  3. Log out:
    Zugriff verboten
    CSRF check failed
  4. Remove debug code
  5. Open Nextcloud freshly on new browser tab: I was still logged in, demonstrating the bug and its security issue very well!
  6. Check logs:
    
    [no app in context] Info: obfuscatedToken: nCV07rTLYHtzW0rcmcNVgIwHAIBMdWeTSrnx8cXTck8=, secret: 1Uojl+2AKgpYDC+p6bY678RoUus2EgT1DPPem7GjQRY=
    GET /nextcloud/index.php/apps/logreader/poll?lastReqId=Me8CKtvlgIogiTMhvoy3
    from 192.168.178.21 by admin at 2020-01-09T22:20:49+00:00

[no app in context] Info: getDecryptedValue: IoWyYKJq+WeupuooHoRkzgcfFJ/jtp3Y GET /nextcloud/index.php/apps/logreader/poll?lastReqId=Me8CKtvlgIogiTMhvoy3 from 192.168.178.21 by admin at 2020-01-09T22:20:49+00:00

Below two entries come two times, as can be seen with slightly changed secret, having "+" replaced with white spaces, decrypted value changed accordingly?

[no app in context] Info: obfuscatedToken: nCV07rTLYHtzW0rcmcNVgIwHAIBMdWeTSrnx8cXTck8=, secret: 1Uojl 2AKgpYDC p6bY678RoUus2EgT1DPPem7GjQRY= GET /nextcloud/index.php/logout?requesttoken=nCV07rTLYHtzW0rcmcNVgIwHAIBMdWeTSrnx8cXTck8%3D%3A1Uojl%2B2AKgpYDC%2Bp6bY678RoUus2EgT1DPPem7GjQRY%3D from 192.168.178.21 by admin at 2020-01-09T22:20:49+00:00

[no app in context] Info: getDecryptedValue: IoWyÔÁâípQÔGúm©Æ )³ál:7\wPJëñ GET /nextcloud/index.php/logout?requesttoken=nCV07rTLYHtzW0rcmcNVgIwHAIBMdWeTSrnx8cXTck8%3D%3A1Uojl%2B2AKgpYDC%2Bp6bY678RoUus2EgT1DPPem7GjQRY%3D from 192.168.178.21 by admin at 2020-01-09T22:20:49+00:00

[no app in context] Warning: going to validate csrf token "IoWyYKJq+WeupuooHoRkzgcfFJ/jtp3Y" with "IoWyÔÁâípQÔGúm©Æ )³ál:7\wPJëñÂ" GET /nextcloud/index.php/logout?requesttoken=nCV07rTLYHtzW0rcmcNVgIwHAIBMdWeTSrnx8cXTck8%3D%3A1Uojl%2B2AKgpYDC%2Bp6bY678RoUus2EgT1DPPem7GjQRY%3D from 192.168.178.21 by admin at 2020-01-09T22:20:49+00:00

[no app in context] Info: obfuscatedToken: nCV07rTLYHtzW0rcmcNVgIwHAIBMdWeTSrnx8cXTck8=, secret: 1Uojl 2AKgpYDC p6bY678RoUus2EgT1DPPem7GjQRY= GET /nextcloud/index.php/logout?requesttoken=nCV07rTLYHtzW0rcmcNVgIwHAIBMdWeTSrnx8cXTck8%3D%3A1Uojl%2B2AKgpYDC%2Bp6bY678RoUus2EgT1DPPem7GjQRY%3D from 192.168.178.21 by admin at 2020-01-09T22:20:49+00:00

[no app in context] Info: getDecryptedValue: IoWyÔÁâípQÔGúm©Æ )³ál:7\wPJëñ GET /nextcloud/index.php/logout?requesttoken=nCV07rTLYHtzW0rcmcNVgIwHAIBMdWeTSrnx8cXTck8%3D%3A1Uojl%2B2AKgpYDC%2Bp6bY678RoUus2EgT1DPPem7GjQRY%3D from 192.168.178.21 by admin at 2020-01-09T22:20:49+00:00

Below two entries again come two times, showing secret and decrypted value as it was before the logout.

[no app in context] Info: obfuscatedToken: nCV07rTLYHtzW0rcmcNVgIwHAIBMdWeTSrnx8cXTck8=, secret: 1Uojl+2AKgpYDC+p6bY678RoUus2EgT1DPPem7GjQRY= GET /nextcloud/index.php/apps/logreader/poll?lastReqId=IBpRq6Ll1fjC4vocsFCP from 192.168.178.21 by admin at 2020-01-09T22:20:49+00:00

[no app in context] Info: getDecryptedValue: IoWyYKJq+WeupuooHoRkzgcfFJ/jtp3Y GET /nextcloud/index.php/apps/logreader/poll?lastReqId=IBpRq6Ll1fjC4vocsFCP from 192.168.178.21 by admin at 2020-01-09T22:20:49+00:00

- As can be seen, the token secret, related to the logout, has two `+` replaced by white spaces, which leads to a changed decrypted value, I guess?
- Afterwards (before I removed debug code and reloaded/re-accessed Nextcloud) token secret and decrypted value changed back to what they were before.
- So finally it looks like that, for the logout request, the token secret is handled wrong, probably replacing/expending certain "magic" characters, or wrong variable type or such?
__________
From GET request it can be seen that the `+` in secret is translated to `%2B`, at least within the log entry, as well when checking the log file directly or raw formatted output. Okay but this is correct URL coding for `+`.

In UweDoe logs above, I see the same, `+` being replaced by white spaces in logout related entries: https://github.com/nextcloud/server/issues/17065#issuecomment-537486778
Is there a way to force a certain token without `+` to test with? 😉
____________
#### EDIT
**Bingo!** I restarted new sessions, until I got a token without `+` inside its secret (and obfuscated token string as well none by chance), and logout worked well:

[no app in context] Info: obfuscatedToken: 4L1Ejh5r2FHZONJDUy3WzWjlihWSXt2aAUKGGdFClFQ=, secret: qdIT90cgkiDyb7c2I1i5oiCK2H7oOb78Rwipc6Uypw0= GET /nextcloud/index.php/apps/logreader/poll?lastReqId=J1ldwJroHzasyuDr2oMF from 192.168.178.21 by admin at 2020-01-09T23:40:11+00:00

[no app in context] Info: obfuscatedToken: 4L1Ejh5r2FHZONJDUy3WzWjlihWSXt2aAUKGGdFClFQ=, secret: qdIT90cgkiDyb7c2I1i5oiCK2H7oOb78Rwipc6Uypw0= GET /nextcloud/index.php/logout?requesttoken=4L1Ejh5r2FHZONJDUy3WzWjlihWSXt2aAUKGGdFClFQ%3D%3AqdIT90cgkiDyb7c2I1i5oiCK2H7oOb78Rwipc6Uypw0%3D from 192.168.178.21 by admin at 2020-01-09T23:40:11+00:00


This explains why some users report contradictory solutions and sometimes waiting for a token timeout solves, sometimes creates the logout issue. It is simply about if the token strings contain `+` characters or not, or possible other affected characters, although I could not find any others.
kesselb commented 4 years ago

Thanks :+1:

https://github.com/nextcloud/server/blob/5bf3d1bb384da56adbf205752be8f840aac3b0c5/lib/private/legacy/user.php#L280

https://github.com/nextcloud/server/blob/d393b1612bab004359621500ff5e771d10165a95/lib/private/AppFramework/Http/Request.php#L490

If we encode it first we probably have to decode it later ;) Are you able to reproduce with $token = urldecode($this->items['get']['requesttoken']);?

ChristophWurst commented 4 years ago

This explains why some users report contradictory solutions and sometimes waiting for a token timeout solves, sometimes creates the logout issue. It is simply about if the token strings contain + characters or not, or possible other affected characters, although I could not find any others.

Oh wow. I never thought about that.

Thanks you two, this is some stellar debugging work :pray:

MichaIng commented 4 years ago

@kesselb lib/private/AppFramework/Http/Request.php

        public function passesCSRFCheck(): bool {
                if($this->csrfTokenManager === null) {
                        return false;
                }

                if(!$this->passesStrictCookieCheck()) {
                        return false;
                }

                if (isset($this->items['get']['requesttoken'])) {
                        $token = urldecode($this->items['get']['requesttoken']);
                } elseif (isset($this->items['post']['requesttoken'])) {
                        $token = $this->items['post']['requesttoken'];
                } elseif (isset($this->items['server']['HTTP_REQUESTTOKEN'])) {
                        $token = $this->items['server']['HTTP_REQUESTTOKEN'];
                } else {
                        //no token found.
                        return false;
                }
                $token = new CsrfToken($token);

                return $this->csrfTokenManager->isTokenValid($token);
        }

Issue stays the same:

[no app in context] Info: obfuscatedToken: NTDWiyTQK3ztBRgmc61+hmtdgcl4Y+6wKOkYZMtSoOw=, secret: BmKU5ku8ZyrZXVVrRMwLtA9p2IU+IMH5WqVPJr0L06Q=
GET /nextcloud/index.php/apps/logreader/poll?lastReqId=4Ii888Bm3GM431fg6zdS
from 192.168.178.21 by admin at 2020-01-10T11:54:42+00:00

[no app in context] Info: obfuscatedToken: NTDWiyTQK3ztBRgmc61 hmtdgcl4Y 6wKOkYZMtSoOw=, secret: BmKU5ku8ZyrZXVVrRMwLtA9p2IU IMH5WqVPJr0L06Q=
GET /nextcloud/index.php/logout?requesttoken=NTDWiyTQK3ztBRgmc61%2Bhmtdgcl4Y%2B6wKOkYZMtSoOw%3D%3ABmKU5ku8ZyrZXVVrRMwLtA9p2IU%2BIMH5WqVPJr0L06Q%3D
from 192.168.178.21 by admin at 2020-01-10T11:54:42+00:00

Or decode is done doubled somewhere?

MichaIng commented 4 years ago

Added logging now like this: lib/private/AppFramework/Http/Request.php

        public function passesCSRFCheck(): bool {
                if($this->csrfTokenManager === null) {
                        return false;
                }

                if(!$this->passesStrictCookieCheck()) {
                        return false;
                }

                if (isset($this->items['get']['requesttoken'])) {
                        $token = $this->items['get']['requesttoken'];
            // START DEBUG
                        \OC::$server->getLogger()->warning('requesttoken: '. $token);
            // END DEBUG
                } elseif (isset($this->items['post']['requesttoken'])) {
                        $token = $this->items['post']['requesttoken'];
                } elseif (isset($this->items['server']['HTTP_REQUESTTOKEN'])) {
                        $token = $this->items['server']['HTTP_REQUESTTOKEN'];
                } else {
                        //no token found.
                        return false;
                }
                $token = new CsrfToken($token);

                return $this->csrfTokenManager->isTokenValid($token);
        }

Important part of the received log:

requesttoken: jW9QFyNHKQpKHVU b9Giylc03lxKyt7thEuh9ZE8niM=:wicnJRQDYnAyKDZqROHQgjtCsjswmZOH/i3YuNpIpmU=

With $token = urldecode($this->items['get']['requesttoken']);:

requesttoken: 4XhN/pNv6jtLheHnUKQhqRPIl57JzmmEnp3nVQH3IAQ=:rjA6zKQroUEzsIKze5RT4X /mznSTu5PueGEqDGEI=

With $token = urlencode($this->items['get']['requesttoken']); (just for debugging, of course this looks totally unreasonable):

requesttoken: eUWM5dtfC5leylElezVx4zqfE72Bv7Kw+kRm+NKf2Vc%3D%3ANg371+wbQOMm%2FzJxUAUDq1bpf9r77P%2FagCIftZnr4RE%3D

So from what I see, somewhere the URL decryption is done already, probably PHP internally when scraping request strings? However only the + is somehow decrypted doubled. The GET and browser request URL string looks totally fine.


Hack $token = str_replace(' ','+',$this->items['get']['requesttoken']);

requesttoken: m/1c4cFcSuhYHPvgPzPMSBsVszduK1kMg0Ky1LlkCbw=:1LUr0/YYAZIgKZi0FAO+AHdj31AUeBRm+STLmfIQMfo=
kesselb commented 4 years ago

https://3v4l.org/aXnXQ

rawurlencode and rawurldecode should work.

MichaIng commented 4 years ago

@kesselb I'll test it in the evening. See my last bloody workaround which works fine:

$token = str_replace(' ','+',$this->items['get']['requesttoken']);
ChristophWurst commented 4 years ago

https://stackoverflow.com/questions/996139/urlencode-vs-rawurlencode explains the differences a bit

rullzer commented 4 years ago

I'm still a bit puzzled.

https://3v4l.org/aXnXQ

rawurlencode and rawurldecode should work.

https://3v4l.org/As3Bo But they work just as fine with the normal ones.

In any case here php automatically does the decoding. They are url encoded so we can pass them in a url. See https://www.php.net/manual/en/function.urldecode.php and esp:

Warning The superglobals $_GET and $_REQUEST are already decoded. Using urldecode() on an element in $_GET or $_REQUEST could have unexpected and dangerous results.

@MichaIng how does the url in your network console look that gets accessed? Is that properly encoded?

rullzer commented 4 years ago

A quick test file. Since that is easier than debugging nextcloud

https://gist.github.com/rullzer/14ca2778cb71f85a3272da3c4e6f2cdc

if you enter that via: <SERVER>?token=ABC%2BDEF+GHI

What does that give you all?

MichaIng commented 4 years ago

urlencode

Returns a string in which all non-alphanumeric characters except -_. have been replaced with a percent (%) sign followed by two hex digits and spaces encoded as plus (+) signs.

... which accordingly means that (+) signs are decoded as spaces. What I still don't get is that the (+) signs are correctly encoded first, it seems, but auto-decoded PHP-internally (?) wrong or doubled? I mean how does PHP know if the string is raw-encoded, or not, hence if (+) must be decoded or not?

For the encoding done in Nextcloud, it should not matter if raw or non-raw encode is used, since the request strings do not contain white spaces anyway. In both cases, (+) is correctly encoded, hence the decode function (PHP-internally, which is urldecode (non-raw)) should not matter as well. There is definitely another reason why (+), and only (+), is decoded two times (%2B => + => space), probably not even by URL decoding done PHP-internally, but probably at another place in code when handling the strings/arrays, etc...

However I am corrently not at home, will test the above later.

MichaIng commented 4 years ago

@rullzer

2020-01-11 00:16:08 root@VM-Buster:/var/www$ cat test.php
<?php
$token = $_GET['token'];
var_dump($token);
$encToken = urlencode($token);
?>
<br>
<a href="?token=<?php echo $token; ?>">Go to: ?<?php echo $token; ?></a><br>
<a href="?token=<?php echo $encToken; ?>">Go to: ?<?php echo $encToken; ?></a><br>

http://192.168.178.30/test.php?token=ABC%2BDEF+GHI

string(11) "ABC DEF GHI"
Go to: ?ABC DEF GHI
Go to: ?ABC+DEF+GHI

Using spaces, + or %2B all results in spaces in string, hence result is exactly the same in all cases 🤔. Spaces are encoded to +, so the encrypted string is as it should be.

So it is PHP-internal indeed, very strange.... So there is no chance to preserve a + from input URL? I can't believe it...


Reference: https://stackoverflow.com/questions/2671840/php-plus-sign-with-get-query Further testing:

<?php echo $_GET['token']; ?><br>
<?php echo urlencode('A+B'); ?><br>
<?php echo rawurlencode('A+B'); ?><br>
<?php echo urldecode('A%2BB'); ?><br>
<?php echo rawurldecode('A%2BB'); ?>

Result, regardless of how + has been decoded or not in URL:

A B
A%2BB
A%2BB
A+B
A+B

So all research leads to urlencode/rawurlencode being the solution for lost + signs, but that doesn't work for me.


Back to webserver (lighttpd/1.4.53 Debian Buster package)

The following works, proving url-normalize-required as malicious Lighttpd option: (url-query-20-plus has no effect btw, the comment made me give it a try)

server.http-parseopts = (
  "header-strict"           => "enable",# default
  "host-strict"             => "enable",# default
  "host-normalize"          => "enable",# default
  "url-normalize-unreserved"=> "enable",# recommended highly
 #"url-normalize-required"  => "enable",# recommended
  "url-ctrls-reject"        => "enable",# recommended
  "url-path-2f-decode"      => "enable",# recommended highly (unless breaks app)
 #"url-path-2f-reject"      => "enable",
  "url-path-dotseg-remove"  => "enable",# recommended highly (unless breaks app)
 #"url-path-dotseg-reject"  => "enable",
 #"url-query-20-plus"       => "enable",# consistency in query string
)

Is it really the webservers job to manipulate URL/query strings on parsing, which can only (?) have any dangerous effect when forwarded to another server/handler like PHP? It is the task of the PHP script to handle those strings correctly and of the PHP implementation to limit access to defined areas, isn't it? It was really the last idea I had to recheck/test webserver configs...

MichaIng commented 4 years ago

Summary

The question is if it should be ignored, since Lighttpd is not officially supported, or if some workaround should be implemented. Since $_GET['token'] regularly has no spaces, those could be simply replaced back with +, if present: str_replace(' ','+',$str). Shouldn't add some security risk and if the token string is wrong anyway for another reason (so that it contains spaces), then this will not produce additional damage at least.

kesselb commented 4 years ago

Good finding :+1:

https://www.lighttpd.net/2019/5/27/1.4.54/ will be more strict and probably break other requests.

some workaround should be implemented.

Please keep in mind that such "hey it would be good ..." changes always have a price tag. At some point people don't remember why it has been added or expect the behaviour to be default.

If I understand you correctly it's possible to turn off the normalization. That would be something for the docs. Probably a own lighttpd page.

Also good to know: https://news.netcraft.com/archives/2019/12/10/december-2019-web-server-survey.html

tsposato commented 4 years ago

I am also encountering this problem with nginx too. So I don't think it is encountered only on lighthttpd.

On Sat, 11 Jan 2020, 9:58 pm kesselb, notifications@github.com wrote:

Good finding 👍

https://www.lighttpd.net/2019/5/27/1.4.54/ will be more strict and probably break other requests.

some workaround should be implemented.

Please keep in mind that such "hey it would be good ..." changes always have a price tag. At some point people don't remember why it has been added or expect the behaviour to be default.

If I understand you correctly it's possible to turn off the normalization. That would be something for the docs. Probably a own lighttpd page.

Also good to know: https://news.netcraft.com/archives/2019/12/10/december-2019-web-server-survey.html

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/nextcloud/server/issues/17065?email_source=notifications&email_token=AGP7VZQY7UYJ63SR6FMU63TQ5GQ6VA5CNFSM4IU3GXY2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEIV7KEA#issuecomment-573306128, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGP7VZSIEEKGUL5DCFWTWQ3Q5GQ6VANCNFSM4IU3GXYQ .

MichaIng commented 4 years ago

Please keep in mind that such "hey it would be good ..." changes always have a price tag. At some point people don't remember why it has been added or expect the behaviour to be default.

@kesselb Such must always be implemented with proper comments and at best link to related discussion, so everyone is able to track why/where it's coming from. But after sleeping a night over it, I agree having a Lighttpd page in Nextcloud docs to point this out, and disable conflicting options right in the webserver, makes more sense then having query strings coded back and forth as a result of a workaround.

Btw: https://redmine.lighttpd.net/projects/lighttpd/wiki/Server_http-parseoptsDetails

But another topic: Since this issue only occurs on logout, it seems that this is the only case where the session token is transmitted via request URL hence parsed from $_GET? Is this really necessary? Wouldn't it be more reasonable to keep this server-internally hidden from eyes and monitors? In theory if one is able to catch the browser URL with obfuscated token and secret visible, from a publicly placed monitor, one would be able to log into the same session currently, as long as the token is valid? I have no insight about how such in handled, but e.g. the log viewer app is well aware which session (token) it is accessed with without having it as part of the request URL (?). Couldn't the logout "button" get the session that is to be logged out (hence the token that is to be invalidated) as well receive this info server-internally?


@tsposato Do you have any proxy, VPN, machine internal rewrites or redirects in front of your Nextcloud instance, aside of the redirects apart from the ones of the docs Nginx config example?

rullzer commented 4 years ago

@MichaIng thanks for testing. But this indeed very weird and troubling as well.

As you can see Nextcloud itself doesn't do the url decode on those but then happen when things are passed to php. So I agree this is an issue in the webserver configs.

As for why this is in the URL. It is because logout is a GET request. The token is there to prevent CSRF attacks. We now have more modern ways to deal with this. But the issue is that we can't guarantee they work in all versions. And being able to logout is kind of critical.

I discussed with @ChristophWurst the others day. For 19 we could try to migrate this menu to Vue. And then we could do some smarter things.

However, as we still want to pass the token. But then it will be passed in POST. We should double test if this is handled properly.

MichaIng commented 4 years ago

@rullzer Sounds reasonable. Yeah as logout indeed is critical the method should be failsafe as possible. However this is the reason why a correct token is critical which is with Lighttpd defaults and some other cases (as report(s) above) not the case. Moving away from GET would at least avoid issues with pre-decoded URL parts.

I will open a new issue about adding Lighttpd to the docs. I currently have not the time for a PR, but will link a related threat from forum and the config we use on DietPi as a start.

rullzer commented 4 years ago

@MichaIng could you modify the script I posted to see if POST is also double escaped?

And thanks a bunch for your debugging efforts. Really appercicated and good that we did find why it happens sometimes.

MichaIng commented 4 years ago

@rullzer learning how to send a POST request via PHP: https://stackoverflow.com/questions/5647461/how-do-i-send-a-post-request-with-php#6609181

~Which method is used in Nextcloud, cURL or no cURL?~ EDIT: Okay 19 hits vs 0 hits of related function. So non-cURL method. Will test this night.

rullzer commented 4 years ago

@MichaIng or just use curl.

curl -X POST -d 'token=FOO' SERVER

GeorgFleig commented 4 years ago

Thanks for all the debugging efforts everyone!

Besides the logout page I've also seen this error during the execution of the web updater. When finalizing the update (update schema, apps, ..) by clicking the button, I got this error as well. Going back to the previous page and clicking the button again worked after a few attempts. Same as during the logout.

Webserver: lighttpd 1.4.53 Nextcloud: 17.0.2

MichaIng commented 4 years ago

@GeorgFleig Do you remember or can you replicate whether the URL in browser contains the token string, hence this is a GET request as well? Would fit actually, also fits to your solution to retry for certain times (until the token strings contain no +). Ah the others will know already.

d235j commented 4 years ago

Note that there's a known bug in lighttpd-1.4.53 involving expansion of + characters. This is planned to be fixed in 1.4.55.

https://redmine.lighttpd.net/issues/2999

tsposato commented 4 years ago

I run into this same issue with nginx. Is this a similar problem with the nginx config? Is there a way I can test and get logs which will help?

d235j commented 4 years ago

I think there are two separate bugs here :(

MichaIng commented 4 years ago

@d235j Many thanks for linking this, so at least we're not the only ones who recognised this to be an issue.

However I am not 100% sure about this really being a "bug" and as well the solution looks more like a workaround that makes the whole concept inconsistent.

+ IS in fact the correct URL encoding for spaces. So as this Lighttpd option is to decode the URL string, this is done totally correct. It just breaks thinks when a second decoding is done afterwards. This seems to be a bid unique to %2B => + => <space>, since it is one of rare (or only?) single characters, coding other characters.

Now from what I see, the "fix" simply excludes the + from decoding. So now when someone encodes a string with white spaces, those will be encoded as + (together with other characters to their %XX codes), but when Lighttpd decodes this via this option, all BUT the + is decoded back. Hence you have a half/half decoded string, which of course is a total mess for anything that wants to parse that afterwards.

Decoding MUST only be done once, in every case, it is as simple as this. As PHP decodes the $_GET array automatically, any previous decoding potentially breaks every PHP script which needs to read that, explicitly if contained raw string contains + symbols.

@tsposato Do you have any proxy or VPN or custom rewrite/redirects on front of the Nginx webserver, so anything that in theory or obviously manipulates or writes the URL/query string to a new request?

GeorgFleig commented 4 years ago

@MichaIng I checked my server logs. The URL does indeed contain the requesttoken and this token contained %2B when the request was failing. No %2B for the successful requests. So this seems to be the same issue. The URL in question was /core/ajax/update.php?requesttoken=.

MichaIng commented 4 years ago

@GeorgFleig Thanks for checking, so yes this is basically the same issue. Similarly it is hopefully possible to migrate this to a POST request.

MichaIng commented 4 years ago

@rullzer Finally did the POST test, sorry for the delay:

2020-01-18 16:31:49 root@VM-Buster:/var/www$ cat index.php
<?php
$url = 'http://localhost/receive.php';
$data = array('raw' => 'A+B', 'encoded' => 'A%2BB');

$options = array(
    'http' => array(
        'header'  => "Content-type: application/x-www-form-urlencoded\r\n",
        'method'  => 'POST',
        'content' => http_build_query($data)));

$response = file_get_contents($url, false, stream_context_create($options));

echo 'POST data: ', var_dump($data), 'Response raw: ', $response, 'Response decoded ', urldecode($response);
echo 'GET: ', var_dump($_GET)
?>

2020-01-18 16:32:08 root@VM-Buster:/var/www$ cat receive.php
<?php var_dump($_POST) ?>

2020-01-18 16:32:11 root@VM-Buster:/var/www$ curl 'localhost?raw=A+B&encoded=A%2BB'
POST data: array(2) {
  ["raw"]=>
  string(3) "A+B"
  ["encoded"]=>
  string(5) "A%2BB"
}
Response raw: array(2) {
  ["raw"]=>
  string(3) "A+B"
  ["encoded"]=>
  string(5) "A%2BB"
}
Response decoded array(2) {
  ["raw"]=>
  string(3) "A B"
  ["encoded"]=>
  string(5) "A+B"
}
GET: array(2) {
  ["raw"]=>
  string(3) "A B"
  ["encoded"]=>
  string(3) "A B"
}

2020-01-18 16:32:16 root@VM-Buster:/var/www$ curl -X POST -d 'raw=A+B&encoded=A%2BB' localhost/receive.php
array(2) {
  ["raw"]=>
  string(3) "A B"
  ["encoded"]=>
  string(3) "A+B"
}