毕业设计之php RASP(二) 威胁判断

先感谢0kee的大哥指导 ~

这里分为三个部分
1、获取函数的参数值
2、对http传入值的获取,($_GET$_POST$_COOKIE)
3、如何判断存在威胁(词法分析、污染标记)

函数参数获取

Zend中有接口可以获取到参数:zend_parse_parameterszend_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 ");

posted @ 2018-04-26 15:09 l3m0n 阅读(...) 评论(...)  编辑 收藏