毕业设计之php RASP(二) 威胁判断
先感谢0kee的大哥指导 ~
这里分为三个部分
1、获取函数的参数值
2、对http传入值的获取,($_GET
、$_POST
、$_COOKIE
)
3、如何判断存在威胁(词法分析、污染标记)
函数参数获取
Zend中有接口可以获取到参数:zend_parse_parameters
,zend_get_parameters_ex
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|z/", &cmd, &cmd_len, &ret_code) == FAILURE) {
return;
}
或者
zend_get_parameters_ex(argc, &command) == SUCCESS
可以跟进看一下
static int zend_parse_va_args(int num_args, const char *type_spec, va_list *va, int flags TSRMLS_DC)
arg = (zval **) (zend_vm_stack_top(TSRMLS_C) - 1 - (arg_count-i));
或者
ZEND_API int zend_get_parameters_ex(int param_count, ...) /* {{{ */
{
void **p;
int arg_count;
va_list ptr;
zval ***param;
TSRMLS_FETCH();
p = zend_vm_stack_top(TSRMLS_C) - 1;
arg_count = (int)(zend_uintptr_t) *p;
if (param_count>arg_count) {
return FAILURE;
}
va_start(ptr, param_count);
while (param_count-->0) {
param = va_arg(ptr, zval ***);
*param = (zval **) p-(arg_count--);
}
va_end(ptr);
return SUCCESS;
}
可以看到它其实就是从Zend虚拟机的vm栈顶上面获取参数。
当然也可以通过php运行时的一些结构,比如EG(argument_stack)
void **p = EG(argument_stack)->top ;
函数的参数个数保存在int arg_count = opline->extended_value;中,拿到参数个数之后,我们就可以移动指针了。
比如获取该函数的各个参数,也就是函数的调用约定,表达式为:
zval *arg1 = *((zval**)(p - arg_count)); //参数1
zval *arg2 = *((zval**)(p - (arg_count - 1))); //参数2
zval *arg3 = *((zval**)(p - (arg_count - 2))); //参数3
例如:
root@ubuntu:/var/www/html/bishe# cat 1.php
<?php
echo str_replace("world","Shanghai","Hello world!");
HTTP数据获取
从php5.4开始后就自带了一个小型的server,所以可以对它进行调试
gdb /opt/php_debug/bin/php
set args -S 0.0.0.0:1234
run
按ctrl + c
b lemon_php_http_request
run
对于如何从php扩展中获取$_GET
、$_POST
等值,鸟哥一篇博文已经有了分析。
http的信息存在在http_globals,文件位置:php-src-php-5.5.9/main/php_globals.h
#define TRACK_VARS_POST 0
#define TRACK_VARS_GET 1
#define TRACK_VARS_COOKIE 2
#define TRACK_VARS_SERVER 3
#define TRACK_VARS_ENV 4
#define TRACK_VARS_FILES 5
#define TRACK_VARS_REQUEST 6
比如访问:
http://love.lemon:1234/1.php?i=iam&i1=lemon
从PG(http_globals)[TRACK_VARS_GET]
中获取GET请求的数据,它也是一个hash table
ulong ikey;
char *skey;
zval **data;
HashTable *h;
zval *arr;
arr = PG(http_globals)[TRACK_VARS_GET];
h = HASH_OF(arr);
for (zend_hash_internal_pointer_reset(h);
zend_hash_has_more_elements(h) == SUCCESS;
zend_hash_move_forward(h))
{
zend_hash_get_current_data(h, (void**)&data);
zend_hash_get_current_key(h, &skey, &ikey, 0);
}
威胁判断
这部分属于最难做的地方,如果不够精确的话,很影响最后的结果。如果要做bypass的话,可以重点研究此部分。
污染标记
可以在PHP_RINIT_FUNCTION
进行设置,也就是有请求的时候将所有的数据都标记为污染数据,然后再进入流程后可根据一些函数操作来消除标记,最后进入危险函数的时候看污染标记是否存在来确认威胁。
b lemon_php_mark_strings
这里想要加上一个标记就需要了解一下php变量的结构体
typedef struct _zval_struct zval;
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
} zvalue_value;
struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};
变量的值是存在_zvalue_value
中,这里使用联合体也是为了空间利用考虑,因为一个变量同时只能属于一种类型,至于_zval_struct
里面的就是变量的一些说明,比如变量类型,是否被引用等。很蛋疼的事情就是在这里面每个字段都有明确的意义,没有一个预留的字段。
鸟哥的taint这里的做法则是把字符串的长度扩充一个int, 然后用magic number做标记写到后面去
Z_STRVAL_PP(ppzval) = erealloc(Z_STRVAL_PP(ppzval), Z_STRLEN_PP(ppzval) + 1 + PHP_TAINT_MAGIC_LENGTH);
PHP_TAINT_MARK(*ppzval, PHP_TAINT_MAGIC_POSSIBLE);
但是要追踪污染标记是很复杂的,因为经过比如字符串拼接,各种处理的函数,编码之类的后就得重新打上标记。这块也可以看上一篇如何去hook,再进行字符串处理的时候再进行标记一次或者比如进入了intval
就取消标记。
但是毕设我就偷懒一点吧,简单的判断一下Http输入值能不能完整进入函数参数。
<?php
$a = @$_GET['i'];
$b = "sys"."tem";
$b("echo ".$a." iaml3m0n ");