将mysql_real_escape字符串与sprintf结合使用

时间:2015-01-23 18:45:18

标签: c++ mysql security

也许我只是太愚蠢而无法再次搜索。

无论如何,情况就是这样。

为了防止SQL注入,我需要使用mysql_real_escape_string,但是这个功能非常笨重,并且需要“很多”额外的代码。我希望将函数保持在基本上sprintf - 函数。

的包装内

想法:每当sprintf遇到%s时,它会在相应的va_arg上运行mysql_real_escape_string,然后将其添加到目标字符串。

示例:

doQuery("SELECT * FROM `table` WHERE name LIKE '%%s%%';", input);

假设inputTom's diner之类的字符串,则完整查询将如下所示:

SELECT * FROM `table` WHERE name LIKE '%Tom\'s diner%';

我找到了一种相当优雅的方式来实现我想要的,但是存在与之相关的安全风险,我想知道是否有更好的方法。

以下是我正在尝试的内容:

void doQuery(const char *Format, ...) {
    char sQuery[1024], tQuery[1024], *pQuery = sQuery, *pTemp = tQuery;
    va_list val;
    strcpy(sQuery, Format);
    while((pQuery = strchr(pQuery, '\'')) != NULL) *pQuery = 1;
    va_start(val, Format);
    vsprintf(tQuery, sQuery, val);
    va_end(val);
    pQuery = sQuery;
    do {
        if(*pTemp == 1) {
            char *pSearch = strchr(pTemp, 1);
            if(!pSearch) return; //Error, missing second placeholder
            else {
                *pQuery++ = '\'';
                mysql_real_escape_string(sql, pQuery, pTemp, pSearch - pTemp);
                pQuery += strlen(pQuery);
                *pQuery++ = '\'';
                pTemp = pSearch;
            }
        } else *pQuery++ = *pTemp;
    } while(*pTemp++);
    //Execute query, return result, etc.
}

这个函数是从记忆中写的,我不是百分之百确定它的正确性,但我认为你明白了。无论如何,明显的安全风险取决于占位符1.如果攻击者想到将所述1(数值,而不是字符'1')放入输入字符串,他会自动有一个攻击点,即非攻击点-escaped撇号。

现在,有没有人有任何想法,我如何解决这个问题并仍然得到我想要的行为,最好不为我想要发送到数据库的每个字符串分配额外的缓冲区?如果可能的话,我还想避免重写整个sprintf函数。

非常感谢。

1 个答案:

答案 0 :(得分:1)

经过一段时间的思考后,我相信找到了一个相当简单的答案,这个答案很有用。

我只需要计算我用占位符替换的撇号的出现次数,然后在解析格式化的字符串时向后计数。如果我在第一次传递时发现占位符比我计算的更多,我会知道其中一个参数包含非法字符,因此无效并且不会传递给数据库。


编辑: 太晚了,但我想现在(因为我再一次偶然发现了同样的问题)我找到了一个好方法。笨重,但可行。

bool SQL::vQuery(const char *Format, va_list val) {
    bool Ret = true, bExpanded = false;
    if(strchr(Format, '%') != NULL) {                               //Is there any expanding to be done here?
        int32_t ReqLen = vsnprintf(NULL, 0, Format, val) + 1;       //Determine the required buffer length.
        if(ReqLen < 2) Ret          = false;                        //Lengthquery successful?
        else {
            char *Exp               = new char[ReqLen];             //Evaluation requires a sufficiently large buffer.
            bExpanded               = true;                         //Tell the footer of this function to free the query buffer.
            vsprintf(Exp, Format, val);                             //Expand the string into the first buffer.

            if(strchr(Format, '\'') == NULL) Format = Exp;          //No apostrophes found in the format(!) string? No escaping necessary.
            else if(strchr(Exp, 1)) Ret = false;                    //Illegal character detected. Abort.
            else {
                char *pExp          = Exp,
                    *Query          = new char[ReqLen * 2],         //Reserves (far more than) enough space for escaping.
                    *pQuery         = Query;

                strcpy(Query, Format);                              //Copy the format string to the (modifiable) Query buffer.
                while((pQuery = strchr(pQuery, '\'')) != NULL)
                    *pQuery         = 1;                            //Replace the character with the control character.

                vsprintf(Exp, Query, val);                          //Expand the whole thing AGAIN, this time with the substitutions.
                pQuery = Query;                                     //And rewind the pointer.

                while(char *pEnd = strchr(pExp, 1)) {               //Look for the text-delimiter.
                    *pEnd           = 0;                            //Terminate the string at this point.
                    strcpy(pQuery, pExp);                           //Copy the unmodified string to the final buffer.
                    pQuery          += pEnd - pExp;                 //And advance the pointer to the new end.

                    pExp            = ++pEnd;                       //Beginning of the 'To be escaped' string.
                    if((pEnd        = strchr(pExp, 1)) != NULL) {   //And what about the end?
                        *pEnd       = 0;                            //Terminate the string at this point.
                        *pQuery++   = '\'';
                        pQuery      += mysql_real_escape_string(pSQL, pQuery, pExp, pEnd - pExp);
                        *pQuery++   = '\'';
                        pExp        = ++pEnd;                       //End of the 'To be escaped' string.
                    } else Ret      = false;                        //Malformed query string.
                }
                strcpy(pQuery, pExp);                               //No more '? Just copy the rest.
                Format = Query;                                     //And please use the Query-Buffer instead of the raw Format.
                delete[] Exp;                                       //Get rid of the expansion buffer.
            }
        }
    }

    if(Ret) {
        if(result) mysql_free_result(result);                       //Gibt ein ggf. bereits vorhandenes Ergebnis wieder frei.
        Ret     = mysql_query(pSQL, Format, result);
        columns = (result) ? mysql_num_fields(result) : 0;
        row     = NULL;
    }
    if(bExpanded) delete[] Format;                                  //Query completed -> Dispose of buffer.
    return Ret;

}

这种怪物的作用是以下步骤:

  1. 确定是否有任何格式要完成。 (如果没有任何参数,那么在扩展和填充时浪费周期就不多了)
  2. 确定所需的长度(包括0)和错误检查。
  3. 为一次完全展开预留足够的空间,设置标记并展开。
  4. 检查是否有任何“要转义的字符串”(由''标记)
  5. 检查控制字符1是否在扩展中的某个位置。如果是这样,你就会知道这是一次未遂攻击,可以在那里否认它。
  6. 用您选择的控制字符替换每次出现的撇号。 (一个,不能作为参数传递)
  7. 使用更改后的格式字符串再次展开查询。
  8. 查找控制字符作为字符串的分隔符,并将整个事物复制到最终缓冲区中,然后传递给mysql
  9. 在动态记忆过期时释放它们。
  10. 我觉得我终于对这个解决方案感到满意了......我花了一年半的时间来弄明白。 :d

    我希望这也有助于其他人。