| Описание | Title: OpenCATS Career Portal candidate login: missing rate limiting and captcha, credential material stored in a client cookie
Vendor: opencats (https://github.com/opencats)
Product: OpenCATS (custom PHP + MySQL/mysqli, https://github.com/opencats/OpenCATS)
Affected version: 0.10.0 (CATS_VERSION, constants.php:27), master @ d5cf733f20d62b70dca1fcac23f64d6f2b021ff3 (2026-09-21)
Affected endpoints: POST /careers/index.php (applyToJobSubAction=processLogin; also pa=updateProfile, p=onRegisteredCandidateProfile)
Authentication required: none
Vendor notified: https://github.com/opencats/OpenCATS/issues/905
1. Summary
The candidate-facing Career Portal authenticates a candidate from a low-entropy verification triple — email address + last name + ZIP/postal code — submitted to POST /careers/index.php with applyToJobSubAction=processLogin. The request carries no password and no secret of any kind.
The design of that triple is a deliberate product decision and is not the subject of this report. This report covers four properties of the implementation around it, each of which appears to be unintended and each of which is independently exploitable:
1. No rate limiting. The credential check can be attempted at full request rate with no throttling, lockout, or backoff. 20 consecutive wrong-triple attempts and 20 consecutive correct-triple attempts all returned HTTP 200; no 429, no lockout, no rising latency.
2. No captcha on the login path. validateCareerPortalCaptcha() has exactly one call site in the codebase (modules/careers/CareersUI.php:807, inside the onApplyToJobOrder branch). Neither processLogin nor onRegisteredCandidateProfile calls it — the identity check is the one credential-checking path with no challenge.
3. The verification triple is written into a client cookie. The value issued as cats1cw is the credential; a client holding it authenticates without re-entering anything, and the triple is exposed verbatim to anything that can read or log the cookie.
4. No valid job-order context is required. The processLogin branch keys only off $_POST['applyToJobSubAction'], so the oracle is reachable at the portal root and its outcome is machine-readable from the presence of the cats1cw response header.
Because (1) and (4) remove both the throttling and the discovery cost, the triple can be enumerated automatically: email address and last name are commonly public information, and the ZIP code is a small search space.
2. Root Cause
File: modules/careers/CareersUI.php:520-548
Role: applyToJobSubAction=processLogin branch — runs the identity check and issues the identity cookie
File: modules/careers/CareersUI.php:2080-2084
Role: isCandidateRegistered()
File: modules/careers/CareersUI.php:2086-2185
Role: ProcessCandidateRegistration() — the SQL that matches on the triple
File: modules/careers/CareersUI.php:2136-2163
Role: assembled query; (LCASE(email1)=… OR LCASE(email2)=…) at :2158-2161
File: modules/careers/CareersUI.php:2151-2156
Role: $verificationFields < 1 — the check that defines the triple's fields
File: modules/careers/CareersUI.php:2171-2178
Role: @setcookie(...) — writes the triple into the client cookie
File: modules/careers/CareersUI.php:599
Role: job validation, which happens after authentication
File: modules/careers/CareersUI.php:804, :1169-1172
Role: captcha gate and careerPortalTemplateRequiresCaptcha()
File: modules/careers/CareersUI.php:807
Role: the only call site of validateCareerPortalCaptcha()
File: db/cats_schema.sql:388
Role: default template (21,'CATS 2.0','Content - Candidate Registration', …) — supplies <input-email>, <input-lastName>, <input-zip>, <input-rememberMe>
The triple's fields are taken from the template tags, and <input-new>, <input-registered>, <input-rememberMe> and <input-submit> are explicitly skipped (CareersUI.php:2096-2101), which is why the verification set is exactly email / last name / ZIP.
3. Prerequisites
The Career Portal and candidate registration must be enabled. Both are ordinary administrator settings (lib/CareerPortal.php:51-110 reads them from the settings table with settings_type = 4, i.e. SETTINGS_CAREER_PORTAL, constants.php:53).
No account, session, or prior knowledge beyond a candidate's email address, last name and ZIP code.
4. Reproduction
4.1 Identity check with no password and no captcha
POST /careers/index.php?m=careers&p=applyToJob&ID=1 HTTP/1.1
Host: <target>
Content-Type: application/x-www-form-urlencoded
Connection: close
applyToJobSubAction=processLogin&isNew=no&email=alice.smith%40example.com&lastName=Smith&zip=10001&rememberMe=yes
HTTP/1.1 200 OK
X-Powered-By: PHP/8.4.26
Set-Cookie: <session-cookie>; path=/
Set-Cookie: cats1cw=%22email%22%3D%22alice.smith%2540example.com%22%22lastName%22%3D%22Smith%22%22zip%22%3D%2210001%22; expires=Tue, 13 Oct 2026 10:39:05 GMT; Max-Age=1209600
Content-type: text/html; charset=UTF-8
The request contains no password field. The issued cookie decodes to the verification triple itself:
%22email%22%3D%22alice.smith%2540example.com%22%22lastName%22%3D%22Smith%22%22zip%22%3D%2210001%22
-> "email"="alice.smith%40example.com""lastName"="Smith""zip"="10001"
4.2 Control — same email and last name, wrong ZIP
POST /careers/index.php?m=careers&p=applyToJob&ID=1 HTTP/1.1
Host: <target>
Content-Type: application/x-www-form-urlencoded
applyToJobSubAction=processLogin&isNew=no&email=alice.smith%40example.com&lastName=Smith&zip=99999&rememberMe=yes
HTTP/1.1 200 OK
(no cats1cw cookie)
The status code is identical; the only difference is the presence of the cats1cw header. That header is therefore a clean boolean oracle.
4.3 Reaching the oracle without a valid job order
POST /careers/index.php HTTP/1.1
Host: <target>
Content-Type: application/x-www-form-urlencoded
applyToJobSubAction=processLogin&isNew=no&email=alice.smith%40example.com&lastName=Smith&zip=10001&rememberMe=yes
No m, no p, no ID — the identity check still runs and still issues the cookie. The same holds for an invalid ID=9999.
4.4 No rate limiting
20 consecutive WRONG-zip attempts : all HTTP 200
min 92.4 ms max 195.4 ms avg 135.3 ms
responses carrying an identity cookie : 0
20 consecutive CORRECT-triple attempts : 20/20 succeeded
min 77.5 ms max 164.6 ms avg 115.3 ms
No 429, no account lockout, no cumulative delay.
4.5 Cookie-only use of the issued credential
With only the cats1cw value and no other parameter:
GET /careers/index.php?m=careers&p=showAll&pa=updateProfile HTTP/1.1
Host: <target>
Cookie: cats1cw=%22email%22%3D%22alice.smith%2540example.com%22%22lastName%22%3D%22Smith%22%22zip%22%3D%2210001%22
HTTP/1.1 200 OK
The response is the victim's candidate profile form, pre-filled with the stored record — first name, last name, email, address, city, ZIP, work phone, key skills. All of those values are read from the database and returned to a client that supplied nothing but the cookie.
5. Confirmed Techniques
Identity established from email + last name + ZIP alone; the request contains no password and no captcha answer, and receives HTTP 200 plus an identity cookie.
Cookie value is the verification triple, URL-encoded, verbatim.
Boolean oracle on the cats1cw response header (present = match, absent = no match).
Oracle reachable at the portal root with no valid job-order ID.
40/40 attempts returned HTTP 200; no throttling, lockout or backoff observed.
validateCareerPortalCaptcha() located at a single call site via full-codebase search.
Cookie alone is sufficient to load a candidate's stored profile from the database.
6. Impact
Credential enumeration. With throttling, captcha and job-order context all absent, the three-field identity check can be automated. Email address and last name are routinely public; the ZIP code is a small space. An unauthenticated attacker can therefore establish candidate identity by guessing.
Exposure of the credential to the client. Because the triple is stored in the cookie, anyone who can read or log that cookie — a shared or public machine, an intermediary that records headers, a script-injection flaw elsewhere on the same origin, browser profile synchronisation — holds the credential, without ever knowing the ZIP code.
Account access. An authenticated candidate session exposes and lets the holder modify the candidate record, including personal contact details and attached documents.
7. Boundaries
Stated explicitly so that the impact is not overstated:
The verification triple is a deliberate product design, not a defect introduced by this version. It is defined by the shipped template (db/cats_schema.sql:388) and is described in the source as the equivalent of a password for the candidate portal. This report does not dispute that design; it is about throttling, challenge coverage, and client-side credential storage.
The Career Portal and candidate registration must be enabled by an administrator. Deployments that leave the portal off are unaffected.
The check compares the submitted values against stored candidate re |
|---|