1、系统中经常需要将string转换为table,为求方便一般会直接用类似下面的方式:
function stringToTable(str)
local ret = loadstring("return "..str)()
return ret
end
问题1:这样的调用呢,本身在传入参数正确的情况下,是没有问题。如果传入参数非法,在调用loadstring的时候会失败,返回为nil,不能执行后面的调用操作
问题2:如果字符串是由不受信任的客户端传过来的,客户端可以通过构造特殊的字符串,达到执行LUA代码的目的,这就相当于有了一个远程执行代码的漏洞,例子如下:
str = "{1}, print('run print')"
stringToTable(str)
2、安全的转换方式,通过对编译后的指令进行检查,判断出是否是合法的构造TABLE的语句,如果是才执行。
static int luaB_makeACall (lua_State *L) {
size_t l;
const char *s = luaL_checklstring(L, 1, &l);
const char *chunkname = luaL_optstring(L, 2, s);
int ret = luaL_loadbuffer(L, s, l, chunkname);
LClosure *cl = NULL;
struct Proto *p = NULL;
Instruction *code = NULL;
int i = 0;
int funNum = 0;
if(ret != 0)
{
lua_pushnumber(L,-1);
return 1;
}
// 如果成功则检查是否有违规指令
cl = &clvalue(L->top - 1)->l;
p = cl->p;
code = p->code;
for (i=0;i< p->sizecode;++i)
{
int opcode = GET_OPCODE(code[i]);
switch(opcode)
{
case OP_NEWTABLE:
case OP_LOADK:
case OP_LOADBOOL:
case OP_LOADNIL:
case OP_SETTABLE:
case OP_SETLIST:
case OP_GETGLOBAL:
case OP_RETURN:
break;
case OP_CALL:
case OP_TAILCALL:
funNum++;
if (funNum > 1)
{
lua_pushnumber(L,-1);
return 1;
}
break;
default:
lua_pushnumber(L,-1);
return 1;
}
}
// 校验指令完毕后,执行函数
lua_call(L, 0, 1);
// 返回0表示执行成功
lua_pushnumber(L,0);
return 1;
}
笔者的公司在处理客户端NPC请求时,用了loadstring的方式对客户端传来的TABLE字符串进行解析,这样就存在以上的问题。
另外还有一部分请求在处理的时候,直接执行客户端请求的字符串,所以也有类似问题。
经过几种方案的测试,最终选择了使用检查编译后指令的方式,彻底解决这个问题。