Implement QueryChecker to prevent SQL injections 57/121957/10
authorMu-Woong Lee <muwoong.lee@samsung.com>
Wed, 29 Mar 2017 12:29:02 +0000 (21:29 +0900)
committerMu-Woong Lee <muwoong.lee@samsung.com>
Tue, 25 Apr 2017 01:41:52 +0000 (10:41 +0900)
Change-Id: If625674bd7710d3d769e28687e333fa6d9f5f4ac
Signed-off-by: Mu-Woong Lee <muwoong.lee@samsung.com>
src/server/ContextStoreClient.cpp
src/server/QueryChecker.cpp
src/server/QueryChecker.h
src/server/SchemaChecker.cpp
src/shared/ContextStoreTypesPrivate.h

index 9985854..3727343 100644 (file)
@@ -24,8 +24,6 @@
 
 using namespace ctx;
 
-static QueryChecker __queryChecker;
-
 ContextStoreClient::ContextStoreClient(ServiceBase* hostService, const std::string& busName) :
        ClientBase(hostService, busName)
 {
@@ -91,7 +89,7 @@ Store* ContextStoreClient::__getStore(const std::string& uri)
 {
        Store* store = NULL;
 
-       if (!__queryChecker.validateUri(uri))
+       if (!QueryChecker::validateUri(uri))
                throw static_cast<int>(E_PARAM);
 
        if (isSystem()) {
@@ -128,7 +126,7 @@ void ContextStoreClient::__insert(Store& store, MethodCall& methodCall)
                throw static_cast<int>(E_PARAM);
        }
 
-       if (!__queryChecker.validateProjection(cols))
+       if (!QueryChecker::validateColumns(cols))
                throw static_cast<int>(E_PARAM);
 
        std::vector<std::shared_ptr<Tuple>> tuples = Tuple::buildFrom(vals);
@@ -151,13 +149,13 @@ void ContextStoreClient::__retrieve(Store& store, MethodCall& methodCall)
        if (!projection || !selection || !sortOrder)
                throw static_cast<int>(E_PARAM);
 
-       if (!__queryChecker.validateProjection(projection))
+       if (!QueryChecker::validateProjection(projection))
                throw static_cast<int>(E_PARAM);
 
-       if (!__queryChecker.validateSelection(selection))
+       if (!QueryChecker::validateSelection(selection))
                throw static_cast<int>(E_PARAM);
 
-       if (!__queryChecker.validateSortOrder(sortOrder))
+       if (!QueryChecker::validateSortOrder(sortOrder))
                throw static_cast<int>(E_PARAM);
 
        std::vector<std::shared_ptr<Tuple>> tuples;
@@ -179,7 +177,7 @@ void ContextStoreClient::__remove(Store& store, MethodCall& methodCall)
        if (!selection)
                throw static_cast<int>(E_PARAM);
 
-       if (!__queryChecker.validateSelection(selection))
+       if (!QueryChecker::validateSelection(selection))
                throw static_cast<int>(E_PARAM);
 
        methodCall.reply(store.remove(*this, selection));
index b5658e9..5c1172c 100644 (file)
@@ -1,4 +1,3 @@
-
 /*
  * Copyright (c) 2017 Samsung Electronics Co., Ltd.
  *
 #include <regex>
 #include "QueryChecker.h"
 
+#define COLUMN_REGEX   R"~(^ *[A-Za-z]\w* *$)~"
+#define AGGR_REGEX             R"~(^ *[A-Za-z]+\([A-Za-z]\w*\) *$)~"
+#define QUOTED_REGEX   R"~(\'.*?\')~"
+#define NUMBER_REGEX   R"~(^ *\-?[0-9]+(\.[0-9]+)? *$)~"
+#define ORDER_REGEX            R"~( +[Aa]|([Dd][Ee])([Ss][Cc]) *$)~"
+
 using namespace ctx;
 
-QueryChecker::QueryChecker()
+static bool __isColumnName(const std::string& column)
+{
+       static std::regex colRegex(COLUMN_REGEX, std::regex::optimize);
+       return std::regex_match(column, colRegex);
+}
+
+static bool __isAggregation(const std::string& aggr)
 {
+       static std::regex aggrRegex(AGGR_REGEX, std::regex::optimize);
+       return std::regex_match(aggr, aggrRegex);
 }
 
-QueryChecker::~QueryChecker()
+static bool __isNumber(const std::string& number)
+{
+       static std::regex numberRegex(NUMBER_REGEX, std::regex::optimize);
+       return std::regex_match(number, numberRegex);
+}
+
+QueryChecker::QueryChecker()
 {
 }
 
 bool QueryChecker::validateUri(const std::string& uri)
 {
-       static std::regex uriRegex(URI_REGEX("context\/record"), std::regex::optimize);
-       if (!std::regex_match(uri, uriRegex)) {
-               _E("Invalid parameter");
+       static std::regex uriRegex(STORE_URI_REGEX, std::regex::optimize);
+
+       if (!std::regex_match(uri, uriRegex))
                return false;
+
+       return true;
+}
+
+bool QueryChecker::validateColumns(const std::string& columns)
+{
+       std::vector<std::string> tokens = util::tokenizeString(columns, ",");
+
+       for (auto& token : tokens) {
+               if (!__isColumnName(token)) return false;
        }
+
        return true;
 }
 
 bool QueryChecker::validateProjection(const std::string& projection)
 {
-       // TODO
+       std::vector<std::string> tokens = util::tokenizeString(projection, ",");
+
+       for (auto& token : tokens) {
+               if (!__isColumnName(token) && !__isAggregation(token)) return false;
+       }
+
        return true;
 }
 
 bool QueryChecker::validateSelection(const std::string& selection)
 {
-       // TODO
+       if (selection.empty())
+               return true;
+
+       static std::regex quotedStrRegex(QUOTED_REGEX, std::regex::optimize);
+
+       std::string stripped = std::regex_replace(selection, quotedStrRegex, " ");
+       std::vector<std::string> tokens = util::tokenizeString(stripped, " ()!<>=");
+
+       for (auto& token : tokens) {
+               if (!__isColumnName(token) && !__isNumber(token)) return false;
+       }
+
+       return true;
+}
+
+bool QueryChecker::validateGrouping(const std::string& grouping)
+{
+       if (grouping.empty())
+               return true;
+
+       return validateColumns(grouping);
+}
+
+bool QueryChecker::validateHaving(const std::string& having)
+{
+       if (having.empty())
+               return true;
+
+       std::vector<std::string> tokens = util::tokenizeString(having, "!<>=");
+
+       if (tokens.size() != 2)
+               return false;
+
+       if (!__isAggregation(tokens[0]))
+               return false;
+
+       if (!__isNumber(tokens[1]))
+               return false;
+
        return true;
 }
 
 bool QueryChecker::validateSortOrder(const std::string& sortOrder)
 {
-       // TODO
+       if (sortOrder.empty())
+               return true;
+
+       static std::regex orderRegex(ORDER_REGEX, std::regex::optimize);
+
+       std::vector<std::string> tokens = util::tokenizeString(sortOrder, ",");
+
+       for (auto& token : tokens) {
+               token = std::regex_replace(token, orderRegex, EMPTY_STR);
+               if (!__isColumnName(token) && !__isAggregation(token)) return false;
+       }
+
        return true;
 }
index eaaf932..43c80ec 100644 (file)
@@ -23,16 +23,22 @@ namespace ctx {
 
        class QueryChecker {
        public:
-               QueryChecker();
-               ~QueryChecker();
+               static bool validateUri(const std::string& uri);
+
+               static bool validateColumns(const std::string& columns);
+
+               static bool validateProjection(const std::string& projection);
 
-               bool validateUri(const std::string& uri);
+               static bool validateSelection(const std::string& selection);
 
-               bool validateProjection(const std::string& projection);
+               static bool validateGrouping(const std::string& grouping);
 
-               bool validateSelection(const std::string& selection);
+               static bool validateHaving(const std::string& having);
 
-               bool validateSortOrder(const std::string& sortOrder);
+               static bool validateSortOrder(const std::string& sortOrder);
+
+       private:
+               QueryChecker();
        };
 
 }
index ecc19cc..f45797c 100644 (file)
 #include "Schema.h"
 #include "SchemaChecker.h"
 
-#define COL_REGEX "^[A-Za-z]+\\w*$"
+#define COL_REGEX "^[A-Za-z]\\w*$"
 
 using namespace ctx;
 
 SchemaChecker::SchemaChecker() :
-       __uriRegex(URI_REGEX("context\/record"), std::regex::optimize),
-       __privilegeRegex(URI_REGEX("privilege"), std::regex::optimize),
+       __uriRegex(STORE_URI_REGEX, std::regex::optimize),
+       __privilegeRegex(PRIVILEGE_REGEX, std::regex::optimize),
        __columnNameRegex(COL_REGEX, std::regex::optimize)
 {
 }
index 3651bb8..a8601a7 100644 (file)
@@ -71,5 +71,7 @@
 #define DEFAULT_QUERY_LIMIT    10
 
 #define URI_REGEX(CATEGORY) R"~(^http:\/\/[\w-]+(\.[\w-]+)*\/)~" CATEGORY R"~(\/[\w-]+(\.[\w-]+)*(\/[\w-]+(\.[\w-]+)*)*$)~"
+#define STORE_URI_REGEX                URI_REGEX("context\\/record")
+#define PRIVILEGE_REGEX                URI_REGEX("privilege")
 
 #endif