mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 07:36:14 -05:00
[PM-17733] - [Privilege Escalation] - Unauthorised access allows limited access user to change password of Items (#5452)
* prevent view-only users from updating passwords * revert change to licensing service * add tests * check if organizationId is there * move logic to private method * move logic to private method * move logic into method * revert change to licensing service * throw exception when cipher key is created by hidden password users * fix tests * don't allow totp or passkeys changes from hidden password users * add tests * revert change to licensing service
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
@ -9,7 +10,9 @@ using Bit.Core.Services;
|
||||
using Bit.Core.Test.AutoFixture.CipherFixtures;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Bit.Core.Vault.Enums;
|
||||
using Bit.Core.Vault.Models.Data;
|
||||
using Bit.Core.Vault.Queries;
|
||||
using Bit.Core.Vault.Repositories;
|
||||
using Bit.Core.Vault.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
@ -797,6 +800,233 @@ public class CipherServiceTests
|
||||
Arg.Is<IEnumerable<Cipher>>(arg => !arg.Except(ciphers).Any()));
|
||||
}
|
||||
|
||||
private class SaveDetailsAsyncDependencies
|
||||
{
|
||||
public CipherDetails CipherDetails { get; set; }
|
||||
public SutProvider<CipherService> SutProvider { get; set; }
|
||||
}
|
||||
|
||||
private static SaveDetailsAsyncDependencies GetSaveDetailsAsyncDependencies(
|
||||
SutProvider<CipherService> sutProvider,
|
||||
string newPassword,
|
||||
bool viewPassword,
|
||||
bool editPermission,
|
||||
string? key = null,
|
||||
string? totp = null,
|
||||
CipherLoginFido2CredentialData[]? passkeys = null
|
||||
)
|
||||
{
|
||||
var cipherDetails = new CipherDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = Guid.NewGuid(),
|
||||
Type = CipherType.Login,
|
||||
UserId = Guid.NewGuid(),
|
||||
RevisionDate = DateTime.UtcNow,
|
||||
Key = key,
|
||||
};
|
||||
|
||||
var newLoginData = new CipherLoginData { Username = "user", Password = newPassword, Totp = totp, Fido2Credentials = passkeys };
|
||||
cipherDetails.Data = JsonSerializer.Serialize(newLoginData);
|
||||
|
||||
var existingCipher = new Cipher
|
||||
{
|
||||
Id = cipherDetails.Id,
|
||||
Data = JsonSerializer.Serialize(
|
||||
new CipherLoginData
|
||||
{
|
||||
Username = "user",
|
||||
Password = "OriginalPassword",
|
||||
Totp = "OriginalTotp",
|
||||
Fido2Credentials = []
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ICipherRepository>()
|
||||
.GetByIdAsync(cipherDetails.Id)
|
||||
.Returns(existingCipher);
|
||||
|
||||
sutProvider.GetDependency<ICipherRepository>()
|
||||
.ReplaceAsync(Arg.Any<CipherDetails>())
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var permissions = new Dictionary<Guid, OrganizationCipherPermission>
|
||||
{
|
||||
{ cipherDetails.Id, new OrganizationCipherPermission { ViewPassword = viewPassword, Edit = editPermission } }
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IGetCipherPermissionsForUserQuery>()
|
||||
.GetByOrganization(cipherDetails.OrganizationId.Value)
|
||||
.Returns(permissions);
|
||||
|
||||
return new SaveDetailsAsyncDependencies
|
||||
{
|
||||
CipherDetails = cipherDetails,
|
||||
SutProvider = sutProvider,
|
||||
};
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveDetailsAsync_PasswordNotChangedWithoutViewPasswordPermission(string _, SutProvider<CipherService> sutProvider)
|
||||
{
|
||||
var deps = GetSaveDetailsAsyncDependencies(sutProvider, "NewPassword", viewPassword: false, editPermission: true);
|
||||
|
||||
await deps.SutProvider.Sut.SaveDetailsAsync(
|
||||
deps.CipherDetails,
|
||||
deps.CipherDetails.UserId.Value,
|
||||
deps.CipherDetails.RevisionDate,
|
||||
null,
|
||||
true);
|
||||
|
||||
var updatedLoginData = JsonSerializer.Deserialize<CipherLoginData>(deps.CipherDetails.Data);
|
||||
Assert.Equal("OriginalPassword", updatedLoginData.Password);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveDetailsAsync_PasswordNotChangedWithoutEditPermission(string _, SutProvider<CipherService> sutProvider)
|
||||
{
|
||||
var deps = GetSaveDetailsAsyncDependencies(sutProvider, "NewPassword", viewPassword: true, editPermission: false);
|
||||
|
||||
await deps.SutProvider.Sut.SaveDetailsAsync(
|
||||
deps.CipherDetails,
|
||||
deps.CipherDetails.UserId.Value,
|
||||
deps.CipherDetails.RevisionDate,
|
||||
null,
|
||||
true);
|
||||
|
||||
var updatedLoginData = JsonSerializer.Deserialize<CipherLoginData>(deps.CipherDetails.Data);
|
||||
Assert.Equal("OriginalPassword", updatedLoginData.Password);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveDetailsAsync_PasswordChangedWithPermission(string _, SutProvider<CipherService> sutProvider)
|
||||
{
|
||||
var deps = GetSaveDetailsAsyncDependencies(sutProvider, "NewPassword", viewPassword: true, editPermission: true);
|
||||
|
||||
await deps.SutProvider.Sut.SaveDetailsAsync(
|
||||
deps.CipherDetails,
|
||||
deps.CipherDetails.UserId.Value,
|
||||
deps.CipherDetails.RevisionDate,
|
||||
null,
|
||||
true);
|
||||
|
||||
var updatedLoginData = JsonSerializer.Deserialize<CipherLoginData>(deps.CipherDetails.Data);
|
||||
Assert.Equal("NewPassword", updatedLoginData.Password);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveDetailsAsync_CipherKeyChangedWithPermission(string _, SutProvider<CipherService> sutProvider)
|
||||
{
|
||||
var deps = GetSaveDetailsAsyncDependencies(sutProvider, "NewPassword", viewPassword: true, editPermission: true, "NewKey");
|
||||
|
||||
await deps.SutProvider.Sut.SaveDetailsAsync(
|
||||
deps.CipherDetails,
|
||||
deps.CipherDetails.UserId.Value,
|
||||
deps.CipherDetails.RevisionDate,
|
||||
null,
|
||||
true);
|
||||
|
||||
Assert.Equal("NewKey", deps.CipherDetails.Key);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveDetailsAsync_CipherKeyChangedWithoutPermission(string _, SutProvider<CipherService> sutProvider)
|
||||
{
|
||||
var deps = GetSaveDetailsAsyncDependencies(sutProvider, "NewPassword", viewPassword: true, editPermission: false, "NewKey");
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => deps.SutProvider.Sut.SaveDetailsAsync(
|
||||
deps.CipherDetails,
|
||||
deps.CipherDetails.UserId.Value,
|
||||
deps.CipherDetails.RevisionDate,
|
||||
null,
|
||||
true));
|
||||
|
||||
Assert.Contains("do not have permission", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveDetailsAsync_TotpChangedWithoutPermission(string _, SutProvider<CipherService> sutProvider)
|
||||
{
|
||||
var deps = GetSaveDetailsAsyncDependencies(sutProvider, "NewPassword", viewPassword: true, editPermission: false, totp: "NewTotp");
|
||||
|
||||
await deps.SutProvider.Sut.SaveDetailsAsync(
|
||||
deps.CipherDetails,
|
||||
deps.CipherDetails.UserId.Value,
|
||||
deps.CipherDetails.RevisionDate,
|
||||
null,
|
||||
true);
|
||||
|
||||
var updatedLoginData = JsonSerializer.Deserialize<CipherLoginData>(deps.CipherDetails.Data);
|
||||
Assert.Equal("OriginalTotp", updatedLoginData.Totp);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveDetailsAsync_TotpChangedWithPermission(string _, SutProvider<CipherService> sutProvider)
|
||||
{
|
||||
var deps = GetSaveDetailsAsyncDependencies(sutProvider, "NewPassword", viewPassword: true, editPermission: true, totp: "NewTotp");
|
||||
|
||||
await deps.SutProvider.Sut.SaveDetailsAsync(
|
||||
deps.CipherDetails,
|
||||
deps.CipherDetails.UserId.Value,
|
||||
deps.CipherDetails.RevisionDate,
|
||||
null,
|
||||
true);
|
||||
|
||||
var updatedLoginData = JsonSerializer.Deserialize<CipherLoginData>(deps.CipherDetails.Data);
|
||||
Assert.Equal("NewTotp", updatedLoginData.Totp);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveDetailsAsync_Fido2CredentialsChangedWithoutPermission(string _, SutProvider<CipherService> sutProvider)
|
||||
{
|
||||
var passkeys = new[]
|
||||
{
|
||||
new CipherLoginFido2CredentialData
|
||||
{
|
||||
CredentialId = "CredentialId",
|
||||
UserHandle = "UserHandle",
|
||||
}
|
||||
};
|
||||
|
||||
var deps = GetSaveDetailsAsyncDependencies(sutProvider, "NewPassword", viewPassword: true, editPermission: false, passkeys: passkeys);
|
||||
|
||||
await deps.SutProvider.Sut.SaveDetailsAsync(
|
||||
deps.CipherDetails,
|
||||
deps.CipherDetails.UserId.Value,
|
||||
deps.CipherDetails.RevisionDate,
|
||||
null,
|
||||
true);
|
||||
|
||||
var updatedLoginData = JsonSerializer.Deserialize<CipherLoginData>(deps.CipherDetails.Data);
|
||||
Assert.Empty(updatedLoginData.Fido2Credentials);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveDetailsAsync_Fido2CredentialsChangedWithPermission(string _, SutProvider<CipherService> sutProvider)
|
||||
{
|
||||
var passkeys = new[]
|
||||
{
|
||||
new CipherLoginFido2CredentialData
|
||||
{
|
||||
CredentialId = "CredentialId",
|
||||
UserHandle = "UserHandle",
|
||||
}
|
||||
};
|
||||
|
||||
var deps = GetSaveDetailsAsyncDependencies(sutProvider, "NewPassword", viewPassword: true, editPermission: true, passkeys: passkeys);
|
||||
|
||||
await deps.SutProvider.Sut.SaveDetailsAsync(
|
||||
deps.CipherDetails,
|
||||
deps.CipherDetails.UserId.Value,
|
||||
deps.CipherDetails.RevisionDate,
|
||||
null,
|
||||
true);
|
||||
|
||||
var updatedLoginData = JsonSerializer.Deserialize<CipherLoginData>(deps.CipherDetails.Data);
|
||||
Assert.Equal(passkeys.Length, updatedLoginData.Fido2Credentials.Length);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteAsync_WithPersonalCipherOwner_DeletesCipher(
|
||||
|
Reference in New Issue
Block a user