Skip to content

Commit bdc1aba

Browse files
committed
fix(connector-besu/quorum/xdai): unvalidated dynamic method call
Added checks to make sure that the Web3 Contract instances "methods" object has a property of their own called the same way the method is called by the request object. This way if someone tries to execute malicious code by providing method names that are designed to execute something other than the smart contract methods we throw back an error to them instead of complying. This is needed to fix the following CodeQL security advisories: https://github.com/hyperledger/cactus/security/code-scanning/23 https://github.com/hyperledger/cactus/security/code-scanning/24 https://github.com/hyperledger/cactus/security/code-scanning/25 https://github.com/hyperledger/cactus/security/code-scanning/26 Todo for later: create a web3-common package that can be used to house re-usable pieces of code such as the function that validates if a contract really has a certain method or not. Right now this method is copy pasted to all 3 web3 flavored connectors which is not very nice. Fixes #1911 Signed-off-by: Peter Somogyvari <[email protected]>
1 parent 190cf12 commit bdc1aba

File tree

3 files changed

+140
-0
lines changed

3 files changed

+140
-0
lines changed

packages/cactus-plugin-ledger-connector-besu/src/main/typescript/plugin-ledger-connector-besu.ts

+54
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,39 @@ export class PluginLedgerConnectorBesu
305305

306306
return consensusHasTransactionFinality(currentConsensusAlgorithmFamily);
307307
}
308+
309+
/**
310+
* Verifies that it is safe to call a specific method of a Web3 Contract.
311+
*
312+
* @param contract The Web3 Contract instance to check whether it has a method with a specific name or not.
313+
* @param name The name of the method that will be checked if it's usable on `contract` or not.
314+
* @returns Boolean `true` when it IS safe to call the method named `name` on the contract.
315+
* @throws If the contract instance is falsy or it's methods object is falsy. Also throws if the method name is a blank string.
316+
*/
317+
public async isSafeToCallContractMethod(
318+
contract: Contract,
319+
name: string,
320+
): Promise<boolean> {
321+
Checks.truthy(
322+
contract,
323+
`${this.className}#isSafeToCallContractMethod():contract`,
324+
);
325+
326+
Checks.truthy(
327+
contract.methods,
328+
`${this.className}#isSafeToCallContractMethod():contract.methods`,
329+
);
330+
331+
Checks.nonBlankString(
332+
name,
333+
`${this.className}#isSafeToCallContractMethod():name`,
334+
);
335+
336+
const { methods } = contract;
337+
338+
return Object.prototype.hasOwnProperty.call(methods, name);
339+
}
340+
308341
public async invokeContract(
309342
req: InvokeContractV1Request,
310343
): Promise<InvokeContractV1Response> {
@@ -392,6 +425,16 @@ export class PluginLedgerConnectorBesu
392425
contractInstance = new this.web3.eth.Contract(abi, contractAddress);
393426
}
394427

428+
const isSafeToCall = await this.isSafeToCallContractMethod(
429+
contractInstance,
430+
req.methodName,
431+
);
432+
if (!isSafeToCall) {
433+
throw new RuntimeError(
434+
`Invalid method name provided in request. ${req.methodName} does not exist on the Web3 contract object's "methods" property.`,
435+
);
436+
}
437+
395438
const methodRef = contractInstance.methods[req.methodName];
396439
Checks.truthy(methodRef, `${fnTag} YourContract.${req.methodName}`);
397440
const method: ContractSendMethod = methodRef(...req.params);
@@ -945,6 +988,17 @@ export class PluginLedgerConnectorBesu
945988
}
946989
const { contractAddress } = request.invokeCall;
947990
const contractInstance = new this.web3.eth.Contract(abi, contractAddress);
991+
992+
const isSafeToCall = await this.isSafeToCallContractMethod(
993+
contractInstance,
994+
request.invokeCall.methodName,
995+
);
996+
if (!isSafeToCall) {
997+
throw new RuntimeError(
998+
`Invalid method name provided in request. ${request.invokeCall.methodName} does not exist on the Web3 contract object's "methods" property.`,
999+
);
1000+
}
1001+
9481002
const methodRef = contractInstance.methods[request.invokeCall.methodName];
9491003
Checks.truthy(
9501004
methodRef,

packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/plugin-ledger-connector-quorum.ts

+43
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import {
6262
GetPrometheusExporterMetricsEndpointV1,
6363
IGetPrometheusExporterMetricsEndpointV1Options,
6464
} from "./web-services/get-prometheus-exporter-metrics-endpoint-v1";
65+
import { RuntimeError } from "run-time-error";
6566

6667
export interface IPluginLedgerConnectorQuorumOptions
6768
extends ICactusPluginOptions {
@@ -222,6 +223,38 @@ export class PluginLedgerConnectorQuorum
222223
return consensusHasTransactionFinality(currentConsensusAlgorithmFamily);
223224
}
224225

226+
/**
227+
* Verifies that it is safe to call a specific method of a Web3 Contract.
228+
*
229+
* @param contract The Web3 Contract instance to check whether it has a method with a specific name or not.
230+
* @param name The name of the method that will be checked if it's usable on `contract` or not.
231+
* @returns Boolean `true` when it IS safe to call the method named `name` on the contract.
232+
* @throws If the contract instance is falsy or it's methods object is falsy. Also throws if the method name is a blank string.
233+
*/
234+
public async isSafeToCallContractMethod(
235+
contract: InstanceType<typeof Contract>,
236+
name: string,
237+
): Promise<boolean> {
238+
Checks.truthy(
239+
contract,
240+
`${this.className}#isSafeToCallContractMethod():contract`,
241+
);
242+
243+
Checks.truthy(
244+
contract.methods,
245+
`${this.className}#isSafeToCallContractMethod():contract.methods`,
246+
);
247+
248+
Checks.nonBlankString(
249+
name,
250+
`${this.className}#isSafeToCallContractMethod():name`,
251+
);
252+
253+
const { methods } = contract;
254+
255+
return Object.prototype.hasOwnProperty.call(methods, name);
256+
}
257+
225258
public async getContractInfoKeychain(
226259
req: InvokeContractV1Request,
227260
): Promise<any> {
@@ -306,6 +339,16 @@ export class PluginLedgerConnectorQuorum
306339
contractAddress,
307340
);
308341

342+
const isSafeToCall = await this.isSafeToCallContractMethod(
343+
contractInstance,
344+
req.methodName,
345+
);
346+
if (!isSafeToCall) {
347+
throw new RuntimeError(
348+
`Invalid method name provided in request. ${req.methodName} does not exist on the Web3 contract object's "methods" property.`,
349+
);
350+
}
351+
309352
const methodRef = contractInstance.methods[req.methodName];
310353
Checks.truthy(methodRef, `${fnTag} YourContract.${req.methodName}`);
311354

packages/cactus-plugin-ledger-connector-xdai/src/main/typescript/plugin-ledger-connector-xdai.ts

+43
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import {
5959
} from "./web-services/get-prometheus-exporter-metrics-endpoint-v1";
6060
import { DeployContractSolidityBytecodeJsonObjectEndpoint } from "./web-services/deploy-contract-solidity-bytecode-json-object-endpoint";
6161
import { InvokeContractJsonObjectEndpoint } from "./web-services/invoke-contract-json-object-endpoint";
62+
import { RuntimeError } from "run-time-error";
6263

6364
export const E_KEYCHAIN_NOT_FOUND = "cactus.connector.xdai.keychain_not_found";
6465

@@ -227,6 +228,38 @@ export class PluginLedgerConnectorXdai
227228
return ConsensusAlgorithmFamily.Authority;
228229
}
229230

231+
/**
232+
* Verifies that it is safe to call a specific method of a Web3 Contract.
233+
*
234+
* @param contract The Web3 Contract instance to check whether it has a method with a specific name or not.
235+
* @param name The name of the method that will be checked if it's usable on `contract` or not.
236+
* @returns Boolean `true` when it IS safe to call the method named `name` on the contract.
237+
* @throws If the contract instance is falsy or it's methods object is falsy. Also throws if the method name is a blank string.
238+
*/
239+
public async isSafeToCallContractMethod(
240+
contract: InstanceType<typeof Contract>,
241+
name: string,
242+
): Promise<boolean> {
243+
Checks.truthy(
244+
contract,
245+
`${this.className}#isSafeToCallContractMethod():contract`,
246+
);
247+
248+
Checks.truthy(
249+
contract.methods,
250+
`${this.className}#isSafeToCallContractMethod():contract.methods`,
251+
);
252+
253+
Checks.nonBlankString(
254+
name,
255+
`${this.className}#isSafeToCallContractMethod():name`,
256+
);
257+
258+
const { methods } = contract;
259+
260+
return Object.prototype.hasOwnProperty.call(methods, name);
261+
}
262+
230263
async runInvoke(req: InvokeRequestBaseV1): Promise<InvokeContractV1Response> {
231264
const fnTag = `${this.className}#runInvoke()`;
232265

@@ -248,6 +281,16 @@ export class PluginLedgerConnectorXdai
248281
contractAddress,
249282
);
250283

284+
const isSafeToCall = await this.isSafeToCallContractMethod(
285+
contractInstance,
286+
req.methodName,
287+
);
288+
if (!isSafeToCall) {
289+
throw new RuntimeError(
290+
`Invalid method name provided in request. ${req.methodName} does not exist on the Web3 contract object's "methods" property.`,
291+
);
292+
}
293+
251294
const methodRef = contractInstance.methods[req.methodName];
252295
Checks.truthy(methodRef, `${fnTag} YourContract.${req.methodName}`);
253296

0 commit comments

Comments
 (0)