mirror of
https://github.com/discourse/discourse.git
synced 2024-12-16 23:03:44 +08:00
SECURITY: Limit passwords to 200 characters
Prevents layer 8 attack.
This commit is contained in:
parent
1f797d5d31
commit
3d3313d5ee
|
@ -54,6 +54,8 @@ class SessionController < ApplicationController
|
||||||
params.require(:login)
|
params.require(:login)
|
||||||
params.require(:password)
|
params.require(:password)
|
||||||
|
|
||||||
|
return invalid_credentials if params[:password].length > User.max_password_length
|
||||||
|
|
||||||
login = params[:login].strip
|
login = params[:login].strip
|
||||||
login = login[1..-1] if login[0] == "@"
|
login = login[1..-1] if login[0] == "@"
|
||||||
|
|
||||||
|
|
|
@ -151,6 +151,11 @@ class UsersController < ApplicationController
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if params[:password] && params[:password].length > User.max_password_length
|
||||||
|
render json: { success: false, message: I18n.t("login.password_too_long") }
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
user = User.new(user_params)
|
user = User.new(user_params)
|
||||||
|
|
||||||
authentication = UserAuthenticator.new(user, session)
|
authentication = UserAuthenticator.new(user, session)
|
||||||
|
@ -221,12 +226,17 @@ class UsersController < ApplicationController
|
||||||
if !@user
|
if !@user
|
||||||
flash[:error] = I18n.t('password_reset.no_token')
|
flash[:error] = I18n.t('password_reset.no_token')
|
||||||
elsif request.put?
|
elsif request.put?
|
||||||
raise Discourse::InvalidParameters.new(:password) unless params[:password].present?
|
@invalid_password = params[:password].blank? || params[:password].length > User.max_password_length
|
||||||
@user.password = params[:password]
|
|
||||||
@user.password_required!
|
if @invalid_password
|
||||||
if @user.save
|
@user.errors.add(:password, :invalid)
|
||||||
Invite.invalidate_for_email(@user.email) # invite link can't be used to log in anymore
|
else
|
||||||
logon_after_password_reset
|
@user.password = params[:password]
|
||||||
|
@user.password_required!
|
||||||
|
if @user.save
|
||||||
|
Invite.invalidate_for_email(@user.email) # invite link can't be used to log in anymore
|
||||||
|
logon_after_password_reset
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
render layout: 'no_js'
|
render layout: 'no_js'
|
||||||
|
|
|
@ -111,6 +111,10 @@ class User < ActiveRecord::Base
|
||||||
LAST_VISIT = -2
|
LAST_VISIT = -2
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.max_password_length
|
||||||
|
200
|
||||||
|
end
|
||||||
|
|
||||||
def self.username_length
|
def self.username_length
|
||||||
SiteSetting.min_username_length.to_i..SiteSetting.max_username_length.to_i
|
SiteSetting.min_username_length.to_i..SiteSetting.max_username_length.to_i
|
||||||
end
|
end
|
||||||
|
@ -669,6 +673,7 @@ class User < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def hash_password(password, salt)
|
def hash_password(password, salt)
|
||||||
|
raise "password is too long" if password.size > User.max_password_length
|
||||||
Pbkdf2.hash_password(password, salt, Rails.configuration.pbkdf2_iterations, Rails.configuration.pbkdf2_algorithm)
|
Pbkdf2.hash_password(password, salt, Rails.configuration.pbkdf2_iterations, Rails.configuration.pbkdf2_algorithm)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,8 @@
|
||||||
|
|
||||||
<%=form_tag({}, method: :put) do %>
|
<%=form_tag({}, method: :put) do %>
|
||||||
<p>
|
<p>
|
||||||
<input id="user_password" name="password" size="30" type="password">
|
<input id="user_password" name="password" size="30" type="password" maxlength="<%= User.max_password_length %>">
|
||||||
|
<label><%= t('js.user.password.instructions', count: SiteSetting.min_password_length) %></label>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<%=submit_tag( @user.has_password? ? t('password_reset.update') : t('password_reset.save'), class: 'btn')%>
|
<%=submit_tag( @user.has_password? ? t('password_reset.update') : t('password_reset.save'), class: 'btn')%>
|
||||||
|
|
|
@ -1088,6 +1088,7 @@ en:
|
||||||
omniauth_error: "Sorry, there was an error authorizing your %{strategy} account. Perhaps you did not approve authorization?"
|
omniauth_error: "Sorry, there was an error authorizing your %{strategy} account. Perhaps you did not approve authorization?"
|
||||||
omniauth_error_unknown: "Something went wrong processing your log in, please try again."
|
omniauth_error_unknown: "Something went wrong processing your log in, please try again."
|
||||||
new_registrations_disabled: "New account registrations are not allowed at this time."
|
new_registrations_disabled: "New account registrations are not allowed at this time."
|
||||||
|
password_too_long: "Passwords are limited to 200 characters."
|
||||||
|
|
||||||
user:
|
user:
|
||||||
username:
|
username:
|
||||||
|
|
|
@ -186,6 +186,14 @@ describe SessionController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'invalid password' do
|
||||||
|
it "should return an error with an invalid password if too long" do
|
||||||
|
User.any_instance.expects(:confirm_password?).never
|
||||||
|
xhr :post, :create, login: user.username, password: ('s' * (User.max_password_length + 1))
|
||||||
|
::JSON.parse(response.body)['error'].should be_present
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'suspended user' do
|
describe 'suspended user' do
|
||||||
it 'should return an error' do
|
it 'should return an error' do
|
||||||
User.any_instance.stubs(:suspended?).returns(true)
|
User.any_instance.stubs(:suspended?).returns(true)
|
||||||
|
|
|
@ -287,14 +287,28 @@ describe UsersController do
|
||||||
EmailToken.expects(:confirm).with(token).returns(user)
|
EmailToken.expects(:confirm).with(token).returns(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "fails when the password is blank" do
|
||||||
|
put :password_reset, token: token, password: ''
|
||||||
|
assigns(:user).errors.should be_present
|
||||||
|
session[:current_user_id].should be_blank
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails when the password is too long" do
|
||||||
|
put :password_reset, token: token, password: ('x' * (User.max_password_length + 1))
|
||||||
|
assigns(:user).errors.should be_present
|
||||||
|
session[:current_user_id].should be_blank
|
||||||
|
end
|
||||||
|
|
||||||
it "logs in the user" do
|
it "logs in the user" do
|
||||||
put :password_reset, token: token, password: 'newpassword'
|
put :password_reset, token: token, password: 'newpassword'
|
||||||
|
assigns(:user).errors.should be_blank
|
||||||
session[:current_user_id].should be_present
|
session[:current_user_id].should be_present
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't log in the user when not approved" do
|
it "doesn't log in the user when not approved" do
|
||||||
SiteSetting.expects(:must_approve_users?).returns(true)
|
SiteSetting.expects(:must_approve_users?).returns(true)
|
||||||
put :password_reset, token: token, password: 'newpassword'
|
put :password_reset, token: token, password: 'newpassword'
|
||||||
|
assigns(:user).errors.should be_blank
|
||||||
session[:current_user_id].should be_blank
|
session[:current_user_id].should be_blank
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -508,6 +522,11 @@ describe UsersController do
|
||||||
include_examples 'failed signup'
|
include_examples 'failed signup'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when password is too long' do
|
||||||
|
let(:create_params) { {name: @user.name, username: @user.username, password: "x" * (User.max_password_length + 1), email: @user.email} }
|
||||||
|
include_examples 'failed signup'
|
||||||
|
end
|
||||||
|
|
||||||
context 'when password param is missing' do
|
context 'when password param is missing' do
|
||||||
let(:create_params) { {name: @user.name, username: @user.username, email: @user.email} }
|
let(:create_params) { {name: @user.name, username: @user.username, email: @user.email} }
|
||||||
include_examples 'failed signup'
|
include_examples 'failed signup'
|
||||||
|
|
|
@ -1150,4 +1150,30 @@ describe User do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "hash_passwords" do
|
||||||
|
|
||||||
|
let(:too_long) { "x" * (User.max_password_length + 1) }
|
||||||
|
|
||||||
|
def hash(password, salt)
|
||||||
|
User.new.send(:hash_password, password, salt)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the same hash for the same password and salt" do
|
||||||
|
hash('poutine', 'gravy').should == hash('poutine', 'gravy')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a different hash for the same salt and different password" do
|
||||||
|
hash('poutine', 'gravy').should_not == hash('fries', 'gravy')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a different hash for the same password and different salt" do
|
||||||
|
hash('poutine', 'gravy').should_not == hash('poutine', 'cheese')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises an error when passwords are too long" do
|
||||||
|
-> { hash(too_long, 'gravy') }.should raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue
Block a user