Skip to content

Commit e24bb83

Browse files
jasnowRubySec CI
authored andcommitted
Updated advisory posts against rubysec/ruby-advisory-db@90e2384
1 parent bd7f829 commit e24bb83

File tree

1 file changed

+140
-0
lines changed

1 file changed

+140
-0
lines changed
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
---
2+
layout: advisory
3+
title: 'CVE-2025-65017 (decidim): Decidim''s private data exports can lead to data
4+
leaks'
5+
comments: false
6+
categories:
7+
- decidim
8+
advisory:
9+
gem: decidim
10+
cve: 2025-65017
11+
ghsa: 3cx6-j9j4-54mp
12+
url: https://github.com/decidim/decidim/security/advisories/GHSA-3cx6-j9j4-54mp
13+
title: Decidim's private data exports can lead to data leaks
14+
date: 2026-02-03
15+
description: |
16+
### Impact
17+
18+
Private data exports can lead to data leaks in cases where the UUID
19+
generation causes collisions for the generated UUIDs.
20+
21+
The bug was introduced by #13571 and affects Decidim versions 0.30.0
22+
or newer (currently 2025-09-23).
23+
24+
This issue was discovered by running the following spec several
25+
times in a row, as it can randomly fail due to this bug:
26+
27+
```bash
28+
$ cd decidim-core
29+
$ for i in {1..10}; do bundle exec rspec
30+
spec/jobs/decidim/download_your_data_export_job_spec.rb
31+
-e "deletes the" || break ; done
32+
```
33+
34+
Run the spec as many times as needed to hit a UUID that converts
35+
to `0` through `.to_i`.
36+
37+
The UUID to zero conversion does not cause a security issue but
38+
the security issue is demonstrated with the following example.
39+
40+
The following code regenerates the issue by assigning a predefined
41+
UUID that will generate a collision (example assumes there are
42+
already two existing users in the system):
43+
44+
```ruby
45+
# Create the ZIP buffers to be stored
46+
buffer1 = Zip::OutputStream.write_buffer do |out|
47+
out.put_next_entry("admin.txt")
48+
out.write "Hello, admin!"
49+
end
50+
buffer1.rewind
51+
buffer2 = Zip::OutputStream.write_buffer do |out|
52+
out.put_next_entry("user.txt")
53+
out.write "Hello, user!"
54+
end
55+
buffer2.rewind
56+
57+
# Create the private exports with a predefined IDs
58+
user1 = Decidim::User.find(1)
59+
export = user1.private_exports.build
60+
export.id = "0210ae70-482b-4671-b758-35e13e0097a9"
61+
export.export_type = "download_your_data"
62+
export.file.attach(io: buffer1, filename: "foobar.zip",
63+
content_type: "application/zip")
64+
export.expires_at = Decidim.download_your_data_expiry_time.from_now
65+
export.metadata = {}
66+
export.save!
67+
68+
user2 = Decidim::User.find(2)
69+
export = user2.private_exports.build
70+
export.id = "0210d2df-a0c7-40aa-ad97-2dae5083e3b8"
71+
export.export_type = "download_your_data"
72+
export.file.attach(io: buffer2, filename: "foobar.zip",
73+
content_type: "application/zip")
74+
export.expires_at = Decidim.download_your_data_expiry_time.from_now
75+
export.metadata = {}
76+
export.save!
77+
```
78+
79+
Expect to see an error in the situation.
80+
81+
Now, login as user with ID 1, go to `/download_your_data`, click
82+
"Download file" from the export and expect to see the data that
83+
should be attached to user with ID 2. This is an artificially
84+
replicated situation with the predefined UUIDs but it can easily
85+
happen in real situations.
86+
87+
The reason for the test case failure can be replicated in case
88+
you change the export ID to
89+
`export.id = "e9540f96-9e3d-4abe-8c2a-6c338d85a684"`.
90+
This would return `0` through `.to_s`
91+
92+
After attaching that ID, you can test if the file is available
93+
for the export:
94+
95+
```ruby
96+
user.private_exports.last.file.attached?
97+
=> false
98+
user.private_exports.last.file.blob
99+
=> nil
100+
```
101+
102+
Note that this fails with such UUID as shown in the example and
103+
could easily lead to collisions in case the UUID starts with a
104+
number. E.g. UUID `"0210ae70-482b-4671-b758-35e13e0097a9"` would
105+
convert to `210` through `.to_s`. Therefore, if someone else has
106+
a "private" export with the prefixes "00000210", "0000210",
107+
"000210", "00210", "0210" or "210", that would cause a collision
108+
and the file could be attached to the wrong private export.
109+
110+
Theoretical chance of collision (the reality depends on the
111+
UUID generation algorithm):
112+
113+
- Potential combinations of the UUID first part (8 characters hex): 16^8
114+
- Potentially colliding character combinations (8 numbers
115+
characters in the range of 0-9): 10^8
116+
- 10^8 / 16^8 ≈ 2.3 (23 / 1000 users)
117+
118+
The root cause is that the class `Decidim::PrivateExport` defines
119+
an ActiveStorage relation to `file` and the table
120+
`active_storage_attachments` stores the related `record_id` as
121+
`bigint` which causes the conversion to happen.
122+
123+
### Workarounds
124+
125+
Fully disable the private exports feature until a patch is available.
126+
cvss_v4: 8.2
127+
unaffected_versions:
128+
- "< 0.30.0"
129+
patched_versions:
130+
- "~> 0.30.4"
131+
- ">= 0.31.0"
132+
related:
133+
url:
134+
- https://nvd.nist.gov/vuln/detail/CVE-2025-65017
135+
- https://github.com/decidim/decidim/security/advisories/GHSA-3cx6-j9j4-54mp
136+
- https://github.com/decidim/decidim/releases/tag/v0.31.0
137+
- https://github.com/decidim/decidim/releases/tag/v0.30.4
138+
- https://github.com/decidim/decidim/pull/13571
139+
- https://github.com/advisories/GHSA-3cx6-j9j4-54mp
140+
---

0 commit comments

Comments
 (0)