LFI via SegmentFault 溯源之旅

不畏将来,不念过往。如此,安好!

Someone famous 丰子恺

    前些日子看别人的writeup 上碰见了一个 LFI SegmentFault php 7.0 ,这里面说的一个7.0的小bug,我第一次碰到这样的情况,我感觉很有意思,文章也没有解释为什么,我决定看看为什么。于是先在本机上试了一下


php -r 'print_r(file_get_contents("php://filter/string.strip_tags/resource=/etc/passwd"));'



显然本机上没有报错,本机是PHP 7.2.9-1 (cli)先查了一下这个segmentFault是什么错误

1,内存访问越界

2,多线程程序使用了线程不安全的函数

3,多线程读写的数据未加锁保护

4,非法指针

5,堆栈溢出

心里大概有个了解,于是呼docker php7.0-cli 之

呃呃呃,执行


php -r 'print_r(file_get_contents("php://filter/string.strip_tags/resource=/etc/passwd"));'


单命令之后发现不回显 ,那我看看报错日志,修改了php.ini ,把当前目录挂载进去,有了报错日志。

却发现有的仅仅是一些语法错误,我知道肯定是出错了。

用 docker logs 也没有看见错误信息,我感觉是这个容器的stderr 没输出到我的终端上

我用 sh -c 看看


sh -c "php -r 'print_r(file_get_contents(\"php://filter/string.strip_tags/resource=/etc/passwd\"));"


成功看见了回显


Segmentation fault (core dumped)


当前目录下也有一个core 文件,我用 本机的php 试着打开


gdb php core


看见了报错信息是这段load file出错了

我感觉先从看看这个功能实现的代码和现在的有什么不一样

切换到 php-7.0 下,和 master php7.3 对比一下

定位到关键功能代码处 /root/php-src/ext/standard/filters.c

7.0 172行开始

7.3 170行开始

都是119 行,在sublimerge 下对比一下


typedef struct _php_strip_tags_filter {
const char *allowed_tags;
int allowed_tags_len;
int state;
int persistent;
} php_strip_tags_filter;


第一处,7.3的 state 和 persistent 都是 uint8_t  从int 到 uint 都是一字节应该不影响


static int php_strip_tags_filter_ctor(php_strip_tags_filter *inst, const char *allowed_tags, size_t allowed_tags_len, int persistent)
{
if (allowed_tags != NULL) {
if (NULL == (inst->allowed_tags = pemalloc(allowed_tags_len, persistent))) {
return FAILURE;
}
memcpy((char *)inst->allowed_tags, allowed_tags, allowed_tags_len);
inst->allowed_tags_len = (int)allowed_tags_len;
} else {
inst->allowed_tags = NULL;
}
inst->state = 0;
inst->persistent = persistent;

return SUCCESS;
}


第二处,这个函数应该是构造函数,构造php_strip_tags_filter 这个结果,填充在参数时自定义运行存在的标签,这个函数传参变类型变了,

char *zval_string *

处理上也没什么问题,看下一处


static php_stream_filter *strfilter_strip_tags_create(const char *filtername, zval *filterparams, int persistent)
{
php_strip_tags_filter *inst;
smart_str tags_ss = {0};

inst = pemalloc(sizeof(php_strip_tags_filter), persistent);

if (inst == NULL) { /* it's possible pemalloc returns NULL
instead of causing it to bail out */
return NULL;
}

/*...*/ /*...*/ //省略了一些对自定义参数的处理,因为整个过程我们没有传入,这个参数(哪个标签可以不过滤) if (php_strip_tags_filter_ctor(inst, ZSTR_VAL(tags_ss.s), ZSTR_LEN(tags_ss.s), persistent) != SUCCESS) {
smart_str_free(&tags_ss);
pefree(inst, persistent);
return NULL;
} //php_strip_tags_filter_ctor(inst, allowed_tags, persistent) == SUCCESS ;allowed_tags =zend_string *
           
smart_str_free(&tags_ss);
           
return php_stream_filter_alloc(&strfilter_strip_tags_ops, inst, persistent);
}


7.0 在调用构造函数的时候,把自定义的参数分别传过去用的是 char *  tags, int  len 两个参数

而 7.2 调用的时候是把整个字符参数放在 zval_string *tags传过去的然后再在构造函数里面 分别读出val len

我看了半天也没什么错误啊,都是合理的。

而后我检查了是不是 IO 的问题 我用其他的filter 读相同的文件,没问题

用strip_tags 函数读整个文件字符串,因为filter 时调用的就是 strip_tags 对应的php 内部函数,也没问题

有点苦恼,我再理了一遍思绪,把参数获取的代码全不看,因为没有用到。应该是最后的那个函数filter 构造函数

我看上了这个参数 smart_str tags_ss = {0};

相当于 memset(tags, '/0', sizeof(tags_ss)); 

再看看smart_str 定义:


typedef struct { 

    zend_string *s; 

    size_t a; 

} smart_str;


size_t a 是定义分配的大小,有一个默认值,低于这个值,a就是默认值,如果超过a的值,就加上a的默认值倍数来分配

接着看向传参过程 php_strip_tags_filter_ctor(inst, ZSTR_VAL(tags_ss.s), ZSTR_LEN(tags_ss.s), persistent)

好像不对,,,看看下面zval_string 取值定义


#define ZSTR_VAL(zstr) (zstr)->val

#define ZSTR_LEN(zstr) (zstr)->len

#define ZSTR_H(zstr) (zstr)->h

#define ZSTR_HASH(zstr) zend_string_hash_val(zstr)

相当于 tags_ss.s->val ,tags_ss.s->len

tags_ss. ={0}   tags_ss.s 指向的地址 \x0000000000000000 // 根据cpu的寻址位数 这里是64位

c是根据 指针类型去读一段连续内存,c在我心里其实是很灵活的。题外话...

在\x0000000000000000上读一段 zval_string 结构,当然非法访问了。。

回过头来为什么 7.3 也读了为什么没有出错,其实在一个构造函数里面,一开始 就判断了 allowed_tags != NULL

才会去继续操作zval_string

7.0 上也有check 啊,应该在check 之前他已经开始非法读了ZSTR_VAL(tags_ss.s), ZSTR_LEN(tags_ss.s) 并不会返回想象中的NULL。



很多时候,我在问自己,这些年看了那么多paper,技术还是如此的菜呢,相当于大量的碎片学习,可能没有系统的定制一个机会去学,有一次在看国外大神的paper的时候,他却保持一个观念,他喜欢尝试别人不敢尝试的东西,比如如果你觉得这家运营商足够大,他就肯定很难有漏洞,这个时候你可能会望而却步,早早也就放弃这个目标,其实你也就输在了第一步.

程序是人写出来,因为人很难做到面面聚到,逻辑上总会有错误,我明白这也是为什么真正意义上的hacker 是一个很优秀的程序员,看过了很多paper,学了很多技巧。却总是不去深究这背后到底是什么的存在。后来要想用的时候,有模糊的记忆我知道这个,却也迟迟想不起来,其实学东西需要的是追求去基础的东西,而后举一反三。从复杂变简单是一个抽象的过程。浅尝辄止是每个的都可能面临的问题。

现在我遇的每一个没有答案的问题,我总会觉得不舒服,我想要问个为什么。就算感觉前方一无道路,但我知道所以东西都是有根的,我总会找到它。

永远不说放弃,努力努力再努力,终会如愿以偿![坏笑]

2018年11月08日16:41:08