VYPR
High severityNVD Advisory· Published Feb 1, 2023· Updated Mar 27, 2025

CVE-2022-25916

CVE-2022-25916

Description

mt7688-wiscan before 0.8.3 is vulnerable to command injection in the scan() function due to unsanitized input.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

mt7688-wiscan before 0.8.3 is vulnerable to command injection in the scan() function due to unsanitized input.

Vulnerability

Description

The mt7688-wiscan npm package, used for WiFi scanning on MediaTek Linkit Smart 7688 devices, contains a command injection vulnerability in the wiscan.scan function. Versions prior to 0.8.3 fail to properly sanitize user-supplied input passed to the function, allowing an attacker to inject arbitrary operating system commands [1][4]. The root cause is the lack of validation or escaping of the intf parameter (and potentially other parameters) before it is used in shell commands that perform scanning operations [2].

Exploitation

An attacker can exploit this vulnerability by providing a crafted string containing shell metacharacters (e.g., ;, |, $) as the first argument to the wiscan.scan function. A proof-of-concept (PoC) published by Snyk illustrates the trivial nature of exploitation: wiscan.scan(';touch EXPLOITED;#', function(){}) results in the execution of the touch EXPLOITED command [4]. No authentication or special privileges are required; any code that calls wiscan.scan() with attacker-controllable input can be leveraged for command execution.

Impact

Successful exploitation allows an attacker to execute arbitrary commands with the privileges of the Node.js process typically running as root or a highly privileged user on the embedded device. This can lead to full device compromise, including unauthorized access, data exfiltration, installation of malware, or disruption of device functionality [1][4]. Given the typical use of mt7688-wiscan in IoT and embedded systems, the impact can extend to broader network compromise.

Mitigation

The vulnerability has been patched in version 0.8.3, which restricts the interface name to only alphabets and numbers, effectively removing shell injection vectors [2]. Users should update to mt7688-wiscan@>=0.8.3 immediately. There is no known workaround; applications using vulnerable versions should upgrade as soon as possible [4].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
mt7688-wiscannpm
< 0.8.30.8.3

Affected products

2

Patches

1
ff6d6567c65b

v0.8.3 intf only accetps alphabets and numbers

https://github.com/simenkid/mt7688-wiscanSimen LiJan 30, 2023via ghsa
3 files changed · +208 201
  • index.js+125 121 modified
    @@ -1,162 +1,166 @@
     var exec = require('child_process').exec;
     
     var defaultInterface = 'ra0',
    -    freqs = [ '2.412', '2.417', '2.422', '2.427', '2.432', '2.437', '2.442', '2.447', '2.452', '2.457', '2.462' ];
    +  freqs = [
    +    '2.412',
    +    '2.417',
    +    '2.422',
    +    '2.427',
    +    '2.432',
    +    '2.437',
    +    '2.442',
    +    '2.447',
    +    '2.452',
    +    '2.457',
    +    '2.462',
    +  ];
     
     var wiscan = {};
     
     wiscan.scan = function (intf, callback) {
    -    var child;
    +  var child;
     
    -    if (typeof intf === 'function') {
    -        callback = intf;
    -        intf = defaultInterface;
    -    }
    +  if (typeof intf === 'function') {
    +    callback = intf;
    +    intf = defaultInterface;
    +  }
     
    -    intf = intf || defaultInterface;
    -    callback = callback || function () {};
    +  intf = intf || defaultInterface;
    +  callback = callback || function () {};
     
    -    if (typeof intf !== 'string')
    -        return callback(new Error('intf should be a string.'));
    +  if (typeof intf !== 'string')
    +    return callback(new Error('intf should be a string.'));
     
    -    child = exec('iwinfo ' + intf + ' scan', function (error, stdout, stderr) {
    -        if (error) {
    -            stderr = stderr.trim();
    -            return callback(new Error(stderr));
    -        }
    +  if (!/[0-9a-zA-Z]/.test(intf.charAt(0)))
    +    return callback(new Error('Bad intf.'));
    +
    +  child = exec('iwinfo ' + intf + ' scan', function (error, stdout, stderr) {
    +    if (error) {
    +      stderr = stderr.trim();
    +      return callback(new Error(stderr));
    +    }
     
    -        var info = stdout,
    -            parsed = [];
    -
    -        info = info.replace(/\n/g, ' ');
    -        info = info.replace(/"/g, '');
    -        info = info.split(' ');
    -        info.forEach(function (char, i) {
    -            if (char === 'ESSID' && info[i+1] === '')
    -                info[i+1] = 'unknown';
    -            else if (char !== '')
    -                parsed.push(char);
    -        });
    -
    -        parsed = parse(parsed);
    -        callback(null, parsed);
    +    var info = stdout,
    +      parsed = [];
    +
    +    info = info.replace(/\n/g, ' ');
    +    info = info.replace(/"/g, '');
    +    info = info.split(' ');
    +    info.forEach(function (char, i) {
    +      if (char === 'ESSID' && info[i + 1] === '') info[i + 1] = 'unknown';
    +      else if (char !== '') parsed.push(char);
         });
    +
    +    parsed = parse(parsed);
    +    callback(null, parsed);
    +  });
     };
     
     wiscan.scanByEssid = function (intf, essid, callback) {
    -    var target = null;
    +  var target = null;
     
    -    if (arguments.length === 2) {
    -        callback = essid;
    -        essid = intf;
    -        intf = defaultInterface;
    -    }
    +  if (arguments.length === 2) {
    +    callback = essid;
    +    essid = intf;
    +    intf = defaultInterface;
    +  }
     
    -    intf = intf || defaultInterface;
    +  intf = intf || defaultInterface;
     
    -    if (typeof intf !== 'string')
    -        return callback(new Error('intf should be a string.'));
    -    else if (typeof essid !== 'string')
    -        return callback(new Error('essid should be a string.'));
    +  if (typeof intf !== 'string')
    +    return callback(new Error('intf should be a string.'));
    +  else if (typeof essid !== 'string')
    +    return callback(new Error('essid should be a string.'));
     
    -    callback = callback || function () {};
    +  callback = callback || function () {};
     
    -    wiscan.scan(intf, function (err, infos) {
    -        if (err)
    -            return callback(err);
    +  wiscan.scan(intf, function (err, infos) {
    +    if (err) return callback(err);
     
    -        infos.forEach(function (info) {
    -            if (info.essid === essid)
    -                target = info;
    -        });
    -
    -        callback(null, target);
    +    infos.forEach(function (info) {
    +      if (info.essid === essid) target = info;
         });
    +
    +    callback(null, target);
    +  });
     };
     
     wiscan.lqi = function (intf, essid, callback) {
    -    if (arguments.length === 2) {
    -        callback = essid;
    -        essid = intf;
    -        intf = defaultInterface;
    -    }
    +  if (arguments.length === 2) {
    +    callback = essid;
    +    essid = intf;
    +    intf = defaultInterface;
    +  }
     
    -    intf = intf || defaultInterface;
    +  intf = intf || defaultInterface;
     
    -    wiscan.scanByEssid(intf, essid, function (err, info) {
    -        if (err)
    -            return callback(err);
    +  wiscan.scanByEssid(intf, essid, function (err, info) {
    +    if (err) return callback(err);
     
    -        if (info)
    -            callback(null, info.quality);
    -        else
    -            callback(null, null);
    -    });
    +    if (info) callback(null, info.quality);
    +    else callback(null, null);
    +  });
     };
     
     function parse(items) {
    -    var parsed = [],
    -        len = items.length,
    -        idx = 0;
    -
    -    if (items.length === 0)
    -        return parsed;
    -
    -    items.forEach(function (c, i) {
    -        var val;
    -        if (c === 'Cell') {
    -            //- [deleted: need not cell field] val = items[i+1];
    -            //- [deleted: need not cell field] val = isNaN(parseInt(val)) ? val : parseInt(val);
    -            parsed.push({});
    -        } else if (c === 'Address:') {
    -            parsed[idx].address = items[i+1];
    -        } else if (c === 'ESSID:') {
    -            parsed[idx].essid = items[i+1];
    -        } else if (c === 'Mode:') {
    -            parsed[idx].mode = items[i+1];
    -        } else if (c === 'Channel:') {
    -            val = items[i+1];
    -            val = isNaN(parseInt(val)) ? val : parseInt(val);
    -            parsed[idx].channel = val;
    -            parsed[idx].frequency = getFrequency(val);
    -        } else if (c === 'Signal:') {
    -            val = items[i+1];
    -            val = isNaN(parseInt(val)) ? val : parseInt(val);
    -            parsed[idx].signal = val;
    -        } else if (c === 'Quality:') {
    -
    -            val = items[i+1].split('/')[0];
    -            val = isNaN(parseInt(val)) ? val : parseInt(val);
    -            parsed[idx].quality = val;
    -        } else if (c === 'Encryption:') {
    -            var x = i + 1,
    -                enc = '';
    -
    -            while (items[x] !== 'Cell') {
    -                if (x !== len) {
    -                    enc = enc + items[x] + ' ';
    -                    x += 1;
    -                } else {
    -                    break;
    -                }
    -            }
    -            enc = enc.trim();
    -            parsed[idx].encryption = enc;
    -            idx += 1;
    +  var parsed = [],
    +    len = items.length,
    +    idx = 0;
    +
    +  if (items.length === 0) return parsed;
    +
    +  items.forEach(function (c, i) {
    +    var val;
    +    if (c === 'Cell') {
    +      //- [deleted: need not cell field] val = items[i+1];
    +      //- [deleted: need not cell field] val = isNaN(parseInt(val)) ? val : parseInt(val);
    +      parsed.push({});
    +    } else if (c === 'Address:') {
    +      parsed[idx].address = items[i + 1];
    +    } else if (c === 'ESSID:') {
    +      parsed[idx].essid = items[i + 1];
    +    } else if (c === 'Mode:') {
    +      parsed[idx].mode = items[i + 1];
    +    } else if (c === 'Channel:') {
    +      val = items[i + 1];
    +      val = isNaN(parseInt(val)) ? val : parseInt(val);
    +      parsed[idx].channel = val;
    +      parsed[idx].frequency = getFrequency(val);
    +    } else if (c === 'Signal:') {
    +      val = items[i + 1];
    +      val = isNaN(parseInt(val)) ? val : parseInt(val);
    +      parsed[idx].signal = val;
    +    } else if (c === 'Quality:') {
    +      val = items[i + 1].split('/')[0];
    +      val = isNaN(parseInt(val)) ? val : parseInt(val);
    +      parsed[idx].quality = val;
    +    } else if (c === 'Encryption:') {
    +      var x = i + 1,
    +        enc = '';
    +
    +      while (items[x] !== 'Cell') {
    +        if (x !== len) {
    +          enc = enc + items[x] + ' ';
    +          x += 1;
    +        } else {
    +          break;
             }
    -    });
    -    return parsed;
    +      }
    +      enc = enc.trim();
    +      parsed[idx].encryption = enc;
    +      idx += 1;
    +    }
    +  });
    +  return parsed;
     }
     
     function getFrequency(ch) {
    -    var f = freqs[ch - 1];
    +  var f = freqs[ch - 1];
     
    -    if (f)
    -        f = f + ' GHz';
    -    else
    -        f = '';
    +  if (f) f = f + ' GHz';
    +  else f = '';
     
    -    return f;
    +  return f;
     }
     
     module.exports = wiscan;
    
  • package.json+1 1 modified
    @@ -1,6 +1,6 @@
     {
       "name": "mt7688-wiscan",
    -  "version": "0.8.2",
    +  "version": "0.8.3",
       "description": "A wifi access points scanning tool for MediaTek Linkit Smart 7688",
       "main": "index.js",
       "scripts": {
    
  • README.md+82 79 modified
    @@ -2,23 +2,26 @@
     
     ## Table of Contents
     
    -1. [Overiew](#Overiew)    
    -2. [Installation](#Installation) 
    +1. [Overiew](#Overiew)
    +2. [Installation](#Installation)
     3. [Usage](#Usage)
     
     <a name="Overiew"></a>
    +
     ## 1. Overview
     
    -**mt7688-wiscan** is a wifi access points scanning tool which is running with node.js on MediaTek Linkit Smart 7688. This tool provides three APIs [lqi()](#API_lqi), [scan()](#API_scan), and [scanByEssid()](#API_scanByEssid) that help you with getting link quality indicator (LQI) to an specific AP, scanning neighbor wifi APs, and scanning a specific AP with its **essid**.  
    +**mt7688-wiscan** is a wifi access points scanning tool which is running with node.js on MediaTek Linkit Smart 7688. This tool provides three APIs [lqi()](#API_lqi), [scan()](#API_scan), and [scanByEssid()](#API_scanByEssid) that help you with getting link quality indicator (LQI) to an specific AP, scanning neighbor wifi APs, and scanning a specific AP with its **essid**.
     
    -I am using this tool on machine nodes in my LWM2M IoT project. On each machine, there is a panel to show some information about the machine and to show LQI between machine and its router as well.  
    +I am using this tool on machine nodes in my LWM2M IoT project. On each machine, there is a panel to show some information about the machine and to show LQI between machine and its router as well.
     
     <a name="Installation"></a>
    +
     ## 2. Installation
     
     > $ npm install mt7688-wiscan --save
    -  
    +
     <a name="Usage"></a>
    +
     ## 3. Usage
     
     ### Require the module
    @@ -27,75 +30,72 @@ I am using this tool on machine nodes in my LWM2M IoT project. On each machine,
     var wiscan = require('mt7688-wiscan');
     ```
     
    -********************************************
    +---
    +
     <a name="API_lqi"></a>
    +
     ### .lqi([intf,] essid, callback)
    -Query the LQI (link quality indicator) between your Linkit Smart 7688(in station mode) and its router(AP).  
     
    -**Arguments:**  
    +Query the LQI (link quality indicator) between your Linkit Smart 7688(in station mode) and its router(AP).
    +
    +**Arguments:**
    +
    +1. `intf` (_String_, optional): A default value of `'ra0'` will be used if not given.
    +2. `essid` (_String_): The ESSID of the AP to scan for.
    +3. `callback` (_Function_): `function(err, result) { ... }`. The `result` is a number between 0 and 100 to indicate the relative link quality between the station and access point. The value is bigger to show better link quality. If given 'essid' is not found after scan, `result` will be `null`.
     
    -1. `intf` (_String_, optional): A default value of `'ra0'` will be used if not given.  
    -2. `essid` (_String_): The ESSID of the AP to scan for.  
    -3. `callback` (_Function_): `function(err, result) { ... }`. The `result` is a number between 0 and 100 to indicate the relative link quality between the station and access point. The value is bigger to show better link quality. If given 'essid' is not found after scan, `result` will be `null`.  
    -  
    -  
     **[Note]**
    -* It takes around 5 seconds to accomplish a single scan.  
    -* If you've changed the name of radio interface with OpenWrt configuration tool, you should give this method with the correct interface name, for example, `'myradio'`.  
    -* You can also try this tool on other platforms, but be aware of that the radio interface name is subject to platforms. Use `iwconfig` command at console to get some hints.  
    -  
    -**Returns:**  
    -  
    -* _none_
     
    -**Examples:**  
    -  
    +- It takes around 5 seconds to accomplish a single scan.
    +- If you've changed the name of radio interface with OpenWrt configuration tool, you should give this method with the correct interface name, for example, `'myradio'`.
    +- You can also try this tool on other platforms, but be aware of that the radio interface name is subject to platforms. Use `iwconfig` command at console to get some hints.
    +
    +**Returns:**
    +
    +- _none_
    +
    +**Examples:**
    +
     ```js
     // scan with deafult radio interface, just give it an essid to scan for
     wiscan.lqi('my_office_ap', function (err, result) {
    -    if (err)
    -        console.log(err);       // null
    -    else
    -        console.log(result);    // 78
    +  if (err) console.log(err); // null
    +  else console.log(result); // 78
     });
     
     // if an AP with given essid is not around (result is nothing after scan)
     wiscan.lqi('ap_not_found', function (err, result) {
    -    if (err)
    -        console.log(err);       // null
    -    else
    -        console.log(result);    // null
    +  if (err) console.log(err); // null
    +  else console.log(result); // null
     });
     
     // scan with the given radio interface and essid
     wiscan.lqi('ra0', 'my_office_ap', function (err, result) {
    -    if (err)
    -        console.log(err);       // null
    -    else
    -        console.log(result);    // 82
    +  if (err) console.log(err); // null
    +  else console.log(result); // 82
     });
     
     // scan with the given radio interface, e.g. 'bad_ra', that doesn't exist
     wiscan.lqi('bad_ra', 'my_office_ap', function (err, result) {
    -    if (err)
    -        console.log(err);       // [Error: No such wireless device: bad_ra]
    +  if (err) console.log(err); // [Error: No such wireless device: bad_ra]
     });
    -
     ```
     
    -********************************************
    +---
     
     <a name="API_scan"></a>
    +
     ### .scan([intf,] callback)
    -Scan neighbor wifi access points.  
     
    -**Arguments:**  
    +Scan neighbor wifi access points.
    +
    +**Arguments:**
     
    -1. `intf` (_String_, optional): A default value of `'ra0'` will be used if not given.  
    +1. `intf` (_String_, optional): A default value of `'ra0'` will be used if not given.
     2. `callback` (_Function_): `function(err, result) { ... }`. The `result` is an array of scanned report objects. Each report object has the following format:
     
     ```js
    -{ 
    +{
         address: 'D8:FE:E3:E5:9F:3B',    // String. MAC address of the found AP
         essid: 'sivann',                 // String
         mode: 'Master',                  // String
    @@ -106,78 +106,81 @@ Scan neighbor wifi access points.
         encryption: 'WPA2 PSK (AES-OCB)' // String
     }
     ```
    -  
    -**Returns:**  
    -  
    -* _none_
     
    -**Examples:**  
    -  
    +**Returns:**
    +
    +- _none_
    +
    +**Examples:**
    +
     ```js
     // scan with deafult radio interface
     wiscan.scan(function (err, result) {
    -    console.log(result);
    -
    -    // [
    -    //     { address: 'D8:FE:E3:E5:9F:3B',  essid: 'sivann', ...   },
    -    //     { address: '20:0C:C8:01:1D:98',  essid: 'delta_01', ... },
    -    //     { address: '9C:D6:43:01:7E:C7',  essid: 'AVIS', ...     },
    -    //     ...
    -    // ]
    +  console.log(result);
    +
    +  // [
    +  //     { address: 'D8:FE:E3:E5:9F:3B',  essid: 'sivann', ...   },
    +  //     { address: '20:0C:C8:01:1D:98',  essid: 'delta_01', ... },
    +  //     { address: '9C:D6:43:01:7E:C7',  essid: 'AVIS', ...     },
    +  //     ...
    +  // ]
     });
     
     // scan with given radio interface
     wiscan.scan('ra0', function (err, result) {
    -    console.log(result);
    +  console.log(result);
     });
     
     // given radio interface is not valid
     wiscan.scan('foo', function (err, result) {
    -    console.log(err);   // [Error: No such wireless device: foo]
    +  console.log(err); // [Error: No such wireless device: foo]
     });
     ```
     
    -********************************************
    +---
     
     <a name="API_scanByEssid"></a>
    +
     ### .scanByEssid([intf,] essid, callback)
    -Scan for a specific AP with its essid.  
     
    -**Arguments:**  
    +Scan for a specific AP with its essid.
     
    -1. `intf` (_String_, optional): A default value of `'ra0'` will be used if not given.  
    -2. `essid` (_String_): The ESSID of the AP to scan for.  
    -3. `callback` (_Function_): `function(err, result) { ... }`. The result is a report object, otherwise `null` if not found.  
    +**Arguments:**
    +
    +1. `intf` (_String_, optional): A default value of `'ra0'` will be used if not given.
    +2. `essid` (_String_): The ESSID of the AP to scan for.
    +3. `callback` (_Function_): `function(err, result) { ... }`. The result is a report object, otherwise `null` if not found.
     
     </br>
       
     **Returns:**  
       
     * _none_
     
    -**Examples:**  
    -  
    +**Examples:**
    +
     ```js
     wiscan.scanByEssid('sivann', function (err, result) {
    -    console.log(result);
    -
    -    // { 
    -    //     address: 'D8:FE:E3:E5:9F:3B',
    -    //     essid: 'sivann',
    -    //     mode: 'Master',
    -    //     channel: 1,
    -    //     frequency: '2.412 GHz',
    -    //     signal: -256,
    -    //     quality: 68,
    -    //     encryption: 'WPA2 PSK (AES-OCB)' // String
    -    // }
    +  console.log(result);
    +
    +  // {
    +  //     address: 'D8:FE:E3:E5:9F:3B',
    +  //     essid: 'sivann',
    +  //     mode: 'Master',
    +  //     channel: 1,
    +  //     frequency: '2.412 GHz',
    +  //     signal: -256,
    +  //     quality: 68,
    +  //     encryption: 'WPA2 PSK (AES-OCB)' // String
    +  // }
     });
     
     // AP not found
     wiscan.scanByEssid('no_such_ap', function (err, result) {
    -    console.log(result);    // null
    +  console.log(result); // null
     });
     ```
     
     ## License
    +
     MIT
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.