先感謝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 ");