一、介绍
动态污点分析(Dynamic Taint Analysis,DTA)是一种动态信息流分析方法,其跟踪程序运行时对数据的处理,并记录处理过程中数据的传播,污点分析的目的是找出目的数据结果与源数据之间的依赖关系。污点分析可以分为三个方面:污点标记、污点传播和污点检查。污点标记是指来自网络等不可信渠道的数据都会被标记为“污点”。在污点标记后,污点数据进行各种运算所得的结果也是不可信的,因此也被标记上了“被污染的”的属性,这个过程就是污点传播。
简介:Taint是一个PHP插件,主要的功能有检测XSS、SQL注入、命令注入、代码注入等漏洞。
原理:检查某些关键函数(是否直接使用(没有经过过滤或转义处理)了来自$_GET,$_POST,$_COOKIE,的数据,如使用则给出提示
二、安装使用
wget http://pecl.php.net/get/taint-1.2.2.tgz (下载最新的taint) tar zxvf taint-1.2.2.tgz cd taint-1.2.2 Phpize ./configure make make install 配置:php/lib/php.ini
成功安装后,会在nginx的error.log处生成warning日志:
三、漏洞测试:
XSS
漏洞代码:
$uri = Yii::app()->request->getParam('uri');echo $uri;
Error.log内容:
2018/10/09 11:39:29 [error] 14763#0: *2333 FastCGI sent in stderr: "PHP message: PHP Warning: actionIndex() [echo]: Attempt to echo a string that might be tainted in /home/dly/www/main/controller/TemplateController.php on line 18" while reading response header from upstream, client: 10.0.27.12, server: localhost, request: "GET /Template/index?uri=dlytestxssecho HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "localhost"
SQL
漏洞代码:
public function getLikeStableLink($url){ $strSql="select id from ".$this->table." where state= 2 AND stable_link like '%".$url."%' order by create_time desc"; $result=$this->db()->query($strSql); return $result; }
Error.log内容:
2018/10/09 14:28:03 [error] 14762#0: *2483 FastCGI sent in stderr: "PHP message: PHP Warning: query() [mysqli::query]: SQL statement contains data that might be tainted in /home/dly/www/common/shared/db/DB.php on line 531PHP message: PHP Warning: query() [mysqli::query]: SQL statement contains data that might be tainted in /home/dly/www/common/shared/db/DB.php on line 531" while reading response header from upstream, client: 10.0.27.12, server: localhost, request: "GET /getinnerlink/index?logid=634395945&url=https%3A%2f%2flocalhost%2fcredit%2f12312&category_id=8 HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "localhost"
任意文件读取
漏洞代码:
Echo $uri;
$this->renderFile($uri );
Error.log内容:
2018/10/09 15:36:19 [error] 14770#0: *2623 FastCGI sent in stderr: "PHP message: PHP Warning: actionIndex() [echo]: Attempt to echo a string that might be tainted in /home/dly/www/main/controller/TemplateController.php on line 18PHP message: PHP Warning: renderInternal() [require]: File path contains data that might be tainted in /home/dly/www/framework/web/CBaseController.php on line 123" while reading response header from upstream, client: 10.0.27.12, server: localhost, request: "GET /Template/index?uri=/etc/passwd HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "localhost"
命令执行
漏洞代码:
System($_GET['a']);
Error.log内容:2018/10/09 15:57:09 [error] 14769#0: *2651 FastCGI sent in stderr: "PHP message: PHP Warning: actionIndex() [system]: CMD statement contains data that might be tainted in /home/dly/www/main/controller/TemplateController.php on line 19" while reading response header from upstream, client: 10.0.27.12, server: localhost, request: "GET /Template/index?uri=ifconfig HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "localhost"
代码执行
漏洞代码:
$fun = $_GET['fun'];$par = $_GET['par'];$fun($par);
Error.log内容:
2018/10/09 16:00:38 [error] 14769#0: *2653 FastCGI sent in stderr: "PHP message: PHP Warning: actionIndex() [fcall]: Attempt to call a function which name might be tainted in /home/dly/www/main/controller/TemplateController.php on line 21" while reading response header from upstream, client: 10.0.27.12, server: localhost, request: "GET /Template/index?fun=phpinfo HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "localhost"
四、代码分析:
1、
#define IS_STR_TAINT_POSSIBLE (1<<7) //定义mark规则 #define TAINT_MARK(str) (GC_FLAGS((str)) |= IS_STR_TAINT_POSSIBLE) #define TAINT_POSSIBLE(str) (GC_FLAGS((str)) & IS_STR_TAINT_POSSIBLE) #define TAINT_CLEAN(str) (GC_FLAGS((str)) &= ~IS_STR_TAINT_POSSIBLE)
2、对用户传入的数据进行mark
PHP_RINIT_FUNCTION(taint) { if (SG(sapi_started) || !TAINT_G(enable)) { return SUCCESS; } //污染post数据 if (Z_TYPE(PG(http_globals)[TRACK_VARS_POST]) == IS_ARRAY) { php_taint_mark_strings(Z_ARRVAL(PG(http_globals)[TRACK_VARS_POST])); } //污染GET变量数据 if (Z_TYPE(PG(http_globals)[TRACK_VARS_GET]) == IS_ARRAY) { php_taint_mark_strings(Z_ARRVAL(PG(http_globals)[TRACK_VARS_GET])); } //污染COOKIE数据 if (Z_TYPE(PG(http_globals)[TRACK_VARS_COOKIE]) == IS_ARRAY) { php_taint_mark_strings(Z_ARRVAL(PG(http_globals)[TRACK_VARS_COOKIE])); } return SUCCESS; }
//标记所有的GET、COOKIE、POST、SERVER这些array中的每个key->value初始标记为taint
static void php_taint_mark_strings(zend_array *symbol_table) /* {{{ */ {
zval *val;
ZEND_HASH_FOREACH_VAL(symbol_table, val) {
ZVAL_DEREF(val);
if (Z_TYPE_P(val) == IS_ARRAY) {
php_taint_mark_strings(Z_ARRVAL_P(val));
} else if (IS_STRING == Z_TYPE_P(val) && Z_STRLEN_P(val)) {
TAINT_MARK(Z_STR_P(val));
}
} ZEND_HASH_FOREACH_END();
} /* }}} */
3、遇到非变量赋值函数时,去掉MARK
赋值函数list:
static void php_taint_override_functions() /* {{{ */ { const char *f_join = "join"; const char *f_trim = "trim"; const char *f_split = "split"; const char *f_rtrim = "rtrim"; const char *f_ltrim = "ltrim"; const char *f_strval = "strval"; const char *f_strstr = "strstr"; const char *f_substr = "substr"; const char *f_sprintf = "sprintf"; const char *f_explode = "explode"; const char *f_implode = "implode"; const char *f_str_pad = "str_pad"; const char *f_vsprintf = "vsprintf"; const char *f_str_replace = "str_replace"; const char *f_str_ireplace = "str_ireplace"; const char *f_strtolower = "strtolower"; const char *f_strtoupper = "strtoupper"; const char *f_dirname = "dirname"; const char *f_basename = "basename"; const char *f_pathinfo = "pathinfo"; php_taint_override_func(f_strval, PHP_FN(taint_strval), &TAINT_O_FUNC(strval)); php_taint_override_func(f_sprintf, PHP_FN(taint_sprintf), &TAINT_O_FUNC(sprintf)); php_taint_override_func(f_vsprintf, PHP_FN(taint_vsprintf), &TAINT_O_FUNC(vsprintf)); php_taint_override_func(f_explode, PHP_FN(taint_explode), &TAINT_O_FUNC(explode)); php_taint_override_func(f_split, PHP_FN(taint_explode), NULL); php_taint_override_func(f_implode, PHP_FN(taint_implode), &TAINT_O_FUNC(implode)); php_taint_override_func(f_join, PHP_FN(taint_implode), NULL); php_taint_override_func(f_trim, PHP_FN(taint_trim), &TAINT_O_FUNC(trim)); php_taint_override_func(f_rtrim, PHP_FN(taint_rtrim), &TAINT_O_FUNC(rtrim)); php_taint_override_func(f_ltrim, PHP_FN(taint_ltrim), &TAINT_O_FUNC(ltrim)); php_taint_override_func(f_str_replace, PHP_FN(taint_str_replace), &TAINT_O_FUNC(str_replace)); php_taint_override_func(f_str_ireplace, PHP_FN(taint_str_ireplace), &TAINT_O_FUNC(str_ireplace)); php_taint_override_func(f_str_pad, PHP_FN(taint_str_pad), &TAINT_O_FUNC(str_pad)); php_taint_override_func(f_strstr, PHP_FN(taint_strstr), &TAINT_O_FUNC(strstr)); php_taint_override_func(f_strtolower, PHP_FN(taint_strtolower), &TAINT_O_FUNC(strtolower)); php_taint_override_func(f_strtoupper, PHP_FN(taint_strtoupper), &TAINT_O_FUNC(strtoupper)); php_taint_override_func(f_substr, PHP_FN(taint_substr), &TAINT_O_FUNC(substr)); php_taint_override_func(f_dirname, PHP_FN(taint_dirname), &TAINT_O_FUNC(dirname)); php_taint_override_func(f_basename, PHP_FN(taint_basename), &TAINT_O_FUNC(basename)); php_taint_override_func(f_pathinfo, PHP_FN(taint_pathinfo), &TAINT_O_FUNC(pathinfo)); } /* }}} */
去掉taint标记
PHP_FUNCTION(untaint) { zval *args; int argc; int i; if (!TAINT_G(enable)) { RETURN_TRUE; } if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) { //zend_parse_parameters获取函数传递的参数 //ZEND_NUM_ARGS函数中传递参数的个数 return; } for (i = 0; i < argc; i++) { zval *el = &args[i]; ZVAL_DEREF(el); //判断变量是string型,并且被标记过 if (IS_STRING == Z_TYPE_P(el) && TAINT_POSSIBLE(Z_STR_P(el))) { TAINT_CLEAN(Z_STR_P(el)); } } RETURN_TRUE; }
4、漏洞判断逻辑(XSS举例):
static int php_taint_echo_handler(zend_execute_data *execute_data) /* {{{ */ { const zend_op *opline = execute_data->opline;//指向当前执行的opcode,初始时指向zend_op_array起始位置 //* opcode指令:即PHP代码具体对应的处理动作,与二进制程序中的代码段对应 taint_free_op free_op1; zval *op1; op1 = php_taint_get_zval_ptr(execute_data, opline->op1_type, opline->op1, &free_op1, BP_VAR_R, 0); //获取传入函数的参数 if (op1 && IS_STRING == Z_TYPE_P(op1) && TAINT_POSSIBLE(Z_STR_P(op1))) { //Z_STRVAL_P(op1)获取zval中的string类型的值。XSS漏洞只有在string类型的时候才会触发 if (opline->extended_value) {//opline->extended_value据百度大佬说,这个取出来的函数参数,因为echo使用时,不存在参数传递,但是这个结构却是uint32_t类型感觉跟函数名有关 php_taint_error("print", "Attempt to print a string that might be tainted"); } else { php_taint_error("echo", "Attempt to echo a string that might be tainted"); } } return ZEND_USER_OPCODE_DISPATCH; } /* }}} */
五、taint存在的问题:
1、无法检测二次漏洞,二次注入,二次命令注入等。
2、使用白名单进行过滤后,依旧会报出来
3、最重要的是,taint中对不在上面所说的赋值函数list中的函数,如base64_decode都当成过滤函数看待,会导致大量漏洞
如:
$a = base64_decode($_GET['a']); echo $a;
这种明显是一段XSS漏洞,但是taint无法报出来。
所以修复建议:去掉untaint,自己手写取消mark逻辑。我也是在思考中,有编码进展会直接更新到这篇博客。