CVE-2022-28927 (published 2022-05-19T16:15Z)

A remote code execution (RCE) vulnerability in Subconverter v0.7.2 allows attackers to execute arbitrary code via crafted config and url parameters.

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-434
cwe_name: Unrestricted Upload of File with Dangerous Type
description: The product allows the upload or transfer of dangerous file types that are automatically processed within its environment.
extended_description: 
            
            
            
            
            
         
         

Fix information:
repo_url: https://github.com/tindy2013/subconverter
commit_details: repo_url: https://github.com/tindy2013/subconverter
author: Tindy X
author_date: 2022-04-04 00:38:44+08:00
msg: Enhancements

Add authorization check before loading scripts.
Add detailed logs when loading preference settings.
repository: repo_url: https://github.com/tindy2013/subconverter
repo_name: tindy2013/subconverter
description: Utility to convert between various subscription format
repo_language: C++
owner: tindy2013
file_changes: filename: nodemanip.cpp
change_type: ModificationType.MODIFY
diff: @@ -51,7 +51,7 @@ int addNodes(std::string link, std::vector<Proxy> &allNodes, int groupID, parse_
     link = replaceAllDistinct(link, "\"", "");
 
     /// script:filepath,arg1,arg2,...
-    script_safe_runner(parse_set.js_runtime, parse_set.js_context, [&](qjs::Context &ctx)
+    if(authorized) script_safe_runner(parse_set.js_runtime, parse_set.js_context, [&](qjs::Context &ctx)
     {
         if(startsWith(link, "script:")) /// process subscription with script
         {
@@ -380,7 +380,7 @@ void nodeRename(Proxy &node, const RegexMatchConfigs &rename_array, extra_settin
 
     for(const RegexMatchConfig &x : rename_array)
     {
-        if(!x.Script.empty())
+        if(!x.Script.empty() && ext.authorized)
         {
             script_safe_runner(ext.js_runtime, ext.js_context, [&](qjs::Context &ctx)
             {
@@ -432,7 +432,7 @@ std::string addEmoji(const Proxy &node, const RegexMatchConfigs &emoji_array, ex
 
     for(const RegexMatchConfig &x : emoji_array)
     {
-        if(!x.Script.empty())
+        if(!x.Script.empty() && ext.authorized)
         {
             std::string result;
             script_safe_runner(ext.js_runtime, ext.js_context, [&](qjs::Context &ctx)
@@ -481,7 +481,7 @@ void preprocessNodes(std::vector<Proxy> &nodes, extra_settings &ext)
     if(ext.sort_flag)
     {
         bool failed = true;
-        if(ext.sort_script.size())
+        if(ext.sort_script.size() && ext.authorized)
         {
             std::string script = ext.sort_script;
             if(startsWith(script, "path:"))

programming_language: C++
filename: subexport.cpp
change_type: ModificationType.MODIFY
diff: @@ -183,7 +183,7 @@ void groupGenerate(const std::string &rule, std::vector<Proxy> &nodelist, string
         filtered_nodelist.emplace_back(rule.substr(2));
     }
 #ifndef NO_JS_RUNTIME
-    else if(startsWith(rule, "script:"))
+    else if(startsWith(rule, "script:") && ext.authorized)
     {
         script_safe_runner(ext.js_runtime, ext.js_context, [&](qjs::Context &ctx){
             std::string script = fileGet(rule.substr(7), true);

programming_language: C++
filename: subexport.h
change_type: ModificationType.MODIFY
diff: @@ -39,6 +39,7 @@ struct extra_settings
     bool clash_classical_ruleset = false;
     std::string sort_script = "";
     std::string clash_proxies_style = "flow";
+    bool authorized = false;
 
     extra_settings() {};
     extra_settings(const extra_settings&) = delete;

programming_language: C++
filename: interfaces.cpp
change_type: ModificationType.MODIFY
diff: @@ -407,6 +407,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS)
     std::string proxy = parseProxy(global.proxySubscription);
 
     /// check other flags
+    ext.authorized = authorized;
     ext.append_proxy_type = argAppendType.get(global.appendType);
     if((argTarget == "clash" || argTarget == "clashr") && argGenClashScript.is_undef())
         argExpandRulesets.define(true);

programming_language: C++
filename: settings.cpp
change_type: ModificationType.MODIFY
diff: @@ -577,6 +577,7 @@ void readYAMLConf(YAML::Node &node)
         node["advanced"]["async_fetch_ruleset"] >> global.asyncFetchRuleset;
         node["advanced"]["skip_failed_links"] >> global.skipFailedLinks;
     }
+    writeLog(0, "Load preference settings in YAML format completed.", LOG_LEVEL_INFO);
 }
 
 template <class T, class... U>
@@ -786,13 +787,14 @@ void readTOMLConf(toml::value &root)
     {
         global.cacheSubscription = global.cacheConfig = global.cacheRuleset = 0;
     }
+
+    writeLog(0, "Load preference settings in TOML format completed.", LOG_LEVEL_INFO);
 }
 
 void readConf()
 {
     guarded_mutex guard(gMutexConfigure);
-    //std::cerr<<"Reading preference settings..."<<std::endl;
-    writeLog(0, "Reading preference settings...", LOG_LEVEL_INFO);
+    writeLog(0, "Loading preference settings...", LOG_LEVEL_INFO);
 
     eraseElements(global.excludeRemarks);
     eraseElements(global.includeRemarks);
@@ -815,11 +817,14 @@ void readConf()
     catch (YAML::Exception &e)
     {
         //ignore yaml parse error
+        writeLog(0, e.what(), LOG_LEVEL_DEBUG);
+        writeLog(0, "Unable to load preference settings as YAML.", LOG_LEVEL_DEBUG);
     }
     catch (toml::exception &e)
     {
         //ignore toml parse error
         writeLog(0, e.what(), LOG_LEVEL_DEBUG);
+        writeLog(0, "Unable to load preference settings as TOML.", LOG_LEVEL_DEBUG);
     }
 
     INIReader ini;
@@ -828,8 +833,7 @@ void readConf()
     int retVal = ini.ParseFile(global.prefPath);
     if(retVal != INIREADER_EXCEPTION_NONE)
     {
-        //std::cerr<<"Unable to load preference settings. Reason: "<<ini.GetLastError()<<"\n";
-        writeLog(0, "Unable to load preference settings. Reason: " + ini.GetLastError(), LOG_LEVEL_FATAL);
+        writeLog(0, "Unable to load preference settings as INI. Reason: " + ini.GetLastError(), LOG_LEVEL_FATAL);
         return;
     }
 
@@ -1070,8 +1074,7 @@ void readConf()
     ini.GetBoolIfExist("async_fetch_ruleset", global.asyncFetchRuleset);
     ini.GetBoolIfExist("skip_failed_links", global.skipFailedLinks);
 
-    //std::cerr<<"Read preference settings completed."<<std::endl;
-    writeLog(0, "Read preference settings completed.", LOG_LEVEL_INFO);
+    writeLog(0, "Load preference settings in INI format completed.", LOG_LEVEL_INFO);
 }
 
 int loadExternalYAML(YAML::Node &node, ExternalConfig &ext)

programming_language: C++
filename: settings.h
change_type: ModificationType.MODIFY
diff: @@ -29,7 +29,7 @@ struct Settings
     bool printDbgInfo = false, CFWChildProcess = false, appendUserinfo = true, asyncFetchRuleset = false, surgeResolveHostname = true;
     std::string accessToken, basePath = "base";
     std::string custom_group;
-    int logLevel = LOG_LEVEL_INFO;
+    int logLevel = LOG_LEVEL_VERBOSE;
     long maxAllowedDownloadSize = 1048576L;
     string_map aliases;
 

programming_language: C++