Notes About Security

Built-in protections

  • The Public Area and the Administration Area of TCExam are physically separated on the filesystem. This allows to add further access restrictions based on the filesystem and the web server, and the application ships .htaccess access controls.
  • To access the various TCExam resources you need a valid account (user/password) and a level equal to or greater than the requested resource level. The level of test takers and Public areas is 1 by default. TCExam also supports RADIUS, LDAP and CAS authentication, as well as custom authentication methods.
  • Passwords are stored using a modern password hashing algorithm.
  • Test takers may access a particular test only if they belong to a group enabled for that test.
  • Test takers may access a particular test only if their IP address is enabled for that test.
  • Test takers may execute a particular test only one time (unless authorized by an administrator, or the test is explicitly marked as repeatable).
  • TCExam may generate a unique test for each user by randomly selecting and ordering questions and alternative answers. This drastically reduces or eliminates the risk of copying between test takers.
  • The test time is kept by the server. At the end of the available time the test is automatically closed.
  • The full activity of the test taker is logged on the server, including statistical data (e.g. reaction time) and security data (e.g. IP address).
  • Another security benefit comes from TCExam being Open Source Software, because it is widely known that security by obscurity is not a secure practice in the long run.
  • If you use TCExam in a controlled environment (e.g. a computer room) you can also add all the traditional security measures such as keyloggers, video cameras and so on.

Security hardening in version 17

Version 17 includes a dedicated security-hardening pass:

  • Per-installation random secret. The shipped K_RANDOM_SECURITY value is now a placeholder; the installer generates a unique random secret for every install. The secret is used to generate session IDs, client fingerprints, CSRF tokens and SSO passwords.
  • CSPRNG-based codes. Verification codes and one-time values are generated with a cryptographically secure pseudo-random number generator.
  • Safe file/URL access. File and URL access is restricted through configurable path/host allow-lists (K_FILE_ALLOWED_PATHS / K_FILE_ALLOWED_HOSTS, and the K_PDF_ALLOWED_* options for the PDF engine).
  • Restricted registration groups. Self-registration can be limited to specific groups with K_USRREG_ALLOWED_GROUPS.

Uploaded files, CSRF and backups (17.3)

  • Upload checks. Media uploads reject markup and script types (such as xml, html, js and svgz) even when they are listed in K_ALLOWED_UPLOAD_EXTENSIONS, and every upload rejects server script types. File content is checked against the extension, file names are sanitized and existing files are never overwritten. File Manager rename and new directory names follow the same rules.
  • Plain SVG only. SVG files are accepted only as well-formed UTF-8 documents with static graphics: no scripts, event handlers, links, animations, external references (including CSS url(), @import, image(), image-set(), cross-fade() and src()), entity declarations, DTD subsets or editor-specific namespaces. See the Upgrade Guide for how to export plain SVG files from common editors.
  • Imports outside the cache. User, question and SSL certificate imports are read from the PHP temporary upload directory and are not copied into cache/.
  • No embedded objects. Previews and [object] tags display images, audio and video with <img>, <audio> and <video>, and every other file type as a download link.
  • CSRF tokens. Every POST request with form data or files must carry a valid csrf_token; otherwise the request stops with HTTP status 403 before any data is processed.
  • Backup password. Creating, restoring and downloading a backup requires the current user's password, with a 15-minute lockout after 5 wrong attempts. Users signed in through an SSL client certificate, CAS or Shibboleth are not asked for a password.
  • Passive cache/ folder. The shipped cache/.htaccess and the Docker Apache configuration serve cache/ as passive content: no script execution, X-Content-Type-Options: nosniff, a sandboxed Content-Security-Policy, and markup types sent as downloads.

Access control (17.4)

  • Page levels. A page that does not set its own level requires the administrator level. The results PDF, the test PDF generator and the user results export require the results or tests operator level.
  • Operator scope. Operators see and change only the results of the tests they are authorized for, including the "all tests" view, the user filter and the bulk lock, unlock, extend-time and delete actions. Subjects, questions and answers can be added or moved only under a module, subject or question the operator is authorized for, and only authorized subjects can be added to a test.
  • OMR imports. OMR answer sheets are accepted only for authorized tests and only when every question and answer belongs to the test. The QR-Code data is decoded without instantiating objects.
  • No PDF email token. Emailed PDF reports are generated in-process; the ?email= token of tce_pdf_results.php has been removed.
  • Session cookie only. The session ID is read only from the PHPSESSID cookie, never from a URL parameter.
  • Log and backup directories. admin/log/.htaccess, public/log/.htaccess and admin/backup/.htaccess deny web access, with matching rules in the Docker Apache configuration.

Exam pages, LaTeX, OTP and installer (17.5)

  • Exam test logs. The exam page displays and stores answers only for test logs of the examinee's active attempt: other examinees' questions and answers cannot be displayed, and answers of earlier attempts cannot be changed.
  • LaTeX. [tex] in free-text answers and test comments is not compiled. pdflatex runs with restricted file access and without shell escape, and pdfTeX primitives, \scantokens, \immediate and \shipout are rejected. See the LaTeX rendering page.
  • Output escaping. Names and test names are HTML-escaped on the online users page, the results pages, the module editor and the public test list; answer text and test comments are escaped on the exam page and in the PDF report.
  • One-Time-Password. The code is checked against the key of the user who logs in, codes with a leading zero are accepted, and a used code is blocked only for that user.
  • User editor. Editors below the administrator level can only edit their own account and users with a lower level.
  • Installer. The web installer refuses to run on an installed instance and accepts only POST. install/phpinfo.php and admin/code/tce_update.php have been removed, and the Docker image denies web access to install/.
  • Sessions and CSRF. A new session ID is issued at login. CSRF tokens are HMAC-SHA256 values that depend on the full session ID.
  • Test IP ranges. The allowed IP ranges of a test are checked against the address of the current request.
  • LDAP, CAS and SSL certificates. The LDAP user name is escaped in the search filter. CAS validates the server certificate when K_CAS_SERVER_CA_CERT is set. SSL client certificate login accepts certificates that have not expired and does not repeat the login for a user who is already logged in.

Isolating the administration area

The /admin/ path manages users, questions, tests and results. Treat it as a separate, internal service, so that TCExam's own login is not the only barrier:

  • Separate entry point. Serve /admin/ from a dedicated virtual host or hostname and deny /admin/ on the public host. Set K_PATH_URL_ADMIN in shared/config/tce_paths.php to the base URL of the admin host, and leave K_COOKIE_DOMAIN empty so that the session cookie is host-only.
  • Network restriction. Make the admin host reachable only through a VPN or an identity-aware access gateway, and drop all other traffic with a firewall. A firewall cannot filter by URL path: give the admin area its own listener (hostname with its own IP address, port or node) first.
  • Second authentication layer. Require TLS client certificates, a reverse-proxy SSO with MFA, or at least HTTP Basic authentication over HTTPS, with credentials separate from TCExam accounts.
  • Serve only what is needed. Under /admin/, serve only admin/code/*.php and admin/styles/, and deny admin/backup/, admin/log/, admin/doc/, admin/config*/ and public/log/. The admin pages also load files from shared/jscripts/, the shared/ styles and cache/.
  • Rate limiting, headers and logging. Rate-limit /admin/ requests and login POSTs, send X-Frame-Options: DENY, Referrer-Policy: no-referrer and Strict-Transport-Security, and write a dedicated access log for the admin host.
  • Split deployment (optional). On exam-delivery nodes that only serve public/, do not install admin/; run administration from an internal node against the same database.

The comparison of network options and the Apache, nginx, nftables and ufw examples are in the "Isolating the administration area" section of SECURITY.md.

Recommended hardening checklist

  • After installation, delete the install/ directory and set restrictive file permissions (see the Installation page).
  • Log in with the default admin / 1234 account, create a new level-10 administrator with a strong password and remove the default admin user immediately.
  • Serve TCExam over HTTPS and isolate the admin/ folder as described in Isolating the administration area.
  • Keep the admin/backup/ folder unreachable from the web (or move it elsewhere and set K_PATH_BACKUP in shared/config/tce_paths.php), and keep the .htaccess files of admin/backup/, admin/log/ and public/log/. On nginx, deny these three paths in the server block.
  • Keep cache/.htaccess in place and enable mod_headers with AllowOverride AuthConfig FileInfo (or All) for the TCExam folder. On nginx, add the equivalent cache/ rules listed in the Upgrade Guide.
  • When upgrading from a version older than 17.3.0, review the files already present in cache/ and the [object] tags in existing questions, as described in the Upgrade Guide.
  • When upgrading from a version older than 17.5.0, delete cache/latex_*.png to remove images generated from examinee text.
  • With CAS authentication, set K_CAS_SERVER_CA_CERT in shared/config/tce_cas.php so that the CAS server certificate is validated.
  • Keep PHP, the database and the Composer dependencies up to date.

For the full security policy and how to report a vulnerability, see SECURITY.md. Please report security issues privately instead of opening a public issue.