Security Hall of Fame
Valid, previously-unknown reports under our bug bounty program. Summaries are published after a fix; we do not disclose tokens, personal data or exploit steps.
Bug bounty program — scope, rules and rewards
Anupam Giri
- HighFixeddjangoeurope message queue
Cross-tenant RabbitMQ credential attachment
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H = 7.5, High
CWE-639 · CWE-862 — CWE-639 (Authorization Bypass Through User-Controlled Key / IDOR). CWE-862 (Missing Authorization) is the missing ownership check on the vhost.
POST /rabbitmquser/ did not check that the caller owned the vhost named in the request body, so any authenticated account could attach a credential of its own choosing to any vhost whose UUID it knew.
Confirmed, and accepted at High. We take the reporter’s word on the AMQPS step and did not need to re-run it. We debated Medium internally, on the grounds that an attacker needs a vhost UUID and the report does not demonstrate a way to obtain one. That second point is fair, and we checked it ourselves rather than assume: the id of the RabbitMQ instance is a UUID4 id. A UUID4 is 122 bits of random space, so guessing or enumerating one is impractical, and taking over another customer’s RabbitMQ account this way is very hard. The one broadly readable RabbitMQ endpoint exposes clusters without their vhosts, so we found no disclosure path either. Fixed: attaching credentials now requires that the caller owns the vhost.
- MediumFixeddjangoeurope message queue
Deleted RabbitMQ credentials remain live at the broker
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H = 6.6, Medium
CWE-459 · CWE-613 — CWE-459 (Incomplete Cleanup). CWE-613 (Insufficient Session Expiration) covers deleted AMQP logins remaining valid at the broker.
DELETE /rabbitmquser/ removed the credential from the control panel without revoking it at the RabbitMQ broker, so a deleted AMQP login could keep working.
Holds. Classified as Medium. Deleting a credential in the panel removed the database row and sent nothing to the broker. The broker only dropped logins missing from a full user-list save, so a deleted AMQP login stayed live until someone else on the same cluster saved a credential — on a quiet cluster that can take a long time. PR:H because the attacker must already have held a working credential for that exact vhost; the flaw is that revocation did not take effect, not that anyone gained access they never had. AC:H because the next full sync of the cluster removes it, a condition the attacker does not control. While it lasts, the credential has full read, write and delete on the vhost. A different root cause from the first finding, so not a duplicate. A later vhost-level follow-up is the same root cause and the same fix, and is not scored separately. A delete now syncs the cluster user list, so the login is revoked at the broker.
- LowFixeddjangoeurope profile
Unauthenticated profile photos with EXIF location data
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N = 3.7, Low
CWE-552 · CWE-212 · CWE-459 — CWE-552 (Files or Directories Accessible to External Parties). CWE-212 (Improper Removal of Sensitive Information Before Storage or Transfer) covers EXIF location left on the file. CWE-459 (Incomplete Cleanup) covers the file remaining after the profile photo was cleared.
Profile photos were stored under the original filename and served without a login. EXIF location data was left on the file. Clearing a photo from the profile did not delete it, so the file stayed reachable.
Fixed. Classified as Low. There was no profile-picture upload form in the front-end, so reaching the upload path required more than using the product as shipped (AC:H). Photos are stored under a non-guessable name, EXIF location data is stripped on upload, and removing a photo from the profile deletes the stored file.
- LowFixedwcenter / djangoeurope payments
Bank-transfer payment reference taken from the URL
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:L/A:N = 3.1, Low — limited impact.
CWE-639 · CWE-472 — CWE-639 (Authorization Bypass Through User-Controlled Key / IDOR). CWE-472 (External Control of Assumed-Immutable Web Parameter) covers taking CID and reference from the URL.
The bank-transfer page showed CID and payment reference from the URL query string, with no check that those values belong to the logged-in account. A crafted link could make a logged-in customer see someone else’s reference and CID and put them on a real bank transfer.
Holds. Accepted at Low, not Medium. Bank transfers are matched by hand, so a wrong reference misdirects nothing by itself. For the attack to pay off, a customer would have to follow a crafted link and send a real transfer, and we would then have to credit it to an account whose holder does not match the sender. Even then the attacker gains panel credit, not money, and the first complaint about a missing payment exposes and reverses it. I:H is not defensible: nothing the attacker controls is written anywhere. AC:H because success depends on that manual booking — a condition outside the attacker’s control. The page now shows a reference and CID only when they come from one of the user’s own billing records; a crafted link can no longer make the page show anything the user could not already see. Our thanks for a careful report that was honest about what it could not see.
Raj Chaudhary
- LowFixedwcenter / djangoeurope API
Unauthenticated GET /server/ returned every shared server
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N = 0.0, Low under our programme. CVSS 0.0 with C:N.
CWE-862 · CWE-863 — CWE-862 (Missing Authorization). CWE-863 (Incorrect Authorization) covers is_public being treated as a cosmetic filter rather than an access check.
Signed out, GET /server/ applied the start page’s is_public selection only when the query asked for it, so omitting the parameter returned every shared server. GET /server/<id>/ answered for a server the picker does not list. ?is_public=0 returned the public set because the value was only tested for truthiness. Writes on /server/ already returned 401.
Holds as a bug. Classified as Low. The published data contains host-key fingerprints and phpMyAdmin and phpPgAdmin URLs; those data are already public. Host-key fingerprints are public by design: sshd sends them to anyone who connects, and a fingerprint does not help a man in the middle produce a matching key. phpMyAdmin and phpPgAdmin URLs are customer login pages on public DNS names, protected by database credentials. Data centres and providers can be read from IP whois. Hardware, OS and package versions are shown to customers. A server id gives no access. Signed-in customers see every shared server by design; account creation is open, so the bug saved one signup. What the bug bypassed is the start page’s selection of servers, not an access control around information. Confidentiality is none (C:N): nothing returned is restricted. We still rate it Low under our programme because the endpoint did not do what the code intended, and the is_public=0 quirk was a real defect. Fixed: signed out, the list and the detail are limited to the servers on the start page; an unlisted server’s id returns 404. Signed in, nothing changes. Both flags are read as booleans. No serializer fields were removed. The fix goes out with the next release.
Pramod Kumar Ravela
- MediumFixedwcenter / djangoeurope
Password reset token not invalidated after use
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N = 6.8, Medium — top of the band, just under High.
CWE-640 · CWE-613 — CWE-640 (Weak Password Recovery Mechanism for Forgotten Password). CWE-613 (Insufficient Session Expiration) covers the token remaining valid after a successful change.
Reported that a password reset token was not invalidated after a successful password change, allowing a reset link to be replayed for the remainder of its two-hour lifetime.
Reset tokens are now strictly single-use. The report also prompted a wider review of our password reset flow, which led us to enforce our password policy on that path, sign out existing browser sessions on reset, invalidate superseded reset links, and add rate limiting to the endpoint. Our thanks for a clearly written and reproducible report.
Gaurang Maheta
- MediumFixeddjangoeurope registration, password reset, trial accounts and contact form
Client-side-only registration anti-bot check
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N = 5.3, Medium
CWE-602 · CWE-804 — CWE-602 (Client-Side Enforcement of Server-Side Security). CWE-804 (Guessable CAPTCHA) is the more precise fit and is cited alongside it.
The registration anti-spam check could be computed entirely in the client — no server secret, nonce or expiry — so it did not stop automated account creation.
Accepted in full. Registration, password reset, trial-account creation and the contact form now require a server-issued proof-of-work challenge bound to the request: signed and scoped by our own infrastructure, with an expiry, single-use, and not forgeable offline. We use self-hosted proof-of-work rather than a third-party CAPTCHA, so there is no extra processor, cookie or fingerprinting. Proof-of-work is not a hard barrier; rate limits remain the main ceiling on abuse. The old client-side gate is being removed in a staged change once this has been stable. Our thanks for a clean, correct report that got a long-standing issue fixed.
- MediumFixeddjangoeurope registration & password reset
Missing rate limits on registration and password reset
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N = 6.4
CWE-770 · CWE-799 — CWE-770 (Allocation of Resources Without Limits or Throttling). CWE-799 (Improper Control of Interaction Frequency) is the frequency-control view of the same gap.
Login is throttled, but registration and password-reset were not. Registration could create accounts and send welcome mail without a cap; password reset sent mail on every request, so one inbox could be flooded.
Accepted at Medium. The reported vector is unchanged. The harm is mail volume and sending reputation, which CVSS models poorly because the impact falls on a third party’s inbox rather than on our own system; that does not change the rating. The fix is deployed. Registration cannot bomb a single address — one welcome mail per address — but could send to many distinct addresses; password reset could flood one inbox. Both endpoints now have rate limits, including a per-target cap on reset mail so rotating IPs against one inbox no longer works. Reset tokens remain strictly single-use, independently of those limits. Our thanks for a clear and reproducible report; it prompted us to review the whole unauthenticated surface around registration and password reset.
- MediumFixeddjangoeurope registration
Unauthenticated customer-email enumeration
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N = 5.3
CWE-204 — CWE-204 (Observable Response Discrepancy).
The registration endpoint returned different error messages for already-registered versus unknown email addresses, so an unauthenticated caller could confirm which addresses were customers.
The oracle is closed. Anonymous registration now answers the same way whether the address is new or already a customer; a duplicate creates nothing. The address owner may receive a notice that an account already exists, linking to the password-reset page with no reset token. Our thanks for reporting this alongside the rate-limit finding.
Peter Levashov
- LowFixedwcenter / djangoeurope API
Owner cannot revoke scoped API keys
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N = 3.7, Low
CWE-863 · CWE-284 — CWE-863 (Incorrect Authorization) covers checking the target key’s capabilities instead of the caller’s. CWE-284 (Improper Access Control) covers the token-lifecycle gap, including the expiry PATCH that ignored stored permissions.
DELETE and PATCH /token/<id>/ checked whether the key being changed could manage API keys, instead of whether the caller could. A key with a permissions object never has that flag, so the owner was refused for every scoped key. Only full-access keys could be changed or deleted. The keys remained usable after those attempts. Impact stayed within the key’s assigned scope: the owner could not terminate a compromised scoped credential.
Confirmed. Classified as Low. AC:H because the attacker must already hold a key leaked by some other route; the bug only kept open a leak that the owner could not close themselves. Support could still delete any key on request. A second defect sat behind the 403: even with that check corrected, a PATCH carrying only a new expiry would have been refused with "Permission option required.", because the stored permissions were not taken into account. Both are fixed. The owner can now change and delete every key of theirs, whatever the key itself may do. Scoped keys still cannot manage keys. Regression tests cover scoped and full-access keys, with and without an expiry, can_manage_tokens true and false, and the reverse direction (a scoped key cannot delete, widen or create keys). API keys are kept alive on a password reset on purpose, so a routine forgot-password does not break customer automation; the password-changed mail lists the remaining keys and asks the customer to delete any they do not recognise. For scoped keys that deletion was the step this finding showed to be broken. The fix goes out with the next release.
Want to be listed? Report a finding through the bug bounty program.