CVE-2019-19250 (published 2019-11-25T17:15Z)

OpenTrade before 2019-11-23 allows SQL injection, related to server/modules/api/v1.js and server/utils.js.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-89
cwe_name: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
description: The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.
extended_description: 
            
            
         
         

Fix information:
repo_url: https://github.com/3s3s/opentrade
commit_details: repo_url: https://github.com/3s3s/opentrade
author: Your Name
author_date: 2019-11-23 08:21:47+01:00
msg: critical bug with SQL injection fixed!
repository: repo_url: https://github.com/3s3s/opentrade
repo_name: 3s3s/opentrade
description: OpenTrade - Open Source Cryptocurrency Exchange
repo_language: JavaScript
owner: 3s3s
file_changes: filename: v1.js
change_type: ModificationType.MODIFY
diff: @@ -168,7 +168,7 @@ exports.onGetOrderbook = function(req, res)
     });
 }
 
-exports.onGetMarketSummary = function(req, res)
+exports.onGetMarketSummary = async function(req, res)
 {
     const dataParsed = url.parse(req.url);
     if (!dataParsed || !dataParsed.query)
@@ -190,16 +190,19 @@ exports.onGetMarketSummary = function(req, res)
 
     const MarketName = queryStr.market;
     
-    g_constants.dbTables['coins'].selectAll('name, ticker, info, icon', 'ticker="'+data[1]+'"', '', (err, rows) => {
-        if (err || !rows)
-            return onError(req, res, err && err.message ? err.message : 'unknown database error');
+    try
+    {
+    //g_constants.dbTables['coins'].selectAll('name, ticker, info, icon', 'ticker="'+data[1]+'"', '', (err, rows) => {
+    //    if (err || !rows)
+    //        return onError(req, res, err && err.message ? err.message : 'unknown database error');
 
-        if (!rows.length)
-            return onError(req, res, 'ticker '+data[1]+' not found');
+        //if (!rows.length)
+        //    return onError(req, res, 'ticker '+data[1]+' not found');
 
-        const COIN = rows[0];
-        const coin_icon_src = rows[0].icon;
-        const coin_info = JSON.parse(utils.Decrypt(rows[0].info));
+        //const COIN = rows[0];
+        const COIN = await utils.GetCoinFromTicker(data[1]);
+        const coin_icon_src = COIN.icon;
+        const coin_info = JSON.parse(utils.Decrypt(COIN.info));
         
         const WHERE = 'coin="'+COIN.name+'" AND time*1 > ('+Date.now()+'*1 - '+period+'*3600*1000)';
         
@@ -207,11 +210,8 @@ exports.onGetMarketSummary = function(req, res)
         
         let ret = GetCache(METHOD);
         if (ret)
-        {
-            onSuccess(req, res, ret);
-            return;
-        }
-        
+            return onSuccess(req, res, ret);
+
         g_constants.dbTables['history'].selectAll('max((fromBuyerToSeller*1)/fromSellerToBuyer) AS Height, min((fromBuyerToSeller*1)/fromSellerToBuyer) AS Low, sum(fromSellerToBuyer*1) AS Volume', WHERE, 'GROUP BY coin', (err, rows) => {
             if (err || !rows)
             {
@@ -254,7 +254,12 @@ exports.onGetMarketSummary = function(req, res)
             
          });
         
-    });
+    }
+    catch (e)
+    {
+        return onError(req, res, e.message || 'unknown error');
+    }
+    //});
 }
 
 exports.onGetMarketHistory = function(req, res)
@@ -310,7 +315,7 @@ exports.onGetMarketHistory = function(req, res)
     });
 }
 
-function SubmitOrder(req, res, buysell)
+async function SubmitOrder(req, res, buysell)
 {
     try
     {
@@ -323,7 +328,7 @@ function SubmitOrder(req, res, buysell)
         const currency = queryStr.market.split('-');
         if (!currency.length || currency.length != 2) throw new Error('Bad request. Parameter currency is invalid.');
         
-        utils.GetCoinFromTicker(currency[1], coin => {
+        const coin = await utils.GetCoinFromTicker(currency[1]); //, coin => {
             if (!coin || !coin.name) 
                 return onError(req, res, 'Coin ticker not found');
 
@@ -352,7 +357,7 @@ function SubmitOrder(req, res, buysell)
                     return onError(req, res, e.message);
                 }
             })
-        });    
+        //});    
     }
     catch(e) {
         return onError(req, res, e.message);
@@ -417,7 +422,7 @@ exports.onMarketCancel = function(req, res)
     }
 }
 
-exports.onMarketGetOpenOrders = function(req, res)
+exports.onMarketGetOpenOrders = async function(req, res)
 {
     try
     {
@@ -430,7 +435,7 @@ exports.onMarketGetOpenOrders = function(req, res)
         const currency = queryStr.market.split('-');
         if (!currency.length || currency.length != 2) throw new Error('Bad request. Parameter currency is invalid.');
         
-        utils.GetCoinFromTicker(currency[1], coin => {
+        const coin = await utils.GetCoinFromTicker(currency[1]); //, coin => {
             if (!coin || !coin.name) 
                 return onError(req, res, 'Coin ticker not found');
 
@@ -468,14 +473,14 @@ exports.onMarketGetOpenOrders = function(req, res)
                     return onError(req, res, e.message);
                 }
             })
-        });    
+        //});    
     }
     catch(e) {
         return onError(req, res, e.message);
     }
 }
 
-exports.onAccountGetBalance = function(req, res)
+exports.onAccountGetBalance = async function(req, res)
 {
     try
     {
@@ -485,7 +490,7 @@ exports.onAccountGetBalance = function(req, res)
         const queryStr = querystring.parse(dataParsed.query);
         if (!queryStr.apikey || !queryStr.nonce || !queryStr.currency) throw new Error('Bad request. Required parameter (apikey or nonce or currency) not found.');
     
-        utils.GetCoinFromTicker(queryStr.currency, coin => {
+        const coin = await utils.GetCoinFromTicker(queryStr.currency);//, coin => {
             if (!coin || !coin.name) 
                 return onError(req, res, 'Coin ticker not found');
 
@@ -516,7 +521,7 @@ exports.onAccountGetBalance = function(req, res)
                     return onError(req, res, e.message);
                 }
             })
-        });    
+        //});    
     }
     catch(e) {
         return onError(req, res, e.message);
@@ -599,7 +604,7 @@ exports.onEditAPIkey = function(req, res)
     }
 }
 
-exports.onAccountGetDepositAddress = function(req, res)
+exports.onAccountGetDepositAddress = async function(req, res)
 {
     const dataParsed = url.parse(req.url);
     if (!dataParsed || !dataParsed.query || !req.headers['apisign'])
@@ -609,7 +614,7 @@ exports.onAccountGetDepositAddress = function(req, res)
     if (!queryStr.apikey || !queryStr.nonce || !queryStr.currency)
         return onError(req, res, 'Bad request. Required parameter (apikey or nonce or currency) not found.');
     
-    utils.GetCoinFromTicker(queryStr.currency, coin => {
+    const coin = utils.GetCoinFromTicker(queryStr.currency); //, coin => {
         if (!coin || !coin.name) 
             return onError(req, res, 'Coin ticker not found');
 
@@ -632,7 +637,7 @@ exports.onAccountGetDepositAddress = function(req, res)
                 return onError(req, res, e.message);
             }
         })
-    });    
+    //});    
 }
 
 exports.onAccountGetOrder = function(req, res)
@@ -745,7 +750,7 @@ exports.onRedeemCoupon = function(req, res)
 }
 
 
-exports.onAccountWithdraw = function(req, res)
+exports.onAccountWithdraw = async function(req, res)
 {
     const dataParsed = url.parse(req.url);
     if (!dataParsed || !dataParsed.query || !req.headers['apisign'])
@@ -761,7 +766,7 @@ exports.onAccountWithdraw = function(req, res)
     if (queryStr.quantity < 0.00001)
         return onError(req, res, 'Bad request. quantity < 0.00001 (is too low)');
 
-    utils.GetCoinFromTicker(queryStr.currency, coin => {
+    const coin = await utils.GetCoinFromTicker(queryStr.currency); //, coin => {
         if (!coin || !coin.name) 
             return onError(req, res, 'Coin ticker not found');
 
@@ -814,7 +819,7 @@ exports.onAccountWithdraw = function(req, res)
                 });
             }
         }, req);
-    });
+    //});
 }
 
 function CheckAPIkey(strKey, strSign, strQuery, callback, req)

programming_language: JavaScript
filename: utils.js
change_type: ModificationType.MODIFY
diff: @@ -648,8 +648,14 @@ exports.CheckCoin = function(coin, callback)
 
 exports.GetCoinFromTicker = function(ticker, callback)
 {
-    g_constants.dbTables['coins'].selectAll('ROWID AS id, *', 'ticker="'+escape(ticker)+'"', '', (err, rows) => {
-        if (err || !rows || !rows.length) return callback({});
-        callback(rows[0]);
-    })
+    return new Promise(async (ok, cancel) => {
+        try {
+            const rows = await g_constants.dbTables['coins'].Select('ROWID AS id, *', 'ticker="'+escape(ticker)+'"', '');
+            if (!rows || !rows.length || !rows[0].name) throw new Error('Coin ticker ('+escape(ticker)+') not found!');
+            return ok(rows[0]);
+        }
+        catch(e) {
+            return cancel(e);
+        }
+    });
 }
\ No newline at end of file

programming_language: JavaScript