The /reports/:id.json endpoint discloses potentially sensitive user attributes when reporter summary is present
Timeline
Hi
The.json endpoint of any disclosed report is leaking reporter's email, OTP backup codes, reporter's phone number, "graphql_secret_token", tshirt size all the reporter account's internal details etc.
Code 51 BytesUnwrap lines Copy Download
1 GET /reports/█████.json HTTP/2
2Host: hackerone.com
- I was checking Hackerone's disclosed report ██████████ and suddenly during check found .json point is leaking too much data of reporter
████. I immediately reported it to you.
█████
- PoC:- Leakage of data of reporter
█████
█████
Impact
Reporter H1 account private data disclosed
posted a comment.
February 20, 2025, 4:21pm UTCAnyone listening?
posted a comment.
February 21, 2025, 2:59pm UTCNot want to spam here but this report's severity is Critical, It should be remediated faster. Please check this report soon.
HackerOne staff
posted a comment. Thanks, we're on it.
HackerOne staff
changed the status to Triaged. Thanks again, @avinash_, we were able to reproduce the issue.
HackerOne staff
changed the status to RetestingThe reporter has been invited to perform a retest for $100.
We've deployed a fix. Would you mind checking whether you can still reproduce the vulnerability?
completed a retest.
Updated February 21, 2025, 8:49pm UTCRetest finding result
Are you able to reproduce the vulnerability report?
No, the fix works.Please provide a short summary with the results of your retest
I went to the same report and found the data is no more getting leaked under "summaries" section, in summaries section it is now detaling only few info about team/reporter like name, bio etc which is always in public. This new behaviour is intended and applicable for both Team and reporter. It seems it is fixed.██████████
posted a comment.
Updated February 21, 2025, 8:50pm UTCHi @jobert - Thank you fo your super duper quick response and fix. The fix is too fast it is really appreciable.
Thank you for redacting sensitive things in report but Please redact the report id:
███ mentoned, it will show other peoples which user's data was leaked and mentioned in this report.Please also redact the screenshot mentioned in "restest" summary.
Thanks again
Thanks for confirming the fix!
HackerOne staff
closed the report and changed the status to Resolved. This was an awesome find, well done! You submitted this vulnerability less than 90 minutes after the change was deployed to production. We will follow up in the next few days with more details about the vulnerability.
HackerOne staff
changed the report title from CRITICAL- Leakage of reporter's email, OTP backup codes, password change dates, phone number etc with disclosed report .json endpoint to The /reports/:id.json endpoint discloses potentially sensitive user attributes when reporter summary is present. posted a comment.
February 21, 2025, 9:07pm UTC@jobert - Surprised, Thank you so much for this amazing bounty. 😊
HackerOne staff
added weakness "Information Disclosure". posted a comment.
Updated April 1, 2025, 6:09pm UTCHi @jobert - I went through some of disclosed reports of H1, I've noticed one thing:- When in a report, there is summary written from team side then on opening .json endpoint of that report it is disclosing which participant from team member has posted the summary. For ex:-
The report:- https://hackerone.com/reports/2598548 , A summary has provided by Hackerone, UI shows it as:- "Summary By Hackerone"
On opening .json endpoint of report, It is disclosing which participant from team side has posted the summary.
████████
Is disclosing this information publicly an intended behaviour?
Thanks!
- F4080488: Screenshot_2025-02-22_at_4.12.48_PM.png
HackerOne staff
posted a comment. No, that's intentional. Thanks for checking!
HackerOne staff
posted a comment. Hey @avinash_, we wanted to provide some context on the root cause of this issue and why it wasn't caught through automated testing.
On February 19, 2025 HackerOne upgraded from Rails 6.1.7.9 to Rails 7.1.5.1. Both versions offer the ability to serialize Ruby hashes into JSON. There is a slight difference between the way this works in both versions. Let’s assume we have the following Ruby hash:
Code 64 BytesUnwrap lines Copy Download
1hash = {
2 my_key: 'my_value',
3 'my_key' => 'my_other_value',
4}
This hash contains two keys with the same name, but one key is a symbol and the other a string. You can access them independently, as such:
Code 67 BytesUnwrap lines Copy Download
1hash[:my_key]
2# => 'my_value'
3
4hash['my_key']
5# => 'my_other_value'
If
to_json is called on this hash in Rails 6.1.7.9, the duplicate key is the one that is used in the JSON output:Code 45 BytesUnwrap lines Copy Download
1hash.to_json
2# => {"my_key":"my_other_value"}
However, in Rails 7.1.5.1, this behavior changed due to the implementation of the
ActiveSupport::JSON.encode method. Here is the output in Rails 7.1.5.1, which shows that the JSON output contains a duplicate key due to the fact that JSON does not distinguish between symbols and strings and ActiveSupport now treats them as unique keys:Code 65 BytesUnwrap lines Copy Download
1hash.to_json
2# => {"my_key":"my_value","my_key":"my_other_value"}
This resulted in exposing sensitive data on the
/reports/:id.json endpoint. Here is an excerpt of the jbuilder template that serializes report summaries:Code 149 BytesUnwrap lines Copy Download
1json.merge!(summary.json_attributes)
2
3# ...
4
5if summary.persisted?
6 # …
7 json.user do
8 json.partial!('users/user', user: summary.user)
9 end
10end
The
summary.json_attributes method was implemented as following:Code 203 BytesUnwrap lines Copy Download
1class ReportSummary < ApplicationRecord
2 # ...
3 belongs_to :user
4 # ...
5 def json_attributes
6 {
7 id:,
8 category:,
9 content:,
10 updated_at:,
11 user:,
12 }
13 end
14
15 # ...
16end
The
json_attributes method returns an instance of a User object with a symbol as its key, called “user”. When merging this into the jbuilder object, it implicitly calls to_json on this object, which serializes all the user attributes. These user attributes contain sensitive information such as email, unverified account recovery phone number, account backup codes, and GraphQL secret token.When the template is being generated, it eventually calls
json.user with a partial. The key that is appended to the hash is a string, not a symbol. In Rails 6.1.7.9, when the final hash would be serialized to a JSON string, it would only retain the key that was added last, which was the sanitized user partial. However, after the upgrade to Rails 7.1.5.1, it would retain both keys and, thus, the final JSON would contain a duplicate key; one with the user attributes and one with the user partial. At HackerOne we have many automated tests, including tests that verify the JSON structure of this particular endpoint. However, none of these tests failed, which was unexpected. We use the approvals gem to verify JSON structures, often using code that looks similar to this:
Code 38 BytesUnwrap lines Copy Download
1verify(format: :json) { subject.body }
We verified that subject.body after the upgrade did indeed return a duplicate key. The reason why none of the tests failed is because verify parses the JSON again in order to normalize information like IDs, which may change between tests. When parsing JSON in Ruby, only the last instance of a duplicate key is returned:
Code 93 BytesUnwrap lines Copy Download
1JSON.parse('{"my_key":"my_value","my_key":"my_other_value"}')
2=> {"my_key"=>"my_other_value"}
This means that even though the view returned a duplicate key, tests didn’t fail because it would only see the last instance, which was the exact shape of the object prior to the upgrade.
posted a comment.
Updated February 28, 2025, 7:58am UTCHi @jobert - Sorry for the delay,
Thank you for providing such a great explanation about how and why this issue emerged?, So the villain here was the rails 7.1.5.1 implemented hash key-value pair, As I understood, The main issue is that JSON was not differentiate between symbols and string keys, leading to duplicate keys appearing in the serialized output. This change resulted in exposing sensitive user data at report's .json endpoint.
We use the approvals gem to verify JSON structures, often using code that looks similar to this:Code 38 BytesUnwrap lines Copy Download1verify(format: :json) { subject.body }
The approval gem parses json structure and comparing them with previous body here? , which is helping to compare and track if there is more data(sensitive/non-sensitive) is getting disclosed or not? Right?
I've few questions:-
-
Instead of doing single check Can HackerOne add additional checks which inspect raw JSON output here and other json endpoints? or this single "approvals gem" check is sufficient?
-
As you've indicated about
to_json, Why HackerOne is not usingas_jsoninstead ofto_jsonto avoid unexpected serialization behavior? Sinceas_jsonwill return a hash before serialization and does not retain duplicate keys. I'm only asking this because I got some information about both calls in this blog: https://jonathanjulian.com/2010/04/rails-to_json-or-as_json/ -
(Optional question): Am I eligible for any swag? 😂
Again Thank you for all of your responses and clarification in this issue.
Regards,
requested to disclose this report.
March 11, 2025, 5:53am UTCHi @jobert - If everything is Ok then Let's disclose it, Thanks!
HackerOne staff
agreed to disclose this report. Hi @avinash_
We've publicly disclosed your report on HackerOne. This aligns with our dedication to transparency and improving overall security. Thank you for your valuable contribution to a safer internet!
This report has been disclosed.
April 1, 2025, 6:23pm UTC has locked this report.
April 1, 2025, 7:50pm UTC HackerOne staff
posted a comment. Hi Avinash, about your swag question: Please check your email inbox. If you don't see anything there, please let me know.