将 mysql_real_escape 字符串与 sprintf 合并
Combine mysql_real_escape string with sprintf
可能是我太笨了,懒得再找了。
总之,情况是这样的。
为了防止 SQL-注入,我需要使用 mysql_real_escape_string
,但是这个函数非常笨重并且需要 'lot' 额外的代码。我想将函数隐藏在本质上 sprintf
-function.
思路:每当sprintf
遇到%s
,就在对应的va_arg上运行mysql_real_escape_string
,然后添加到目标字符串中.
示例:
doQuery("SELECT * FROM `table` WHERE name LIKE '%%s%%';", input);
假设 input
是一个类似于 Tom'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.
}
这个函数是凭记忆写的,我不能 100% 确定它的正确性,但我想你明白了。无论如何,明显的安全风险在于占位符 1。如果攻击者想到将所述 1(数值,而不是字符“1”)放入输入字符串中,他将自动获得一个攻击点,即非-转义撇号。
现在,有没有人知道如何解决这个问题并仍然获得我想要的行为,最好不要为我要发送到数据库的每个字符串分配额外的缓冲区?如果可能的话,我还想避免覆盖整个 sprintf 函数。
非常感谢。
经过一段时间的思考,我相信找到了一个比较简单的答案,可以很好地达到目的。
我只需要计算我用占位符替换的撇号的出现次数,然后在解析格式化字符串时倒数。如果我发现占位符的次数比我第一次通过时计算的要多,我就会知道其中一个参数包含非法字符,因此无效,不应传递给数据库。
编辑:
很晚了,但我想现在(因为我再次偶然发现了同样的问题)我找到了一个好方法。笨重,但可行。
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;
}
这个怪物所做的是以下步骤:
- 确定是否需要进行任何格式化。 (如果其中没有任何参数,则不会在扩展和东西上浪费太多时间)
- 确定需要的长度(包括0)和错误检查。
- 预留足够的空间进行一次完全展开,设置标记并展开。
- 检查,是否有任何 'strings to be escaped' 预期(由 '' 标记)
- 检查控制字符 1 是否在展开的某处。如果是这样,您就会知道这是一次未遂攻击,并且可以立即予以拒绝。
- 用您选择的控制字符替换每次出现的撇号。 (一个,不能作为参数传递)
- 使用更改后的格式字符串再次展开查询。
- 寻找控制字符作为字符串的分隔符并将整个内容复制到最终缓冲区,然后将其传递给 mysql
- 释放过期的动态内存。
我想我终于对这个解决方案感到满意了……我只花了 1.5 年的时间就弄明白了。 :D
我希望这对其他人也有帮助。
可能是我太笨了,懒得再找了。
总之,情况是这样的。
为了防止 SQL-注入,我需要使用 mysql_real_escape_string
,但是这个函数非常笨重并且需要 'lot' 额外的代码。我想将函数隐藏在本质上 sprintf
-function.
思路:每当sprintf
遇到%s
,就在对应的va_arg上运行mysql_real_escape_string
,然后添加到目标字符串中.
示例:
doQuery("SELECT * FROM `table` WHERE name LIKE '%%s%%';", input);
假设 input
是一个类似于 Tom'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.
}
这个函数是凭记忆写的,我不能 100% 确定它的正确性,但我想你明白了。无论如何,明显的安全风险在于占位符 1。如果攻击者想到将所述 1(数值,而不是字符“1”)放入输入字符串中,他将自动获得一个攻击点,即非-转义撇号。
现在,有没有人知道如何解决这个问题并仍然获得我想要的行为,最好不要为我要发送到数据库的每个字符串分配额外的缓冲区?如果可能的话,我还想避免覆盖整个 sprintf 函数。
非常感谢。
经过一段时间的思考,我相信找到了一个比较简单的答案,可以很好地达到目的。
我只需要计算我用占位符替换的撇号的出现次数,然后在解析格式化字符串时倒数。如果我发现占位符的次数比我第一次通过时计算的要多,我就会知道其中一个参数包含非法字符,因此无效,不应传递给数据库。
编辑: 很晚了,但我想现在(因为我再次偶然发现了同样的问题)我找到了一个好方法。笨重,但可行。
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;
}
这个怪物所做的是以下步骤:
- 确定是否需要进行任何格式化。 (如果其中没有任何参数,则不会在扩展和东西上浪费太多时间)
- 确定需要的长度(包括0)和错误检查。
- 预留足够的空间进行一次完全展开,设置标记并展开。
- 检查,是否有任何 'strings to be escaped' 预期(由 '' 标记)
- 检查控制字符 1 是否在展开的某处。如果是这样,您就会知道这是一次未遂攻击,并且可以立即予以拒绝。
- 用您选择的控制字符替换每次出现的撇号。 (一个,不能作为参数传递)
- 使用更改后的格式字符串再次展开查询。
- 寻找控制字符作为字符串的分隔符并将整个内容复制到最终缓冲区,然后将其传递给 mysql
- 释放过期的动态内存。
我想我终于对这个解决方案感到满意了……我只花了 1.5 年的时间就弄明白了。 :D
我希望这对其他人也有帮助。