{"id":9287,"date":"2019-09-06T15:08:12","date_gmt":"2019-09-06T18:08:12","guid":{"rendered":"http:\/\/blog.plataformatec.com.br\/?p=9287"},"modified":"2019-09-09T10:09:08","modified_gmt":"2019-09-09T13:09:08","slug":"improve-confirmation-token-validation-in-devise-cve-2019-xxxx","status":"publish","type":"post","link":"http:\/\/blog.plataformatec.com.br\/2019\/09\/improve-confirmation-token-validation-in-devise-cve-2019-xxxx\/","title":{"rendered":"Improve confirmation token validation in Devise (CVE-2019-16109)"},"content":{"rendered":"\n
Devise version We received a security report saying that it was possible to confirm records with a blank This only happens if there are records with a blank confirmation token in the database<\/strong>. This is because of the way the method In summary, before version Notice that the SQL query is trying to find users with It’s also worth mentioning that only unconfirmed records<\/strong> – i.e. with Considering that there are records with an empty A more sensible scenario is for applications that automatically sign in accounts after confirmation. That would not only confirm someone’s account but also give an attacker access to it. Although this feature is not included in Devise, we know that some applications might have it.<\/p>\n\n\n\n For already confirmed records – i.e. with a The solution was to validate whether the And the end-user will see the validation error on the screen.<\/p>\n\n\n\n See the pull request<\/a> with the solution for more information.<\/p>\n\n\n\n Aside from updating Devise, you might also want to check whether you have records in that state in your application. You can do that with a query like this one:<\/p>\n\n\n If you get results out of this query, you might want to nullify them to avoid the confirmation by mistake:<\/p>\n\n\n Although we received a report where someone worked in an application that had records with a blank confirmation token in the database, no code or use case was found inside Devise that would make records end up in that state.<\/p>\n\n\n\n If you find a case where this happens, please contact us at opensource@plataformatec.com.br<\/a> and we’ll look at it.<\/p>\n\n\n\n Finally, we want to thank Anthony Mangano<\/a> for reporting this issue and helping with the solution.<\/p>\n","protected":false},"excerpt":{"rendered":" Devise version 4.7.1 was released with a fix for an edge case that could confirm accounts by mistake. We’ll explain now in details what is the issue, how it was fixed and which actions you might want to take in your applications. Description We received a security report saying that it was possible to confirm … \u00bb<\/a><\/p>\n","protected":false},"author":54,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[1],"tags":[36,7],"aioseo_notices":[],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/9287"}],"collection":[{"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/users\/54"}],"replies":[{"embeddable":true,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/comments?post=9287"}],"version-history":[{"count":10,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/9287\/revisions"}],"predecessor-version":[{"id":9300,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/9287\/revisions\/9300"}],"wp:attachment":[{"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/media?parent=9287"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/categories?post=9287"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/tags?post=9287"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}4.7.1<\/code> was released with a fix for an edge case that could confirm accounts by mistake. We’ll explain now in details what is the issue, how it was fixed and which actions you might want to take in your applications.<\/p>\n\n\n\n
Description<\/h2>\n\n\n\n
confirmation_token<\/code> parameter. In order words, hitting the following URL
\/users\/confirmation?confirmation_token=<\/code> would successfully confirm a user instead of showing a validation error – e.g.
Confirmation token can't be blank<\/code>.<\/p>\n\n\n\n
find_first_by_auth_conditions<\/a><\/code> works (which is similar to ActiveRecord’s
#find_by<\/code>). It is important to mention that we haven’t found a case where Devise sets
confirmation_token<\/code> to an empty string.<\/p>\n\n\n\n
4.7.1<\/code>, this is what would happen after hitting the confirmation endpoint with a blank
confirmation_token<\/code>:<\/p>\n\n\n
Started GET \"\/users\/confirmation?confirmation_token=\"<\/span> for<\/span> ::1<\/span> at 2019<\/span>-09<\/span>-05<\/span> 10<\/span>:24<\/span>:29<\/span> -0300<\/span>\nProcessing by Devise::ConfirmationsController#show as HTML<\/span>\n Parameters: {\"confirmation_token\"<\/span>=>\"\"<\/span>}\n User Load (1.0<\/span>ms) SELECT \"users\"<\/span>.* FROM \"users\"<\/span> WHERE \"users\"<\/span>.\"confirmation_token\"<\/span> = $1<\/span> ORDER BY \"users\"<\/span>.\"id\"<\/span> ASC LIMIT $2<\/span> [[\"confirmation_token\"<\/span>, \"\"<\/span>], [\"LIMIT\"<\/span>, 1<\/span>]]<\/code><\/div>Code language:<\/span> PHP<\/span> (<\/span>php<\/span>)<\/span><\/small><\/pre>\n\n\n
confirmation_token = \"\"<\/code>.<\/p>\n\n\n\n
confirmed_at: nil<\/code> in the database – would be confirmed in this case. For already confirmed users, a validation error (
Email was already confirmed, please try signing in<\/code>) would be displayed.<\/p>\n\n\n\n
Possible implications<\/h2>\n\n\n\n
confirmation_token<\/code> in the database, a request sending a blank parameter would confirm the first record found in the database. This means that someone’s account would be confirmed by mistake.<\/p>\n\n\n\n
confirmed_at<\/code> date in the database – the validation error would be displayed, but their email would be leaked to the end user inside the form’s input.<\/p>\n\n\n\n
Solution<\/h2>\n\n\n\n
confirmation_token<\/code> is empty before doing any query in the database. So now, when the same endpoint is hit, nothing happens:<\/p>\n\n\n
Processing by Devise::ConfirmationsController#show as HTML<\/span>\n Parameters: {\"confirmation_token\"<\/span>=>\"\"<\/span>}\nCompleted 200<\/span> OK in 371<\/span>ms (Views: 339.0<\/span>ms | ActiveRecord: 5.9<\/span>ms)<\/code><\/div>Code language:<\/span> PHP<\/span> (<\/span>php<\/span>)<\/span><\/small><\/pre>\n\n\n
Actions you might want to take<\/h2>\n\n\n\n
irb(main):001<\/span>:0<\/span>> User.where(confirmation_token: \"\"<\/span>)\n User Load (0.6<\/span>ms) SELECT \"users\"<\/span>.* FROM \"users\"<\/span> WHERE \"users\"<\/span>.\"confirmation_token\"<\/span> = $1<\/span> LIMIT $2<\/span> [[\"confirmation_token\"<\/span>, \"\"<\/span>], [\"LIMIT\"<\/span>, 11<\/span>]]\n=> #<ActiveRecord::Relation []><\/span><\/code><\/div>Code language:<\/span> PHP<\/span> (<\/span>php<\/span>)<\/span><\/small><\/pre>\n\n\n
irb(main):002<\/span>:0<\/span>> User.where(confirmation_token: \"\"<\/span>).update(confirmation_token: nil)\n User Load (0.4<\/span>ms) SELECT \"users\"<\/span>.* FROM \"users\"<\/span> WHERE \"users\"<\/span>.\"confirmation_token\"<\/span> = $1<\/span> [[\"confirmation_token\"<\/span>, \"\"<\/span>]]\n (0.2<\/span>ms) BEGIN\n User Update (0.7<\/span>ms) UPDATE \"users\"<\/span> SET \"confirmation_token\"<\/span> = $1<\/span>, \"updated_at\"<\/span> = $2<\/span> WHERE \"users\"<\/span>.\"id\"<\/span> = $3<\/span> [[\"confirmation_token\"<\/span>, nil], [\"updated_at\"<\/span>, \"2019-09-05 14:03:20.857488\"<\/span>], [\"id\"<\/span>, 1<\/span>]]\n (1.2<\/span>ms) COMMIT<\/code><\/div>Code language:<\/span> JavaScript<\/span> (<\/span>javascript<\/span>)<\/span><\/small><\/pre>\n\n\n
Causes<\/h2>\n\n\n\n