简单了解taint 污点追踪

php扩展流程

先 模块初始化阶段(MINIT)注册常量或者类等初始化操作
然后 模块激活阶段(RINIT)该过程发生在请求阶段, 例如通过url请求某个页面,则在每次请求之前都会进行模块激活(RINIT请求开始)。 例如PHP注册了一些扩展模块

taint

taint主要由三部分构成,污点标记、污点传播、污点沉降

污点方法

//定义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)

我们根据污点函数可以看到 不同版本添加污点的方式不同
是在 php_taint.h 里面了

# if PHP_VERSION_ID >=70300
#  define EX_CONSTANT(op) RT_CONSTANT(EX(opline), op)
#  undef IS_STR_TAINT_POSSIBLE
#  define IS_STR_TAINT_POSSIBLE (1<<5) /* GC_PROTECTED */
#  define TAINT_MARK(str)     GC_ADD_FLAGS(str, IS_STR_TAINT_POSSIBLE)
#  define TAINT_POSSIBLE(str) (GC_FLAGS((str)) & IS_STR_TAINT_POSSIBLE)
#  define TAINT_CLEAN(str)    GC_DEL_FLAGS(str, IS_STR_TAINT_POSSIBLE)
# else
#  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)
# endif

然后在c文件中可以看到对这三个函数的调用

PHP_RINIT_FUNCTION

对输入源进行污点标记
模块被调用的函数 PHP_RINIT_FUNCTION
可以看到数据输入层面只有这三点,对于webshell的检查肯定是不够的 比如 从请求头中 从环境变量中等等

    // 污染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]));
	}

使用这个函数污染 php_taint_mark_strings

例如这种是全局变量 TRACK_VARS_COOKIE 还有啥呢

TRACK_VARS_POST
TRACK_VARS_GET
TRACK_VARS_FILES
TRACK_VARS_COOKIE
TRACK_VARS_ENV
TRACK_VARS_SERVER
TRACK_VARS_REQUEST

这些是数据输入点 外部传入的话都会打污点标记

标记所有的GET、COOKIE、POST、SERVER这些array中的每个key->value初始标记为污点

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();
} /* }}} */

PHP_MINIT_FUNCTION 数据传递

	php_taint_register_handlers(); // 进行关键执行函数hook  通过opcode
	php_taint_override_functions(); // 对数据传递的函数进行hook 通过劫持handler

在数据传递的过程中对传递的数据也认为是污染的
有哪些函数是危险函数并且认为会污染数据的
php_taint_override_functions

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));

} /* }}} */

怎么hook的呢

劫持hadnler

保留原来函数的handler后替换handler为我们hook的函数

比如 taint_strval 输出字符串值

PHP_FUNCTION(taint_strval) {
	zval *num;
	int tainted = 0;

	if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &num) == FAILURE) {
		return;
	}
    // 判断参数是否有污点 有污点 tainted 设置为1
	if (Z_TYPE_P(num) == IS_STRING && TAINT_POSSIBLE(Z_STR_P(num))) {
		tainted = 1;
	}
    // 调用真实的函数
	TAINT_O_FUNC(strval)(INTERNAL_FUNCTION_PARAM_PASSTHRU);
    // 如果之前是污点的 有返回值且返回值不等于参数 并且 返回值不是空 就污染返回值
	if (tainted && IS_STRING == Z_TYPE_P(return_value) 
			&& Z_STR_P(return_value) != Z_STR_P(num) && Z_STRLEN_P(return_value)) {
		TAINT_MARK(Z_STR_P(return_value));
	}
}

其他函数的写法类似

我们可以看到这个是不全的,例如对base64编码的就没有进行hook

PHP_MINIT_FUNCTION 数据执行

对传入的数据最后的执行函数的opcode进行hook

在函数 PHP_MINIT_FUNCTION (注册常量或者类等初始化操作) 中

	php_taint_register_handlers();
	php_taint_override_functions();

php_taint_register_handlers 中opcode进行hook

static void php_taint_register_handlers() /* {{{ */ {
	int idx;
	for (idx = 0; idx < sizeof(override_opcode_handlers)/sizeof(taint_custom_handler); idx++) {
		origin_opcode_handler[idx] = (void*)zend_get_user_opcode_handler(override_opcode_handlers[idx].opcode);
	}
	for (idx = 0; idx < sizeof(override_opcode_handlers)/sizeof(taint_custom_handler); idx++) {
		zend_set_user_opcode_handler(override_opcode_handlers[idx].opcode, (user_opcode_handler_t)override_opcode_handlers[idx].handler);
	}
	return;
} /* }}} */

所有hook的数据执行函数

static const taint_custom_handler override_opcode_handlers[] = {
	{ ZEND_ECHO, php_taint_echo_handler },
	{ ZEND_EXIT, php_taint_exit_handler },
	{ ZEND_INIT_USER_CALL, php_taint_init_dynamic_fcall_handler },
	{ ZEND_INIT_DYNAMIC_CALL, php_taint_init_dynamic_fcall_handler },
	{ ZEND_INCLUDE_OR_EVAL, php_taint_include_or_eval_handler },
	{ ZEND_CONCAT, php_taint_concat_handler },
	{ ZEND_FAST_CONCAT, php_taint_concat_handler },
#if PHP_VERSION_ID < 70400
	{ ZEND_ASSIGN_CONCAT, php_taint_assign_concat_handler },
#else
	{ ZEND_ASSIGN_OP, php_taint_assign_op_handler },
	{ ZEND_ASSIGN_DIM_OP, php_taint_assign_dim_op_handler },
	{ ZEND_ASSIGN_OBJ_OP, php_taint_assign_obj_op_handler },
#endif
	{ ZEND_ROPE_END, php_taint_rope_handler },
	{ ZEND_DO_FCALL, php_taint_fcall_handler },
	{ ZEND_DO_ICALL, php_taint_fcall_handler },
	{ ZEND_DO_FCALL_BY_NAME, php_taint_fcall_handler }
};

把所有的函数进行hook 使用 zend_set_user_opcode_handler

最后是判断逻辑
是根据opcode进行hook关键的执行函数

比如说 echo php_taint_echo_handler
如果给关键执行函数传入的参数是污点的 那么就输出 危险

static int php_taint_echo_handler(zend_execute_data *execute_data) /* {{{ */ {
	const zend_op *opline = execute_data->opline;
	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))) {
		if (opline->extended_value) {
			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");
		}
	}

	CALL_ORIGIN_HANDLER();
	return ZEND_USER_OPCODE_DISPATCH;
} /* }}} */

检测webshell

我们先简单写一个一句话并且在taint中加一些debug信息
在这里插入图片描述
可以看到先进入minit 先使用opcode hook关键的执行函数 然后hook 数据传递的函数
再之后进入rinit 对数据输入进行污点标记 最后执行关键函数的数据值是污染的那么就认为有问题

如何去除污点

  1. 对于 数据经过排除hook的危险函数的函数则返回值产生新的变量的则相当于去除了污点
  2. 对于特定的可控的可以我们自定义去手动去除污点 TAINT_CLEAN

一些问题

如果对webshell加上@ 至少默认情况下 taint不会输出关键信息 绕过 对于没有hook的传递函数和数据输入点都会产生绕过

之前一直以为是会有一个污染链路的情况,没想到只是把所有外部输入和部分危险函数的数据标记为污点后最后判断关键执行点。然后这个链路可以我们自己去加

参考

taint 污点追踪分析的一些文章
https://www.cnblogs.com/ermei/p/9778021.html
https://www.dazhuanlan.com/2020/04/01/5e84699490d0c/
https://www.laruence.com/tag/taint
https://xz.aliyun.com/t/4268
https://www.jianshu.com/p/c6dea66c54f3

扩展概念
https://www.jianshu.com/p/98eec8b08a8e

PHP 扩展添加方法
https://xz.aliyun.com/t/4214

https://xz.aliyun.com/t/7316
https://www.anquanke.com/post/id/98938

taint扩展的策略和敏感函数黑名单
https://www.cnblogs.com/linzhenjie/p/5485474.html

php语法解析
python版的php语法解析
https://github.com/g-i-o-/pyphp
https://github.com/Alexey-T/pyPhpTree
go版本的php语法解析
https://github.com/z7zmey/php-parser
https://github.com/stephens2424/php
python版通用的高级语法解析
https://github.com/autosoft-dev/tree-hugger

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
SQL注入的原理 什么SQL注入 将SQL代码插入到应用程序的输入参数中,之后,SQL代码被传递到数据库执行。从而达到对应用程序的攻击目的。 注入原理 常见攻击方法 检测是否可以注入【检测注入点】 示例:http://192.168.0.1/temp.aspx?id=a or 1=1-- 如果上面语句可以执行说明可以实现注入,则可以 利用系统过程、系统表注入数据库 示例【给系统添加一个用户,黑客可以实现远程登录控制服务器】:http://192.168.0.1/temp.aspx?id=1;exec xp_cmdshell 'net user admin 123456 /add' 绕过程序的限制 示例:程序中往往限制单引号的数据,但是黑客传入单引号的ASCII码 跨站点注入 在Web页面挂某些恶意的HTML、JavaScript代码 防范SQL注入 限制错误信息的输出,避免错误信息的输出使得黑客知道网站的技术实现采用什么数据库,采用什么平台 示例:在Web.config文件中设置 限制访问数据库账号的权限 在开发应用系统的时候就应该限制,给程序最小访问数据库的权限 使用参数命令传递参数 不要使用拼接字符串的方式构造SQL语句而采用参数命令 使用存储过程 存储过程在数据库中 只能执行存储过程中固定的代码 限制输入长度 防止黑客输入超大字符串,导致服务器瘫痪 防止黑客输入较长的恶意脚本等 实现方法:文本框的MaxLength属性 URL重写技术 示例: http://testWeb/news.aspx?id=111 重写成:http://testWeb/10101111.html 传递参数尽量不用字符串 http://testWeb/news.aspx?id=111 and 1=1【黑色部分给了SQL注入的机会】 SQL优化 为什么要优化 开发是对性能考虑不多【技术差、项目工期紧等原因没有考虑性能问题】 系统运行中,数据量扩大,访问量增多,蹩脚的SQL危害开始显露 低效SQL的危害 系统响应变慢,软件开发中的8秒定律,当打开一个软件或网页超过8秒时间还没有显示,则响应太慢。 死锁,当不同用户都访问某些资源的时候SQL语句不当导致死锁 客户失去信心,软件失败 低效SQL低效在哪里?  性能低下的根源  硬件原因,数据库的配置不合理,数据库的数据文件和日志文件没有分磁盘存放,会极大影响IO性能  没有建立索引,或者SQL没有走索引。在千万级数据的表上建索引是很有必要的。  SQL过于复杂,过长的SQL语句满足程序需求但是影响性能。子查询嵌套过多对性能有影响,查询关联的表特别多也影响性能  频繁访问数据等等 SQL如何被SQLServer执行的 SQL执行原理  解释:首先解释SQL语句【语法是否正确】  解析:检验语句的出现的对象是否有效【进行一个解析,要检查对象所有权的权限】  优化:【检查SQL语句是否能够使用索引,SQL语句如何执行效率高,制定一个执行计划】  编译:  执行SQL语句:【把编译过的查询要求提交并进行处理】 如何优化SQL 完善开发的管理 完善代码审核、测试机制,软件开发是艺术! 检测SQL查询的效率 语法【对IO和Time对SQL执行进行统计】: SET STATISTICS IO ON SET STATISTICS TIME ON ------------SQL代码--------- SET STATISTICS IO OFF SET STATISTICS TIME OFF 注意:在检测之前要清理缓存,因为当我们执行SQL语句的时候查出的数据会在数据库中进行缓存,重新查询会返回缓存中的信息。 DBCC DROPCLEANBUFFERS DBCC FREEPROCCACHE 经验:使用子查询嵌套不要过多,尽量使用表连接查询代替子查询,因为表连接查询效率稍高一点。 SQL优化工具 使用报表服务 通过Reporting Service查找低效SQL 选择 性能 - 批处理执行统计信息和性能相关的几个报表服务: 性能 - 对象执行统计信息 性能 - 按平均CPU时间排在前面的查询 性能 - 按平均IO次数排在前面的查询 性能 - 按总CPU时间排在前面的查询 性能 - 按IO总次数排在前面的查询 操作示范: 1. 数据引擎上点击右键—报表—标准报表—关心的

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值