2011年11月6日星期日

encoding的问题

 

最近解决了一个问题:Web UI会接受用户输入的用户名和密码,通过AJAX把用户名/密码传递到backend,Java Servlet把利用用户名和密码调用一个shell script来check用户名/密码是否正确。这里遇到几个和encoding相关的问题,而这些都会导致如果username/password中存在特殊字符时无法正确地调用。

1. Javascript Encoding

首先,我们通过AJAX把username/password POST到backend,HTTP Post的参数和值的传递形式为:

param1=value1&param2=value2…

如果username/password中存在&或=,结果就是servlet无法正确地parse出param/value

解决方法是使用Javascript对value进行encoding,候选函数有:

  • escape
  • encodeURI
  • encodeURIComponent

而用来做POST value encoding最合适的选择是encodeURIComponet,因为:

  • escape不会编码字符:@×/+,而字符+会被服务器解析为空格
  • encodeURI不会编码字符:~!@#$&*()=:/,;?+',字符&和=显然会导致parse出错
  • encodeURIComponent不会编码字符: ~!*()',就是它了,HTTP Post的转义全部可以搞定~~~

2. Shell Encoidng

OK,现在AJAX已经可以把param/value传递到backend,Java Servlet会把这个参数传递给shell执行:

./myscript.sh 'username' 'password'

问题同上,如果username/password里有特殊字符怎么办?

首先,我们知道在shell里有些特殊字符是有特别含义的,比如&表示后台运行,$表示变量替换,\表示转行…

如果这些字符没有被引号包括起来('/”),那它的含义就是这里描述的了,比如我们了echo $PWD

为了能在参数中包含空格,可以通过双引号括起来,比如cd “/My Document”,问题来了,如果参数中有双引号,怎么办?可以通过\"进行转义,比如echo “Hello \”Zou Fei\””

可是如果在双引号中包含$\&!等特殊字符怎么办?所以我们希望的是shell不要帮我们做这种字符扩展,方式是通过单引号来括起来,在shell里单引号中间的字符是不会被shell解释的,比如echo ‘$PWD’会直接输出$PWD,而不是当前路径。

似乎已经完美了,可还有问题,如果参数里也含有单引号怎么办?转义呗,可是可是,在单引号中间的\'或者'’都不会被shell做转义啊(试一下echo ‘\'’)

man一下bash,可以找到一个很强悍的quoting:

http://www.gnu.org/software/bash/manual/bashref.html#ANSI_002dC-Quoting

3.1.2.4 ANSI-C Quoting

Words of the form $'string' are treated specially. The word expands to string, with backslash-escaped characters replaced as specified by the ANSI C standard. Backslash escape sequences, if present, are decoded as follows:

就是说对于$'param’这种形式的字符串,shell会使用ANSI C的方式进行escaping,比如\\表示\,\n表示回车,那我们就可以\'来表示'咯,所以echo $’\'’会输出单引号,而echo $’$PWD’也不会输出当前路径~~~

O了,下面把它变成代码,用它可以适用于你希望把任意字符作为参数传递给shell,同时不希望shell进行shell interpret:

// need to delete[] the returned char*
char * shell_escape(const char* str)
{
	// shell support the format $'string' for QUOTING (refer MAN page)
	// and the string can be ANSI c style escaping
	int len = strlen(str) * 2 + 3;
	char * new_str = new char[len];
	memset(new_str, 0, len);
	// add "$'" for begin
	strncpy(new_str, "$'", 2);
	int j = 0;
	for (int i = 0; i < strlen(str); i ++)
	{
		if (str[i] == '\\')
		{
			new_str[j++] = '\\';
			new_str[j++] = '\\';
		}
		else if (str[i] == '\'')
		{
			new_str[j++] = '\\';
			new_str[j++] = '\'';
		}
		else
		{
			new_str[j++] = str[i];
		}
	}
	// add "'" for end
	new_str[j] = '\'';
	return new_str;
}

它做的事情很简单,就是把\替换成\\,把'替换成\',然后在首尾加上$'…’,当然要用这个函数得记得delete[]返回值咯~~~

顺带说一句,这个方法还可以解决大多数的shell injection问题。

 

当然,这里只是考虑了encoding的问题,而事实上这种方案本身还有其他一系列问题,比如把password直接用来system是会导致password leak的(只要monitor process就可以了)

没有评论:

发表评论

Test Ads
Hello World