最近解决了一个问题:Web UI会接受用户输入的用户名和密码,通过AJAX把用户名/密码传递到backend,Java Servlet把利用用户名和密码调用一个shell script来check用户名/密码是否正确。这里遇到几个和encoding相关的问题,而这些都会导致如果username/password中存在特殊字符时无法正确地调用。
1. Javascript Encoding
首先,我们通过AJAX把username/password POST到backend,HTTP Post的参数和值的传递形式为:
param1=value1¶m2=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就可以了)
没有评论:
发表评论