mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-06-26 00:06:07 -04:00
Merge pull request #1864 from rmcrackan/rmcrackan/1862-security
Rmcrackan/1862 security
This commit is contained in:
@@ -62,24 +62,16 @@ public static class AudibleApiStorage
|
||||
=> GetIdentityTokensJsonPath(account.AccountId, account.Locale?.Name);
|
||||
public static string GetIdentityTokensJsonPath(string username, string? localeName)
|
||||
{
|
||||
var usernameSanitized = trimSurroundingQuotes(JsonConvert.ToString(username));
|
||||
var localeNameSanitized = trimSurroundingQuotes(JsonConvert.ToString(localeName));
|
||||
var usernameEscaped = EscapeNewtonsoftJsonPathSingleQuotedLiteral(username);
|
||||
var localeNameEscaped = EscapeNewtonsoftJsonPathSingleQuotedLiteral(localeName ?? string.Empty);
|
||||
|
||||
return $"$.Accounts[?(@.AccountId == '{usernameSanitized}' && @.IdentityTokens.LocaleName == '{localeNameSanitized}')].IdentityTokens";
|
||||
return $"$.Accounts[?(@.AccountId == '{usernameEscaped}' && @.IdentityTokens.LocaleName == '{localeNameEscaped}')].IdentityTokens";
|
||||
}
|
||||
private static string trimSurroundingQuotes(string str)
|
||||
{
|
||||
// SubString algo is better than .Trim("\"")
|
||||
// orig string "
|
||||
// json string "\""
|
||||
// Eg:
|
||||
// => str.Trim("\"")
|
||||
// output \
|
||||
// vs
|
||||
// => str.Substring(1, str.Length - 2)
|
||||
// output \"
|
||||
// also works with surrounding single quotes
|
||||
|
||||
return str.Substring(1, str.Length - 2);
|
||||
}
|
||||
/// <summary>
|
||||
/// Escape a value for use inside single-quoted Newtonsoft JSONPath filter literals.
|
||||
/// See https://www.newtonsoft.com/json/help/html/QueryJsonSelectTokenEscaped.htm
|
||||
/// </summary>
|
||||
internal static string EscapeNewtonsoftJsonPathSingleQuotedLiteral(string value)
|
||||
=> value.Replace("\\", @"\\").Replace("'", @"\'");
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@ public partial class Cdm
|
||||
private static uint RandomUint()
|
||||
{
|
||||
var bts = new byte[4];
|
||||
new Random().NextBytes(bts);
|
||||
RandomNumberGenerator.Fill(bts);
|
||||
return BitConverter.ToUInt32(bts, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
using AssertionHelper;
|
||||
using AudibleUtilities;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace AccountsTests;
|
||||
|
||||
[TestClass]
|
||||
public class GetIdentityTokensJsonPathTests
|
||||
{
|
||||
private const string MarkerProperty = "Marker";
|
||||
|
||||
private static JObject CreateAccountsJson(params (string accountId, string locale, string marker)[] accounts)
|
||||
{
|
||||
var accountsArray = new JArray();
|
||||
foreach (var (accountId, locale, marker) in accounts)
|
||||
{
|
||||
accountsArray.Add(new JObject
|
||||
{
|
||||
["AccountId"] = accountId,
|
||||
["IdentityTokens"] = new JObject
|
||||
{
|
||||
["LocaleName"] = locale,
|
||||
[MarkerProperty] = marker
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return new JObject { ["Accounts"] = accountsArray };
|
||||
}
|
||||
|
||||
private static string? SelectMarker(JObject root, string jsonPath)
|
||||
=> root.SelectToken(jsonPath)?[MarkerProperty]?.Value<string>();
|
||||
|
||||
[TestMethod]
|
||||
public void Normal_email_matches_expected_account()
|
||||
{
|
||||
var root = CreateAccountsJson(
|
||||
("other@example.com", "us", "target"),
|
||||
("someone@example.com", "us", "other"));
|
||||
|
||||
var path = AudibleApiStorage.GetIdentityTokensJsonPath("other@example.com", "us");
|
||||
|
||||
SelectMarker(root, path).Should().Be("target");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apostrophe_in_account_id_matches_expected_account()
|
||||
{
|
||||
var root = CreateAccountsJson(("o'hara@example.com", "us", "target"));
|
||||
|
||||
var path = AudibleApiStorage.GetIdentityTokensJsonPath("o'hara@example.com", "us");
|
||||
|
||||
SelectMarker(root, path).Should().Be("target");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Backslash_in_account_id_matches_expected_account()
|
||||
{
|
||||
var root = CreateAccountsJson((@"a\b@c.com", "us", "target"));
|
||||
|
||||
var path = AudibleApiStorage.GetIdentityTokensJsonPath(@"a\b@c.com", "us");
|
||||
|
||||
SelectMarker(root, path).Should().Be("target");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Injection_payload_does_not_match_unrelated_account()
|
||||
{
|
||||
var root = CreateAccountsJson(
|
||||
("' || @.AccountId == 'evil' || @.AccountId == '", "us", "payload"),
|
||||
("evil", "us", "wrong"));
|
||||
|
||||
var path = AudibleApiStorage.GetIdentityTokensJsonPath(
|
||||
"' || @.AccountId == 'evil' || @.AccountId == '",
|
||||
"us");
|
||||
|
||||
SelectMarker(root, path).Should().Be("payload");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EscapeNewtonsoftJsonPathSingleQuotedLiteral_escapes_backslash_before_quote()
|
||||
{
|
||||
AudibleApiStorage.EscapeNewtonsoftJsonPathSingleQuotedLiteral(@"a\b").Should().Be(@"a\\b");
|
||||
AudibleApiStorage.EscapeNewtonsoftJsonPathSingleQuotedLiteral("o'hara").Should().Be(@"o\'hara");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user