Skip to content

Commit 78076a6

Browse files
authored
fix!: Prevent API from accepting seed or public key for account (#4404)
The API would allow seeds (and public keys) to be used in place of accounts at several locations in the API. For example, when calling account_info, you could pass `"account": "foo"`. The string "foo" is treated like a seed, so the method returns `actNotFound` (instead of `actMalformed`, as most developers would expect). In the early days, this was a convenience to make testing easier. However, it allows for poor security practices, so it is no longer a good idea. Allowing a secret or passphrase is now considered a bug. Previously, it was controlled by the `strict` option on some methods. With this commit, since the API does not interpret `account` as `seed`, the option `strict` is no longer needed and is removed. Removing this behavior from the API is a [breaking change](https://xrpl.org/request-formatting.html#breaking-changes). One could argue that it shouldn't be done without bumping the API version; however, in this instance, there is no evidence that anyone is using the API in the "legacy" way. Furthermore, it is a potential security hole, as it allows users to send secrets to places where they are not needed, where they could end up in logs, error messages, etc. There's no reason to take such a risk with a seed/secret, since only the public address is needed. Resolves: #3329, #3330, #4337 BREAKING CHANGE: Remove non-strict account parsing (#3330)
1 parent 67238b9 commit 78076a6

20 files changed

+203
-351
lines changed

src/ripple/app/main/Main.cpp

+4-6
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,12 @@ printHelp(const po::options_description& desc)
125125
<< systemName() << "d [options] <command> <params>\n"
126126
<< desc << std::endl
127127
<< "Commands: \n"
128-
" account_currencies <account> [<ledger>] [strict]\n"
129-
" account_info <account>|<seed>|<pass_phrase>|<key> [<ledger>] "
130-
"[strict]\n"
128+
" account_currencies <account> [<ledger>]\n"
129+
" account_info <account>|<key> [<ledger>]\n"
131130
" account_lines <account> <account>|\"\" [<ledger>]\n"
132131
" account_channels <account> <account>|\"\" [<ledger>]\n"
133-
" account_objects <account> [<ledger>] [strict]\n"
134-
" account_offers <account>|<account_public_key> [<ledger>] "
135-
"[strict]\n"
132+
" account_objects <account> [<ledger>]\n"
133+
" account_offers <account>|<account_public_key> [<ledger>]\n"
136134
" account_tx accountID [ledger_index_min [ledger_index_max "
137135
"[limit "
138136
"]]] [binary]\n"

src/ripple/net/impl/RPCCall.cpp

+5-21
Original file line numberDiff line numberDiff line change
@@ -775,11 +775,9 @@ class RPCParser
775775
return jvRequest;
776776
}
777777

778-
// owner_info <account>|<account_public_key> [strict]
779-
// owner_info <seed>|<pass_phrase>|<key> [<ledger>] [strict]
780-
// account_info <account>|<account_public_key> [strict]
781-
// account_info <seed>|<pass_phrase>|<key> [<ledger>] [strict]
782-
// account_offers <account>|<account_public_key> [<ledger>] [strict]
778+
// owner_info <account>
779+
// account_info <account> [<ledger>]
780+
// account_offers <account> [<ledger>]
783781
Json::Value
784782
parseAccountItems(Json::Value const& jvParams)
785783
{
@@ -895,10 +893,7 @@ class RPCParser
895893
// Parameters 0 and 1 are accounts
896894
if (i < 2)
897895
{
898-
if (parseBase58<PublicKey>(
899-
TokenType::AccountPublic, strParam) ||
900-
parseBase58<AccountID>(strParam) ||
901-
parseGenericSeed(strParam))
896+
if (parseBase58<AccountID>(strParam))
902897
{
903898
jvRequest[accFields[i]] = std::move(strParam);
904899
}
@@ -924,26 +919,15 @@ class RPCParser
924919
{
925920
std::string strIdent = jvParams[0u].asString();
926921
unsigned int iCursor = jvParams.size();
927-
bool bStrict = false;
928922

929-
if (iCursor >= 2 && jvParams[iCursor - 1] == jss::strict)
930-
{
931-
bStrict = true;
932-
--iCursor;
933-
}
934-
935-
if (!parseBase58<PublicKey>(TokenType::AccountPublic, strIdent) &&
936-
!parseBase58<AccountID>(strIdent) && !parseGenericSeed(strIdent))
923+
if (!parseBase58<AccountID>(strIdent))
937924
return rpcError(rpcACT_MALFORMED);
938925

939926
// Get info on account.
940927
Json::Value jvRequest(Json::objectValue);
941928

942929
jvRequest[jss::account] = strIdent;
943930

944-
if (bStrict)
945-
jvRequest[jss::strict] = 1;
946-
947931
if (iCursor == 2 && !jvParseLedger(jvRequest, jvParams[1u].asString()))
948932
return rpcError(rpcLGR_IDX_MALFORMED);
949933

src/ripple/rpc/handlers/AccountChannels.cpp

+16-18
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ addChannel(Json::Value& jsonLines, SLE const& line)
5858
}
5959

6060
// {
61-
// account: <account>|<account_public_key>
61+
// account: <account>
6262
// ledger_hash : <ledger>
6363
// ledger_index : <ledger_index>
6464
// limit: integer // optional
@@ -76,26 +76,25 @@ doAccountChannels(RPC::JsonContext& context)
7676
if (!ledger)
7777
return result;
7878

79-
std::string strIdent(params[jss::account].asString());
80-
AccountID accountID;
81-
82-
if (auto const err = RPC::accountFromString(accountID, strIdent))
83-
return err;
79+
auto id = parseBase58<AccountID>(params[jss::account].asString());
80+
if (!id)
81+
{
82+
return rpcError(rpcACT_MALFORMED);
83+
}
84+
AccountID const accountID{std::move(id.value())};
8485

8586
if (!ledger->exists(keylet::account(accountID)))
8687
return rpcError(rpcACT_NOT_FOUND);
8788

8889
std::string strDst;
8990
if (params.isMember(jss::destination_account))
9091
strDst = params[jss::destination_account].asString();
91-
auto hasDst = !strDst.empty();
9292

93-
AccountID raDstAccount;
94-
if (hasDst)
95-
{
96-
if (auto const err = RPC::accountFromString(raDstAccount, strDst))
97-
return err;
98-
}
93+
auto const raDstAccount = [&]() -> std::optional<AccountID> {
94+
return strDst.empty() ? std::nullopt : parseBase58<AccountID>(strDst);
95+
}();
96+
if (!strDst.empty() && !raDstAccount)
97+
return rpcError(rpcACT_MALFORMED);
9998

10099
unsigned int limit;
101100
if (auto err = readLimitField(limit, RPC::Tuning::accountChannels, context))
@@ -109,10 +108,9 @@ doAccountChannels(RPC::JsonContext& context)
109108
{
110109
std::vector<std::shared_ptr<SLE const>> items;
111110
AccountID const& accountID;
112-
bool hasDst;
113-
AccountID const& raDstAccount;
111+
std::optional<AccountID> const& raDstAccount;
114112
};
115-
VisitData visitData = {{}, accountID, hasDst, raDstAccount};
113+
VisitData visitData = {{}, accountID, raDstAccount};
116114
visitData.items.reserve(limit);
117115
uint256 startAfter = beast::zero;
118116
std::uint64_t startHint = 0;
@@ -180,8 +178,8 @@ doAccountChannels(RPC::JsonContext& context)
180178

181179
if (count <= limit && sleCur->getType() == ltPAYCHAN &&
182180
(*sleCur)[sfAccount] == accountID &&
183-
(!visitData.hasDst ||
184-
visitData.raDstAccount == (*sleCur)[sfDestination]))
181+
(!visitData.raDstAccount ||
182+
*visitData.raDstAccount == (*sleCur)[sfDestination]))
185183
{
186184
visitData.items.emplace_back(sleCur);
187185
}

src/ripple/rpc/handlers/AccountCurrenciesHandler.cpp

+7-6
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,14 @@ doAccountCurrencies(RPC::JsonContext& context)
4646
params.isMember(jss::account) ? params[jss::account].asString()
4747
: params[jss::ident].asString());
4848

49-
bool const bStrict =
50-
params.isMember(jss::strict) && params[jss::strict].asBool();
51-
5249
// Get info on account.
53-
AccountID accountID; // out param
54-
if (auto jvAccepted = RPC::accountFromString(accountID, strIdent, bStrict))
55-
return jvAccepted;
50+
auto id = parseBase58<AccountID>(strIdent);
51+
if (!id)
52+
{
53+
RPC::inject_error(rpcACT_MALFORMED, result);
54+
return result;
55+
}
56+
auto const accountID{std::move(id.value())};
5657

5758
if (!ledger->exists(keylet::account(accountID)))
5859
return rpcError(rpcACT_NOT_FOUND);

src/ripple/rpc/handlers/AccountInfo.cpp

+8-10
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ namespace ripple {
3434

3535
// {
3636
// account: <ident>,
37-
// strict: <bool> // optional (default false)
38-
// // if true only allow public keys and addresses.
3937
// ledger_hash : <ledger>
4038
// ledger_index : <ledger_index>
4139
// signer_lists : <bool> // optional (default false)
@@ -67,15 +65,14 @@ doAccountInfo(RPC::JsonContext& context)
6765
if (!ledger)
6866
return result;
6967

70-
bool bStrict = params.isMember(jss::strict) && params[jss::strict].asBool();
71-
AccountID accountID;
72-
7368
// Get info on account.
74-
75-
auto jvAccepted = RPC::accountFromString(accountID, strIdent, bStrict);
76-
77-
if (jvAccepted)
78-
return jvAccepted;
69+
auto id = parseBase58<AccountID>(strIdent);
70+
if (!id)
71+
{
72+
RPC::inject_error(rpcACT_MALFORMED, result);
73+
return result;
74+
}
75+
auto const accountID{std::move(id.value())};
7976

8077
static constexpr std::
8178
array<std::pair<std::string_view, LedgerSpecificFlags>, 9>
@@ -113,6 +110,7 @@ doAccountInfo(RPC::JsonContext& context)
113110
return result;
114111
}
115112

113+
Json::Value jvAccepted(Json::objectValue);
116114
RPC::injectSLE(jvAccepted, *sleAccepted);
117115
result[jss::account_data] = jvAccepted;
118116

src/ripple/rpc/handlers/AccountLines.cpp

+22-31
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,6 @@
3030

3131
namespace ripple {
3232

33-
struct VisitData
34-
{
35-
std::vector<RPCTrustLine> items;
36-
AccountID const& accountID;
37-
bool hasPeer;
38-
AccountID const& raPeerAccount;
39-
40-
bool ignoreDefault;
41-
uint32_t foundCount;
42-
};
43-
4433
void
4534
addLine(Json::Value& jsonLines, RPCTrustLine const& line)
4635
{
@@ -76,7 +65,7 @@ addLine(Json::Value& jsonLines, RPCTrustLine const& line)
7665
}
7766

7867
// {
79-
// account: <account>|<account_public_key>
68+
// account: <account>
8069
// ledger_hash : <ledger>
8170
// ledger_index : <ledger_index>
8271
// limit: integer // optional
@@ -96,33 +85,28 @@ doAccountLines(RPC::JsonContext& context)
9685
if (!ledger)
9786
return result;
9887

99-
std::string strIdent(params[jss::account].asString());
100-
AccountID accountID;
101-
102-
if (auto jv = RPC::accountFromString(accountID, strIdent))
88+
auto id = parseBase58<AccountID>(params[jss::account].asString());
89+
if (!id)
10390
{
104-
for (auto it = jv.begin(); it != jv.end(); ++it)
105-
result[it.memberName()] = *it;
91+
RPC::inject_error(rpcACT_MALFORMED, result);
10692
return result;
10793
}
94+
auto const accountID{std::move(id.value())};
10895

10996
if (!ledger->exists(keylet::account(accountID)))
11097
return rpcError(rpcACT_NOT_FOUND);
11198

11299
std::string strPeer;
113100
if (params.isMember(jss::peer))
114101
strPeer = params[jss::peer].asString();
115-
auto hasPeer = !strPeer.empty();
116102

117-
AccountID raPeerAccount;
118-
if (hasPeer)
103+
auto const raPeerAccount = [&]() -> std::optional<AccountID> {
104+
return strPeer.empty() ? std::nullopt : parseBase58<AccountID>(strPeer);
105+
}();
106+
if (!strPeer.empty() && !raPeerAccount)
119107
{
120-
if (auto jv = RPC::accountFromString(raPeerAccount, strPeer))
121-
{
122-
for (auto it = jv.begin(); it != jv.end(); ++it)
123-
result[it.memberName()] = *it;
124-
return result;
125-
}
108+
RPC::inject_error(rpcACT_MALFORMED, result);
109+
return result;
126110
}
127111

128112
unsigned int limit;
@@ -138,8 +122,15 @@ doAccountLines(RPC::JsonContext& context)
138122
params[jss::ignore_default].asBool();
139123

140124
Json::Value& jsonLines(result[jss::lines] = Json::arrayValue);
141-
VisitData visitData = {
142-
{}, accountID, hasPeer, raPeerAccount, ignoreDefault, 0};
125+
struct VisitData
126+
{
127+
std::vector<RPCTrustLine> items;
128+
AccountID const& accountID;
129+
std::optional<AccountID> const& raPeerAccount;
130+
bool ignoreDefault;
131+
uint32_t foundCount;
132+
};
133+
VisitData visitData = {{}, accountID, raPeerAccount, ignoreDefault, 0};
143134
uint256 startAfter = beast::zero;
144135
std::uint64_t startHint = 0;
145136

@@ -227,8 +218,8 @@ doAccountLines(RPC::JsonContext& context)
227218
RPCTrustLine::makeItem(visitData.accountID, sleCur);
228219

229220
if (line &&
230-
(!visitData.hasPeer ||
231-
visitData.raPeerAccount ==
221+
(!visitData.raPeerAccount ||
222+
*visitData.raPeerAccount ==
232223
line->getAccountIDPeer()))
233224
{
234225
visitData.items.emplace_back(*line);

src/ripple/rpc/handlers/AccountObjects.cpp

+11-19
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ namespace ripple {
3939

4040
/** General RPC command that can retrieve objects in the account root.
4141
{
42-
account: <account>|<account_public_key>
42+
account: <account>
4343
ledger_hash: <string> // optional
4444
ledger_index: <string | unsigned integer> // optional
4545
type: <string> // optional, defaults to all account objects types
@@ -60,17 +60,13 @@ doAccountNFTs(RPC::JsonContext& context)
6060
if (ledger == nullptr)
6161
return result;
6262

63-
AccountID accountID;
63+
auto id = parseBase58<AccountID>(params[jss::account].asString());
64+
if (!id)
6465
{
65-
auto const strIdent = params[jss::account].asString();
66-
if (auto jv = RPC::accountFromString(accountID, strIdent))
67-
{
68-
for (auto it = jv.begin(); it != jv.end(); ++it)
69-
result[it.memberName()] = *it;
70-
71-
return result;
72-
}
66+
RPC::inject_error(rpcACT_MALFORMED, result);
67+
return result;
7368
}
69+
auto const accountID{std::move(id.value())};
7470

7571
if (!ledger->exists(keylet::account(accountID)))
7672
return rpcError(rpcACT_NOT_FOUND);
@@ -177,17 +173,13 @@ doAccountObjects(RPC::JsonContext& context)
177173
if (ledger == nullptr)
178174
return result;
179175

180-
AccountID accountID;
176+
auto const id = parseBase58<AccountID>(params[jss::account].asString());
177+
if (!id)
181178
{
182-
auto const strIdent = params[jss::account].asString();
183-
if (auto jv = RPC::accountFromString(accountID, strIdent))
184-
{
185-
for (auto it = jv.begin(); it != jv.end(); ++it)
186-
result[it.memberName()] = *it;
187-
188-
return result;
189-
}
179+
RPC::inject_error(rpcACT_MALFORMED, result);
180+
return result;
190181
}
182+
auto const accountID{std::move(id.value())};
191183

192184
if (!ledger->exists(keylet::account(accountID)))
193185
return rpcError(rpcACT_NOT_FOUND);

src/ripple/rpc/handlers/AccountOffers.cpp

+5-8
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ appendOfferJson(std::shared_ptr<SLE const> const& offer, Json::Value& offers)
4747
};
4848

4949
// {
50-
// account: <account>|<account_public_key>
50+
// account: <account>
5151
// ledger_hash : <ledger>
5252
// ledger_index : <ledger_index>
5353
// limit: integer // optional
@@ -65,16 +65,13 @@ doAccountOffers(RPC::JsonContext& context)
6565
if (!ledger)
6666
return result;
6767

68-
std::string strIdent(params[jss::account].asString());
69-
AccountID accountID;
70-
71-
if (auto jv = RPC::accountFromString(accountID, strIdent))
68+
auto id = parseBase58<AccountID>(params[jss::account].asString());
69+
if (!id)
7270
{
73-
for (auto it = jv.begin(); it != jv.end(); ++it)
74-
result[it.memberName()] = (*it);
75-
71+
RPC::inject_error(rpcACT_MALFORMED, result);
7672
return result;
7773
}
74+
auto const accountID{std::move(id.value())};
7875

7976
// Get info on account.
8077
result[jss::account] = toBase58(accountID);

0 commit comments

Comments
 (0)